In the part 1 of the series, we had set up SDKs and tools needed for development of our DApps-like Rest API. The second part introduces a sample contract conforms with ERC-20 standard, which will be used by our web api to interact with blockchain.
The overall architecture of our project would look like below.
The advantage of adding a back-end layer is that your front end Dapps is decoupled from the smart contract.
Most demonstrations of Dapps omit the API layer, the web app directly interacts with the blockchain and handles all calls to smart contract via web3js or similar frameworks. Being structured this way the Dapps is tightly coupled with the contract underneath and might end up in a spaghetti architecture where front end and back end code get messed up.
In more complex settings where there are a plethora of contracts to maintain we will need to orchestrate our services differently. If a smart contract is considered a client service, you might want to spin up or tear down new services without impacting normal function of your business. That is where microservice architecture comes in handy. Each microservice serves a smart contract and can be totally independent of other microservices. The API back-end can be hosted on different computers to Ethereum nodes (thanks to RPC comunication protocol). Therefore we achieve three separated layers for our system.
Furthermore, our Restful APIs are also independent of each other and can be hosted on separated servers. This helps you regroup services by business domains, thus achieve a modular but cohesive architecture where miroservices are bounded business context. This is also known as Domain-Driven-Design.
The architecture above somewhat resonates with Hyperledger Sawtooth design. However in Sawtooth, RestApi is tighten up to blockchain node.
With Asp.Net Core and Nethereum, the above system could be easily set up. In our demo project, we only have one smart contract corresponding to one microservice, and there is no front-end client.
1. Create .NET solution
1.1/ Create Solution
Start by create a project root for your solution. Here my solution folder is named “GamerSmartToken”.
With Visual Studio, create a new solution is trivial.
With dotnet CLI (works for Powershell or Windows Command as well as for Linux bash):
dotnet new sln
The solution file will be created and named after your folder.
1.2/ Create Asp.net Core web api project
With Visual Studio you just have to right click on to the solution and add new project.
With dotnet CLI:
dotnet new webapi --name WebApi dotnet sln add WebApi/WebApi.csproj
- The first command create a new Asp.Net Core web api project named “WebApi” from template.
- The second command add your WebApi project to the solution.
1.3/ Move smart contract folder into your solution
Place the smart contract folder with all of its content we have created from part 2 under the same solution for better organization.
I also move TestChain folder into the solution but this is optional.
1.4/ Create classlib projects to refactor your code
In the same solution, I have created two class libraries: ContractInterface.Common and ContractInterface.ERC20 which contains common methods used to make RPC calls to my Ethereum client. Methods in Common library, for exemple deploying or signing contract, can be reused across different web api which interface with different smart contracts. On the contrary, ERC20 library contains only RPC function specific to ERC20 compliant.
These libraries can be packaged and published to your Nuget repository in order to be easily referenced across different projects.
The rest of the tutorial will refer to this solution structure.
2. Install Nuget packages
We’ll need to add Nethereum package to our three projects: WebApi, ContractInterface.Common, ContractInterface.ERC20.
Tips: ideally I should have wrapped all my types in WebAPI project to reference only to my classlibs, so that my WebApi would never has to interface directly with Nethereum.
From Visual Studio, right click References on your project -> Manage Nuget packages.
In CLI, from solution root folder:
dotnet add WebApi/WebApi.csproj package Nethereum.Web3
dotnet add ContractInterface.Common/ContractInterface.Common.csproj package Nethereum.Web3
dotnet add ContractInterface.ERC20/ContractInterface.ERC20.csproj package Nethereum.Web3
3. Project Web Api
NOTE: before start developing, remember to start our test chain and geth node in order to test. See Test section.
3.1/ Add references:
We have to add references to our classlib in our project Web API beforehand.
From project folder:
dotnet add reference ../ContractInterface.Common/ContractInterface.Common.csproj ../ContractInterface.ERC20/ContractInterface.ERC20.csproj
3.2/ Configure dependency injections in Startup class
Dependency injection is a nice way to separate usage from creation of objects. By delegating the creation of objects to dependency controller, you no longer have to worry about properly properly instantiating and managing object’s lifetime. If you wish to know more about this design patters, please refer to links at the end of the article.
In Asp.Net Core, implementing dependency injection is trivial. All you have to do is registering your types to service container in Startup.cs.
As you can see in my Startup.cs, I have configured my ContractFacade, ContractOperation, AccountService and ContractService as services to be manage by the container. Each of these services require different life cycles and thus the service container offers three options to free you from headache:
- AddSingleton: created once per app instance
- AddScoped: created once per web request
- AddTransient: created each time requested
You also have the choice to register either your concrete classes or interfaces with their implementations. The advantage of registering interfaces is that you can change your implementation at any moment without worrying about dependencies breaking your code. Thus each time your service interface is requested by consumer classes, the service container will look for registered implementation and resolves the dependencies.
3.3/ Configure Authentication service
Our application needs an authentication/authorization scheme in order to verify user’s identity.
Asp.Net Core provides authentication service as a very neat way to implement login schema in your application. Using it is as simple as registering a dependency injection in Startup.cs.
In Startup class, I have called AddAuthentication from ConfigureServices method in order to configure a login logic using Json Web Token (JWT).
By JWT standards, when user sends an authentication request to server, if credentials match the server will response with a token to authorize user’s session. This token is included in each of subsequent requests in order to maintain login session until expires. The token must be digitally signed by server so that it cannot be tampered with. And also remember to give it an expiration to minimize consequent in case of being intercepted, because any one with the token can use it to impersonate you and send requests to the server.
Then you have to add the service into app’s middle-ware pipeline in Configure method, above UseMvc().
In AccountService class, you have to configure Authenticate method in a way that it verifies given credentials and issues new token when the verification passes correctly.
Look into Authenticate method, I have verified user’s ethereum address and password against two hard-coded accounts. The token issued expires within one hour, and contains a claim of user’s identity. The token is hashed using symmetric keyed hash algorithm HmacSha256 on the payload, header and secret key. This is tamper proof because only the server knows its secret key used against hash function, thus only the server can provide a correct hash for the token. If the token has been changed a bit when returned to server, it will be rejected because the hash does not match.
The SymmetricSecurityKey used to calculate hash for the token is created using a provided passphrase configure in appsettings.Development.json.
To retrieve the key from appsettings, I use GetSection method from IConfiguration service (please refer to AccountService.cs image above).
In your controllers, so as to activate authorization scheme, apply [Authorize] attribute to each controller. If you want to exclude one method from authorization scheme, use [AllowAnonymous].
So to recap:
- In Startup.cs: 1/ configure Authentication service, token validation scheme and the type and characteristics of token to be accepted for validation. 2/ Activate authentication middle-ware.
- In AccountService.cs : configure the issue of authentication token by verifying user’s credentials.
- In API controllers: apply authorization scheme if needed.
Here I used the bare minimum of Identity service provided with dotnet core to verify user’s ethereum address and password. You can very much improve it by implementing a much more complex scheme such as using Entity Framework to configure a database, or associating user’s identity with their Ethereum transaction account (similar to a wallet).
3.4/ Contract Controller
ContractController.cs contains all methods related to our smart contract such as deploying contract and retrieving information specific to contract’s characteristics.
- IConfiguration: configuration service of Asp.net core to retrieve information from appsetting.json
- IContractFacade: type of ContractInterface.Common which interfaces with Nethereum to operate upon any smart contracts.
- IContractOperation: type of ContractInterface.ERC20 which interfaces with Nethereum to operate upon ERC20 contract.
- IAccountService: service specific to our Web Api which manages authentication and ethereum accounts to make transactions.
- ContractService: service specific to our Web Api which manage operations on contracts.
- ILogger: logging service of Asp.Net Core
There are two deployment methods provided in the controller. Deploy() allows deploying any type of ethereum smart contracts to blockchain, and DeployDefault() helps deploy our previously created smart contract without a request body (mostly used for test).
Tips: I use wrapper types as inputs for request methods because the Asp.Net controllers implicitly derive inputs from request body (the same as using [FromBody] attribute), and it can only deserialize one input per request body.
To retrieve information related to a particular contract, I add contract address in the route for request methods. This allows getting information of any contracts ERC20 contract given its address.
I don’t place contract address to controller route because the controller contains methods which operate on new contracts that haven’t yet existed on blockchain.
Generally it is not a good practice to implement try-catch blocks in controllers because it is exhausting ! You should instead configure a middle-ware to globally handle exception.
3.5/ People Controller:
PeopleController.cs contains request methods related to operations on holders and spenders of our ERC20 contract such as adding, removing people, inquire for their balance or status, and transfer tokens.
All methods in the controller operates on an existing contract, that’s why I place contract address in the controller route.
3.6/ Account Service:
The account service contains methods for authentication and to manage user’s ethereum accounts. All transactions that change contract’s state in Ethereum need to be signed by an unlocked account. Nethereum provides ManagedAccount as a wrapper type for Ethereum account, which manages unlocking scheme.
4. Using Nethereum to interact with smart contract
The two class libraries use Nethereum to make RPC calls to our smart contract.
To make function calls, what we need:
- Contract: Nethereum’s wrapper type for Ethereum smart contract. Contract object is deserialized from abi by Newtonsoft, hence we need a way to retrieve the compiled contract’s abi. This is the reason why I organized our previously created smart contract into a folder (SmartContracts) under the solution root. By doing so, you can easily create another smart contract and web api for it. A cleaner way to do this is using Entity Framework core to create a database for your project.
- Sender’s ethereum address
- Function name
- Web3 object: I created a wrapper to retrieve transaction receipt using Web3 object and transaction hash. The built in funtion “SendTransactionAndWaitForReceiptAsync” doesn’t work as expected.
- Function inputs: in the same order of appearance as of smart contract.
The above GenericTransaction serves as base function to interact with smart contract. To call a function from our contract:
Voilà. Simple as that.
5. Test APIs
In order to test our API we need to start our Test chain and Geth node. Open TestChain folder in terminal and type:
I exported all my tests into a json file placed under Test folder. You can import it into Postman to serve as a base for your tests. The first test must be authentication using one of the two accounts provided from AccountService.cs. Next remember to change authorization token or deactivate the service before testing.
The full project is open to access on Bitbucket.
Hosting option really depends on how much decentralized you want your system to be. Front end dapps and back end api can be hosted in similar ways.
Using HTTP web server
This is really the centralized way to host your services. The web server communicates with a remote blockchain node and it becomes the single entry point to serve your client apps. If you really want democracy, this is a no-go. However, it is can be a very efficient choice depending on the size and nature of your business. If your business is in legal services, you might want have a set of partners to maintain the blockchain (or a distributed ledger). Your clients on the other hand might not care about the state of your ledgers and they are not supposed to. The web server is therefore the spot to connect the outside world with your closed-circled maintained blockchain.
Using IPFS (interplanetary file system)
If you want your system to be 100% distributed, IPFS is the choice. IPFS is a peer to peer distributed file system similar to torrent. Each machine in the IPFS network is a peer (also a node), and serves as both client (leech) and host (seeder). Files are served from peers (or nodes) in the network. Your Dapps (front end and back end) can be packaged into files and distributed via IPFS. Just like blockchain, once distributed your files can no longer be changed because each is digitally signed and uniquely identified in a cryptographic file structure (Merkle DAG).
More about IPFS here.
Other parts of the story: