======================== Architecture Description ======================== The goal of this document is to outline the interactions of all the pieces involved in the current iteration of the protocol. It aims to fix interaction patterns of certain subsystems without finalising decisions on implementation choices. This should also make *Phase 0.5* more understandable for everybody. Prerequisites ============= We expect a certain baseline understanding of what we are doing in general (Building software systems for :term:`Stores` which sell physical Goods. Currently in a physical location, meaning not e-commerce. If you *are* new to this, you might want to read our `introduction blog post `_). - Conceptual familiarity with `Public-Key signature schemes `_. - **Users** of a :term:`Store` are expected to have a :term:`Wallet`. - :ref:`smart-contracts`: The **Registries** managing configuration and discovery of :term:`Stores` and :term:`Relays`. For now we assume that the mapping between a :term:`Store` and its :term:`Relays` is known. .. contents:: Table of Contents :depth: 3 Creating a Store ================ The first thing a merchant needs to do is the creation of their store itself. This is done by minting **NFT** since the :ref:`sc-registry-store` is an extension of `EIP721 `_. It therefore supplies the standard conformant :sol:func:`mint` function. The ``storeId`` can be chosen by the caller but already taken IDs will lead to a revert. Adding Users and Logging in =========================== Next, we introduce the concept of **Users**, how they are registered with a :term:`Store` and how they get access to the :term:`Listing` and :term:`Inventory`. For this interaction flow, we differentiate **Users** into **Clerks** and **Admins**. - **Registered** are all **Users** which have a **Wallet Address (WA)** assigned to the :ref:`sc-registry-store`. - **Admins** can add or remove **Clerks** - registered **Users** can authorise **KeyCards (KC)** which give them access to the :term:`Store` .. note:: There is only one **Owner**, which also counts as an **Admin**. Only the **Owner** can turn **Clerks** into **Admins** and vice versa. .. _adding-clerks: Onboarding a Clerk ------------------ To add a new **Clerk**, an **Admin** creates a **Invite Secret** ``IS`` and a corresponding **Invite Verifier** ``IV`` via their Client software and sends the Link containing the ``IS`` to the **Clerk** via some side-channel (for example E-Mail or Signal). Its public part, the ``IV``, is submitted to the :ref:`sc-registry-store`. - ``IS`` and ``IV`` are the secret and public part of an ECDSA key-pair. - (While this processes could be modelled with a hash-function, we need to account for what is known as `front running `_ in Ethereum and similar systems, where the inputs to a contract are revealed to the public. Using signatures instead ensures *only* the Clerk had possession of the ``IS``) - The ``IS`` is single use. - All **Admins** can see all ``IV``\ s for their :term:`Store`. When the **Clerk** opens the Link, they are presented with a registration page. The main goal here is to connect with MetaMask to get the **WA of the new Clerk.** We *could* also collect other metadata about the **Clerk** at this point, like their name, an avatar or their e-mail. We could also do a reverse-lookup of an associated ENS address (e.g. my **WA** maps to ``cptx.eth``) but this might have to wait until a later stage. The ``IS`` is redeemed by calling the contract function :sol:func:`redeemInvite`, which changes the invites status for the **Admins**. It checks the signature by using `ecrecover `_ and checks if that returns a valid **IP**. Afterwards this process finally adds the new **WA** to the Relay configuration, which completes the registration of the **Clerk**. The :ref:`flow-adding-clerk` shows the interaction between the different actors. .. uml:: flow-0.1-registration.puml :name: flow-adding-clerk :caption: "Adding a Clerk" flowchart Authorizing Access ------------------ To gain access to the :term:`Store`, the **Clerk** needs to authorise a :term:`KeyCard` (**KC**) using their **WA**. The main goal of the **KC** is to reduce the number of interactions with the :term:`Wallet`. The HTTP Request for this is defined in :ref:`enroll_key_card`. 1. Clerk generates a new asymmetric key-pair, which represents the **KC**. The **KC** is stored in the Store page and is short-lived. 2. The public part is signed with the **WA** of the **Clerk**, prompting a Wallet interaction 3. A request containing **KC.public**, **StoreId** and the Signature from (2) is sent to a corresponding :term:`Relay`. 4. The Relay checks the Signature and if valid, calls :sol:func:`hasAtLeastAccess` and checks if the **WA** from the request is part of the list. 5. If everything does check, the **KC** is added to the list of *allowed* **Users**, which can then be used to initiate remote procedure call (RPC) connections to all Relays. Logging in ---------- The Login mechanism is necessary for elevated RPC commands that are not public, like changing the :term:`Inventory` of the :term:`Store`. - It follows a simple `challenge/response scheme `_ to protect against replaying. - It is started by sending :ref:`ref_market.mass.AuthenticateRequest` to the :term:`Relay`. - The **Clerk** proves control over their **KC** by signing a ``challenge`` the :term:`Relay` responds with. - The **Relay** checks the ``signature`` in :ref:`ref_market.mass.ChallengeSolvedRequest`. - Afterwards the **Clerk** can use the **KC** to sign :ref:`Events` and perform actions on the :term:`Store`. The :ref:`flow-logging-in` shows the interaction between the different systems. .. uml:: flow-0.2-auth-and-login.puml :name: flow-logging-in :caption: "Logging in" flowchart :term:`Store` and :term:`Relay` synchronization =============================================== .. note:: Our working assumption is that :term:`Relays` and their Clients will build the :term:`Listing` and :term:`Inventory` with :term:`Eventual Consistency`. Meaning, all changes to a *Listing* will be individual :ref:`Events`. To gain the current state of a Listing, all Events are replayed and applied locally. Items in the Store: Retrieving the Listing ------------------------------------------ Now that the **Clerk** can do things in the :term:`Store`, let's discuss the first common action: display items in the :term:`Store`, also known as: the :term:`Listing`. - The Relay keeps track of which :ref:`Events` have been acknowledged by a :term:`KeyCard` and which have not. - Once the Store connects to the Relay, the Relay will send all Events that have not been acknowledged yet. - Store *should* verify listing state against on-chain data The :ref:`flow-show-listing` shows the interaction between the different systems. .. uml:: flow-1-show-listing.puml :name: flow-show-listing :caption: "Retrieving the Listing" flowchart Updating the Listing -------------------- To change the :term:`Listing` we need to create :ref:`Events`. The *Event* will result in a new reduced state of the Listing, also known as the **root hash**. To keep :term:`Relays` honest, this root hash is also saved on the block chain, to protect against omissions or other corruptions. See :ref:`sc-store-config` for more details. Events have to have a known type. Currently we foresee: - Add new Item (which creates a new identity) - De-list an Item (marks the Identity invalid for future use but not non-existent) - Update metadata associated with an Item - Change stock count of an Item - Creating Carts and assigning Items to them - See :ref:`ref_schema.proto` for a full list. - See :ref:`events-mutate-listing` for simplified visual illustration of the concept. .. figure:: ../images/EventsMutateTheListing.png :name: events-mutate-listing Events mutate the listing .. note:: We might have a 2nd class of Events, that are non-durable. Specifically, these might be cart-related events or other session data that will have to be kept in sync between relays but not mutate the listing, and thus on-chain data, immediately. Selling Items ============= Now that we can add and remove Items from the :term:`Store`, let's discuss how they are actually sold with our system. .. warning:: To protect from selling items twice, the :term:`Relays` will help with book-keeping. To use an analogy from the physical world: the :term:`Relays` will keep a virtual copy of the shopping cart, marking **items** as used once a cart is finalized or the process expires. Building a Cart for a Customer ------------------------------ .. note:: This flow follows the assumption that we are in *pop-up mode*. Meaning, the **Clerk** will build the cart for the **Customer**. Changing this mode of operation to an e-commerce/self-checkout setting would not be that much different, though. When a **Clerk** adds an Item to the **Cart** the **Client** creates an :ref:`ref_market.mass.ChangeCart` Event, which is sent to the :term:`Relay`. The :term:`Relay` checks the event and signals availability of :term:`Inventory` back to the :term:`Store` page. (As well as other :term:`Relays`, in Phase 0.75 and onward.) The :ref:`flow-fill-cart` shows the interaction between the different systems. .. uml:: flow-2.1-fill-cart.puml :name: flow-fill-cart :caption: "Building a Cart for a Customer" flowchart .. warning:: Item counts are checked against the total stock. Carts don't lock up stuck until check-out has begun. This means that the stock count can change between adding an item to the cart and checking out. However, Clients can see when other carts use up stock (via :ref:`ref_market.mass.ChangeCart`) and will also receive :ref:`ref_market.mass.ChangeStock` Events when other carts completed checkout. Completing the Purchase ----------------------- Once the **Cart** is filled with the desired Items, the **Clerk** initiates the check-out via :ref:`ref_market.mass.CommitCartRequest`. This will collapse the Cart into a single update of the :term:`inventory` and the :term:`Relay` will yield a :ref:`ref_market.mass.CartFinalized` with the needed infromation, like the purchase address and the amount to pay. Once the **Customer** has paid their purchase, the :ref:`ref_market.mass.ChangeStock` Event is applied to the :term:`inventory` by the :term:`Relay` and on-chain data is updated. The :ref:`flow-check-out` shows the interaction between the different systems. .. uml:: flow-2.2-check-out.puml :name: flow-check-out :caption: "Completing the Purchase" flowchart Privacy Considerations ====================== .. warning:: **Now and next**: There is a bunch of things in this current iteration we want to test, knowing that they won't remain that way. There might be breaking changes in future versions of the protocol. - Listing data will be public - we want to make it possible to be indexed and aggregated in the future. - Inventory counts, too? This will give a clear transparent picture of how good or bad a store is doing. - Users that are running the store. This should be private/internal company data. - We need to think about how to protect this data from being leaked. Probably using seperate logs with stricter access control. - The **merchantAddr** will be where all purchases is are collected. - can someone backtrace all sales from that account address? - assuming yes, we will see the **paymentAddr** of individual sales and thus their account address, too? - What exactly hashes into the **receiptHash**? - Can listing changes be correlated to purchases?