This article provides an overview of the supply chain scenario chosen for our analysis, the architecture and technologies used for creating a blockchain network and smart contracts, and a reference implementation. Additionally, in the appendix, we assess alternative approaches and make recommendations for our implementation.
We applied our understanding of blockchain by creating a use case for a consortium network involving a supply chain. The supply chain includes four actors: Producer, Distributor, Retailer, and Customer. Transactions occur between a seller and a buyer, with the actors taking on different roles depending on the step in the process. To simplify, we focused on the relationship between the Retailer and Customer. To implement this, we created two smart contracts: one for storing items and their stock information, and one for managing the workflow of creating, accepting, canceling, and delivering orders. The latter contract also operates the Stock contract to ensure orders are valid and updates stock accordingly. We chose to use two separate contracts to maintain clear distinctions in responsibilities.
The diagram below illustrates the process when a consumer places an order with a retailer. Smart contracts are used to facilitate the operations between the two parties, with some operations accessible to the consumer and some restricted to the retailer. Notifications are sent when relevant operations are completed, allowing the consumer to track progress and the retailer to confirm and deliver the order.
If an operation fails (e.g. an item in an order has no stock), the transaction is reverted, which means all the state changes it produced are dismissed. However, the application will return a message explaining what went wrong.
Our solution was implemented on Azure, using various resources to create the entire workflow. All of these resources were deployed under our Azure AD subscription. We developed two smart contracts (Retailer and Stock) in Solidity and deployed them on an Azure Blockchain Service consortium network. These contracts generate events to notify and provide necessary data about the operations within them. To communicate with these contracts and provide entry points into the entire system, Azure Functions were created that let users call contract functions by means of any REST API client. Additionally, Azure Blockchain Data Manager was used together with an Event Grid Topic to capture emitted events from the contracts and send them as notification emails and entries to a Cosmos DB instance.
Azure Blockchain Service provides a set of tools that simplify the creation and management of consortium blockchain networks, which allows developers to focus on the business logic of their deployed apps, rather than the underlying infrastructure. Azure Blockchain Service has built-in consortium management. In this context, a consortium is a logical group used to manage the governance and connectivity between blockchain members who transact in a multi-party process. Pre-defined smart contracts determine the actions that a consortium member can take. The administrator can invite other parties to join the consortium. Azure Blockchain Service resources are isolated in a private virtual network. Transaction and validation nodes are virtual machines (VMs) that can’t communicate with other VMs in a different network. This ensures communication remains private within the virtual network. Transactions can be sent to blockchain nodes via an JSON-RPC endpoint. The JSON-RPC endpoint includes the provider member as well as an access key. JSON-RPC is a type of RPC protocol which uses JSON to encode requests and responses between client and server. The JSON-RPC interface uses Ethereum JSON-RPC Protocol with Web3 authentication. The JSON-RPC calls can be done over HTTP or WebSockets (See documentation ). Clients communicate with a transaction node using a reverse proxy server that handles user authentication and encrypts data over TLS (see Azure’s official documentation for more details). The reverse proxy is getting the RPC and then looking up the authentication information using the OAuth Protocol. The Reverse Proxy uses TLS Protocol to communicate with the transaction node.
The Venn diagram below illustrates a consortium of three members. All nodes within the network can view data, but the Quorum protocol enables certain transactions to remain private among participants (as outlined in the “Private transactions in Ethereum” section of our Basic Concepts post).
In this sample consortium, transactions will follow this path:
- A new RPC-JSON transactions comes from a client, the entry point of each member will be this reverse proxy.
- The authorized transaction pass through the proxy to the Transaction Node.
- The Transaction Node sent it to the Validator Node.
- The Validator Node broadcast the transaction between all the Validators. Then it is queue to be validated.
Azure Blockchain Data Manager allows loading blockchain applications to capture, transform and deliver transaction data to Azure Event Grid Topics , which are endpoints where events can be sent. There are built-in topics, but users can also create custom ones. When a custom topic is created, an Event Grid API endpoint is created and associated to it. Azure Blockchain Data Manager internally performs an HTTPS POST request to this endpoint to send events to it. For more details, see ‘Capturing and viewing events’ section below.
We used this tool to capture events from our smart contracts and consume them. Azure also provides a way to connect Blockchain Data Manager with a Cosmos DB instance, which we explain later in this post.
Consumer action flow
In this section we explain how each Azure component fits into the action flow from the consumer’s perspective.
- The consumer creates a customer profile through the Consumer API which is identified by a unique user id which is returned.
- The consumer creates an order through the API using the user id from the created user profile.
- Internally, the corresponding endpoint creates an JSON-RPC transaction with the transaction node of the consortium member where the contracts are deployed.
- The createOrder function from the Retailer contract is called, which generates a transaction in the blockchain.
- Blockchain Data Manager captures events from the transaction node and sends them to the Event Grid Topic.
- A Logic App , consortium-events, triggers when the Event Grid receives events and sends them as notification events, while also storing them in a Cosmos DB instance.
Smart contracts are self-executing agreements that help to automate processes and verify the performance of contractual obligations. They are written in code and stored on blockchain networks, allowing them to be securely and efficiently executed without the need for a third-party intermediary. OpenZeppelin provides contracts that allow for ownership to be handled using modifiers that can be applied to contract functions to restrict access to certain addresses. These contracts enable the creation of a contract owner or a whitelist of addresses that are able to operate the contract, as well as allowing for the transfer of ownership.
The Stock smart contract
This contract holds the supply chain’s inventory in the form of a collection of stored items together with their basic info and available stock. Whitelisted addresses can only operate this contract.
In the basic scenario, with a retailer and a consumer, the retailer and the Retailer contract must be added to the whitelist. This will allow the retailer to add and remove items and update their info and stock. It will also allow an item’s stock to be decreased once an order is accepted, through the contract.
On the other hand, the consumer will not be whitelisted and will only be able to view items and their stock (which are public operations in our scenario).
To implement the whitelist, we used OpenZeppelin’s WhitelistedRole contract.
In the following subsections, we explain this contract’s implementation.
Stock contract data structures
Items are organized as a Solidity mapping, which allows to index elements in a key-value format. This mapping is called items, where the key is an unsigned integer value representing its id and the value is a struct called item. This struct contains the following fields: name, description, category (strings) and stock (integer).
The items mapping uses 32-bit unsigned integers as keys (item IDs), which means there will be a maximum of 4,294,967,296 (2³²) available keys. We consider this number is more than enough for our application, so we don’t need to use larger integers as our item IDs. On the other hand, 16-bit integers might have been a bit limited.
Stock contract functions
We implemented the following functions for this contract:
- itemExists (private: only accessible within the contract)
- createItem (only whitelisted addresses)
- removeItem (only whitelisted addresses)
- increaseItemStock (only whitelisted addresses)
- decreaseItemStock (only whitelisted addresses)
- updateItemName (only whitelisted address)
- updateItemDescription (only whitelisted address)
- updateItemCategory (only whitelisted address)
Stock contract events
Events are emitted from some of the functions, returning messages as arguments for logging purposes and/or obtaining relevant values.
The Retailer smart contract
This contract contains the logic that allows orders to be created and delivered and relies on the Stock contract to query, select, and decrease items stock.
As every contract it has an owner, who in the consumer-retailer scenario, is the retailer. Some functions are accessible to the consumer to perform operations such as creating an order, canceling an order, and confirming a delivery. Others are restricted to the owner, for instance, accepting, rejecting, and delivering an order. OpenZeppelin ‘s Ownable contract is used to accomplish this. This smart contract provides a modifier called onlyOwner that can be added to specific functions in a contract, which only allows the contract owner to call them (the owner is the address that deploys the contract by default, but this can be changed or ownership can be transferred at any point).
In the following subsections, we give details about the implementation.
Retailer contract data structures
Orders are organized in a mapping that indexes them by ID (unsigned integer). Each order is a struct that contains three fields: customerAddress, customerEmail, status and items. The last field is another mapping that holds the selected items, each of which is a struct that contains itemId and itemQuantity.
Below is a diagram showing the different states that an order can be in:
Retailer contract functions
This contract contains the functions listed below. Some functions are restricted to the contract’s owner, which is done with OpenZeppelin’s Ownable contract, using the onlyOwner modifier. Others require that the customer who placed the order is the caller. When an order is created, the user’s Ethereum address is stored in the order struct as the customerAddress field. This is achieved by assigning msg.sender (the address of the function’s caller) to this field. When attempting to cancel an order, comparing msg.sender to the customerAddress field that was stored in the order will determine if the caller is in fact the same user who created it.
- orderExists (private: only accessible within the contract)
- getOrderStatus (only the owner or the customer who placed the order)
- checkOrderItemsAvailability (only the owner)
- cancelOrder (only the customer who placed the order)
- getNewOrders (only the owner)
- acceptOrder (only the owner)
- deliverOrder (only the owner)
- rejectOrder (only the owner)
- confirmDelivery (only the customer who placed the order)
- updateMinItemsPerOrder (only the owner)
- updateStockContract (only the owner)
Retailer contract events
The following events are emitted from the contract. All of them provide a message and relevant data in their arguments.
Entry points: Azure Functions
Azure Functions implementation
Azure Functions is a serverless compute service that allows the user to run event-triggered code without having to explicitly provision or manage its infrastructure.
For each contract, the extension automatically generates two C# files: the contract definition and service. These allow developers to interact with the smart contracts from C# applications. In this case, we used them to develop the code for our Azure Functions. Here you can view the Azure Functions to interact with both the stock and retailer contracts. The image below shows the project’s folder structure, highlighting the auto-generated files and the ones that correspond to the Azure Functions.
Capturing and viewing events
As we explained, both contracts emit events when certain operations are executed successfully. Azure provides tools to capture these events and process them to be consumed in different ways. We chose to store them in a Cosmos DB instance and send email notifications.
To accomplish this, we had to set up several things, which we explain in the following sections.
Azure Blockchain Data Manager
The steps to configure Blockchain Data Manager are explained in detail in the official documentation . However, here we provide a summary.
- First, we created an Event Grid Topic. For testing purposes, we deployed a web app that Microsoft suggested in the same guide to view events. We added the app’s URL as an Event Subscription within the Event Grid Topic.
- Then, we created a Blockchain Data Manager instance, which points to the Event Grid Topic we created, specifying the transaction node of the corresponding consortium member.
- We added a blockchain application for Blockchain Data Manager to decode its events. To do so, we executed the following steps:
- We saved the contract’s ABI and transaction bytecode (a hex code that identifies a contract that was deployed to the blockchain).
- We created a Storage Account and uploaded the two files.
- For each file, we generated a Shared Access Signature (SAS) URL from the portal.
- On the Blockchain Data Manager instance, we added a new application, specifying the SAS URLs for both files.
4. Finally, by triggering events from our application, we could see them in the deployed web app. The event shown in the image below corresponds to an address added to the whitelist in the Stock contract.
Storing events in Cosmos DB
Azure provides tools to create a Cosmos DB instance and to connect it to Blockchain Data Manager. This tutorial explains the steps to send data from Blockchain Data Manager to Cosmos DB. The first few steps are how to configure Blockchain Data Manager and add an application, which we already explained. The following steps are:
- Create an Azure Cosmos DB account and add a database and container.
- Create a Logic App to connect Event Grid to Azure Cosmos DB. We named ours consortium-events.
- Add an Event Grid trigger that fires when an event happens, or a condition is met. In our case, the trigger is when a resource event occurs, for which we specified our Event Grid Topic.
- Add a Cosmos DB action to create a document in Cosmos DB for each transaction. In this case, we used the message type as the partition key to categorize messages.
With this setup, we could capture events from our applications and view the data in an ordered manner.
Using events to get the function return value
There are two kinds of functions in a Solidity smart contract: calls and transactions. A call is a function that does not change the state of the blockchain, while a transaction does. For example, a function that just reads a value stored in the contract is a call, while one that updates its value is a transaction.
Any function in Solidity can have a return value. However, in Nethereum, only calls allow an application that uses the contract to access this value. As no state changes occur and, hence, no transactions, the return value is immediately available. Transactions, on the other hand, return a receipt, which contains details about the transaction, but not its return value. Transactions must be verified by the blockchain’s consensus mechanism, which makes the value not immediately available. See the Nethereum documentation for more information.
As a workaround to get the function’s return value from within a transaction, we can publish the function’s return value in an event. By doing this, the return value can be made available to any application that is listening to the event.
To run our solution, you will need to clone our repository here.
Then, you will need an Azure subscription (a free trial is available).
Finally, you will need an IDE. We used VS Code as our main one, although we also used Remix to test some things. Remix is an online IDE, which is very convenient for running quick tests.
Once in VS Code, we used the following required extensions:
If you use Remix to run smart contracts or to interact with them, keep in mind that you will need a wallet app. We used metamask to connect with the blockchain.
To check API payloads and available endpoints there is documentation for the Function Apps endpoints:
- In our scenario, we are passing the contract address (Basic Concepts [⁷]) as a parameter in all API calls for the sake and simplicity of it. The user id is being past to link the consumer to the operation and is used to obtain the user profile. We get this contract address as part of the result of the contract deployment.
Unfortunately, the contract address is a 42-character hexadecimal number, which must be known to make any API calls involving smart contracts. In a real scenario, the user will not need to know the contract address. The user would be able to use a more user-friendly name, for example, the company’s name, to identify the smart contract.
- Also, for simplicity, we have not secured our APIs. For a customer to call an API endpoint, they just need to provide an ID, which is used by the backend to obtain their Ethereum credentials from a database. In a real-world scenario, the APIs will require the customer to have an authorization token obtained through a login flow. Additionally, the customer’s credentials would be stored in a safer environment, such as a key vault.
- In our solution we stored wallet’s passwords in a CosmosDB collection just for simplicity. In a real application, those wallet passwords should be stored properly in user profile encrypted and salted at least. Remember that those passwords can’t be changed or recovered.
A different approach: Logic Apps
Another approach could be using Logic Apps to act as entry points for our contracts. These allow calling contract functions through REST API requests.
Using the Logic Apps builder, the developer must specify input parameters and the smart contract functions that must be called. To reference the contract’s instance, the user must provide the contract’s ABI and bytecode (a hex code that includes the constructor logic and parameters of the smart contract, also known as constructor bytecode or creation bytecode). For all functions except contract deployment, the contract’s address must be provided as a query parameter in the endpoint’s URL.
We chose the Azure Functions approach over this one because it provides more capabilities. Moreover, the Ethereum Connector used in Logic Apps was deprecated last August.
Solidity Extension for Azure Functions
When creating Azure Functions, we recommend using the Solidity extension . The Azure Blockchain Ethereum Extension presents issues with the uint32 type, which is used in some of our contract functions and their associated endpoints.
Customers’ wallets in API
To identify different customers in our distributed app, you can use our Consumer API, where creating a customer profile is as simple as running an Azure Function and it will be stored in a database with its name, email, id and key. This will create a wallet that will be associated to the customer.
Authenticating Contracts Using Whitelisted Role
To authenticate a contract (A) into another contract (B) we recommend using WhitelistedRole access control. It allows you to whitelist contract A and anyone who needs access. With this approach, there is no need to add anything into contract A for authenticating. Plus, the usage is like Ownable’s access control.