Tutorial - Smart Contract

Tip: This tutorial is for people who prefer learn by doing, if you prefer learning the concepts use this guide. Note that this tutorial can be taken along the concepts guide.

What you will find here

This tutorial will walk you through the process of designing, creating, testing, and configuring/installing a smart contract system for a multi-party identity attributes network.

Imagine you never need to drag papers to certify a life event, or even have to call the certifier (a University, government, employer) for people to believe you. Just one app connected to a blockchain network!

Hopefully after this tutorial you will be ready to go and create your own enterprise smart contract systems.

Prerequisites

These dependencies come from Hyperledger Fabric directly as it runs well on Node 8.x. And Docker is used to get started with a local network as soon as possible.

  • Node 8.11.0 (other versions might work, but this is the one we use for development)
  • Docker

Install dependencies

We initially built Convector which could be added to Node projects for Fabric by importing its libraries, but we quickly realized thanks to the community than an easier way to get up and running was needed, se we created the Convector CLI. Soon after we noticed how much people struggled to go through Fabric scripts and tutorials to get the network up and running, so we took a small lib from Convector to create testing networks and we spin it out as Hurley.

That said, you can easily install both by running this:

We have noticed that Linux users sometimes face some difficulties, some related issues have been documented and referenced here please check them out before raising a new issue. 

Tools

When you create a project with Convector CLI your project will have Hurley as a dependency, but installing it globally can give you much more flexibility.

  • Convector CLI is accessible through the command conv.
  • Hurley is accessible through the command hurl.

This tutorial won't go through an extensive explanation of each tool, so please refer to their Github home pages to learn the basics and all of their commands. Convector CLI and Hurley.

Designing your smart contract structure

Convector is based on Models and Controllers. As basic structures, you will add your data models in the *.model.ts files and your business logic in your *.controller.ts files. Read more on models and controllers on each corresponding DOCs page.

For our project, we would like to represent the following data structures somehow (it's a best practice to avoid skipping steps, so let's first think on the "what" and then the "how"):

  • Network Participants. These are the companies issuing attributes in the network. They are responsible for what they state for users and are identifiable in the network.
  • Attribute. It's the statement that a Network Participant certifies for a Person.
  • Person. These are the people representation in the network. They have attributes assigned and can simply query them as needed to certify in front of a third party.

Network Participants should be fairly straight forward to represent as there are already patterns for managing identities in the network with Convector on Hyperledger Fabric.

In the case of Person we will need to think the logic better. In our business requirements we need the ability to:

  • Create a person should be responsibility of just one organization - the government.
  • Each organization (network participant) should be able to issue or certify attributes.
  • Each organization (network participant) should be able to check/query attributes from a person.
  • A single ID should be enough to query all the information related to one person and his attributes.
  • Only the organization that certifies an attribute can edit it.

The flow is quite simple:

  • The government can "enrolls" a new person.
  • The government can add some attributes to that person by default.
  • Then, other organizations can start to issue attributes (profession, insurance status, work experience).
  • Every change in the attributes issued will be evident. Changes on attributes can only be performed by the organization that issued them in the first place.

From a software design perspective, it'd be helpful if you have two packages that get merged into a single smart contract.

This way, you can keep the logic of Network Participants separate from your Person and Attributes logic.

Creating your project

To start a new project, go to a folder where you'd like the project to be created (the cli will create its own folder to contain all the source code).

Open that folder myPoints with your preferred code editor and let's explore the contents.

The default project will create for you a new NodeJS project with:
  • Lerna installed for multi-package management. This means that you can code your application as independent packages and then add them as dependencies locally. This is really helpful to develop mono-repos, a famous pattern for fullstack development.
    • We recommend you learn the basics of Lerna here. It's quite useful. 
    • By default Lerna reads projects from the folder ./packages and by convention Convector CLI projects use the trailing "-cc" to recognize smart contract packages.
  • Inside the person-cc folder, here we have our smart contract's code. Inside the ./packages/person-cc/src folder we will keep the controllers, models, or utils.
  • By default, every project you create with Convector CLI brings the plumbing for unit tests ready.
  • Some other files to notice are the *.person.config.json which defines the configurations to package the smart contract. See this page for more information.
  • The update-paths.js is a file we include to make the project migratable between environments (developer computers) since some paths are required for the packaging process.

Add the Network Participant controllers project

Inside of your project folder root, add a new chaincode for managing the Network Participants (organizations) 

This created a new folder in your ./packages path with a boostrapped package.

Your project should now look like this:

Since your Person controller will have logic from your Participants, for example to check if a request is authorized or not, you need to include the participant-cc package as a dependency for your person-cc package.

Now, your person-cc controller can share logic with your participant-cc

As stated before this example will use the logic from this identity pattern to handle participants (the repo really explanatory, so please refer to it for further details about this pattern).

In our project we will replace the following files with these contents inside of the participant-cc/src folder.

What's the overall behavior of this identity pattern?

  • Network Participant are represented by a model.
  • Each identity has a unique name, that name has one fingerprint (actually, an array but just one can be active at the time) which is the identity for the user - this is generated from a wallet.
  • Identities are perpetual, but their identities (wallets) can vary overtime. 
    • For example if a wallet is compromised, a new one should be generated, to avoid losing references to a Network Participant as the the value of the wallet will be different, in the other models, we reference the ID of the Network Participant instead of directly to the wallet. Therefore, wallets can changed over time without affecting the operation of the network.

See further details here.

Let's code!

Code the models

Head to ./packages/person-cc/src/person.model.ts and change it to reflect our Person model.

By default, classes that extend the ConvectorModel type get some default capabilities and properties, like an id and a type, as well as the feature to talk to the ledger, read more here.

We will need an  Attributes array inside of the Person model. Attributes will be filled by Network Participants.

  • Attributes should keep a reference to the Network Participant to set it. With this we will control access to modify the data.
  • Attributes could expire over time so we will need to set some properties to control scheduled expiration (a point in the future) and an attribute to manually expire it if needed.
  • The content of each Attribute should be flexible enough to allow text fields as well as complex objects.

In the case of the Person, let's keep it simple with just the default "id" field, "name", and the array of attributes.

See those interesting properties on top of the properties of the model?  I.e.: @Default(false)

These are called decorators and are used to define some lightweight validation rules. So instead of doing an if statement to see if a field is empty, you can simple add a decorator right from the model. This will save you a lot of troubles and headaches as in Fabric values and params are always strings that you need to parse and validate manually. Not with Convector 😎. Read more about this here.

Code the controllers

The controller is where the actual logic of your business network resides.

We can think of some rules that the controller should have as well as some functions.

  • Create a Person. Adding people should be (in this example) a task of a single Network Participant - the government. This way we make sure to have some control over the data as well as accountability.
    • It should reject any transaction that is not from the government's wallet.
    • It should not allow to override people registries.
  • Add an attribute. Attributes are added by Network Participants (wallets) and should validate some things:
    • It should verify that the identity that the attribute says "added it" is the actual wallet making the request.
      • I.e.: if I say what a degree attribute is issued by the MIT, I should use MIT's wallet.
    • If the attribute already exists:
      • If it's not the Network Participant that initially issue it, it should reject the transaction.
      • It it's the Network Participant that initially issued it, it should allow it.

The logic is pretty straight forward, right?

Let's turn this logic into code:

Create a Person

A few things to notice from this class:

  • The decorators @Controller('...') (line #13) and @Invokable() (line #15) - this decorators allow you to control how you setup the controller. 
    • @Controller is a namespace for your class. One smart contract may have multiple controllers henceforth to avoid function names collisions (like to controllers with the function name "create") functions will be compile to ${controller}_${function}.
    • @Invokable exposes these functions to be accessible from outside of the smart contract context. This makes them "callable" from your server for example.
    • Read more on Controllers here.
  • Line #9 we validate whether or not a Person already exists with that id.
  • Line #12 we make sure that the Network Participant with the id "gov" exists. This will be the unique identifier for the network participant that plays the Government's role.
  • Line #17 we check that the property this.sender equals to the fingerprint of the Network Participant with the id gov. This is the way we control access to what can be done or not. this.sender is a built-in property that extracts the unique fingerprint from an identity in the blockchain.
  • If everything goes according to plan, call the .save() of that model, and voilá! The data is created for you in the ledger.

Add Attributes

Now, what about the function to add attributes?

Following the logic we previously defined, we need to verify that the stated identity in the attribute actually belong to the wallet making the request. We use the Participant controller for that matter. After all validations are okay, we can check whether or not the attribute already existed and if we can update it with the current wallet that made the request.

The full class will look like this:

This looks fine, but how can we validate this? Do we need a heavy blockchain network to do so? The answer is not! Let's add Unit tests.

Testing

We encourage unit tests always. Even for simple code. It's the way to make sure your code base grows healthy. We use mocha and chai for unit tests but you could technically use whatever you prefer.

If you'd like more details on how to do unit tests and why - read this blog post from our Medium.

For this case, we'd like to validate a few things:

  1. That the government identity is validated correctly when adding people. It would be chaotic that somebody that's not the government invents people!
  2. We would then need to add a government account to emulate it adding people and see if our logic works fine.
  3. We can add a person to see if the process works after we added the government account.
  4. We should validate that attributes can be added.
  5. We should also validate that other organizations can be registered and that they can add attributes.
  6. And last but not least, that no organization can modify attributes added by another organization.

Go to your ./packages/person-cc/tests/person.spec.ts and we will replace that content.

We will need to install a package called chai-as-promised that will allow us to make easier unit tests for asynchronous code (our smart contract).

Let's add chai-as-promised into our code. Your top dependencies should look like this:

We are importing Participant, Attributes, and People into the same unit test. This unit test will create a Mock adapter (fake ledger) that won't require a blockchain network but that by emulating a ledger we can check our logic working by adding and retrieving data.

This unit test can be automated in your Continuous Integration and Continuous Delivery process. 

Since Convector 1.3 the new way to make external calls to the smart contract (from a unit test, a nodejs server, etc) is throught a ClientFactory. This will inject the right layer where the code is running and help us reuse our codebase.

At the end of this exercise we have the full test file so that you can see how to configure it.

1. Check that the government identity is validated correctly when adding people

Notice that test uses eventually, that is brought to the class by chai-as-promised. We simply expect the function .create to fail, as this is the first function we call and there should not be a government Network Participant.

2. Add a government account

We will use the identity that the Mock ledger brings by default (we don't need to do anything extra, just make calls).

It will call register and assign the current identity as the wallet for the Government identity.

3. Successfully create a person

We tried to create a person before, but we failed since just the government account can do and we didn't create it before, but since now we have, let's try it again.

4. Add an attribute as the Government

We will add -with the government wallet- a "birth-year" attribute. The contents will be just a simple string.

5. Add another organization

This organization cannot create new people but can add attributes. Let's validate both.

  • Put close attention to this line (adapter.stub as any).usercert = fakeSecondParticipantCert; it allows us to fake having a different wallet in the unit test. So that we can test different scenarios.
  • Another expected rejection is defined in one of the functions.
  • After these two functions run we should have two Network Participants created: the Government (gov) and the MIT (mit)".

6. Finally, validate that it can add attribute but not modify the ones that it did not create

Notice that in this case, the attribute is more complex than the birth-year we added before, now it can be an object.

The final function will expect a rejection as it will try to change the birth-year, added initially by the government account.

With this, we can automatically test after codes are made to the code that it still does what we expect.

Full unit test file

Run the unit tests 👨🔬

Once you have the whole file pasted in your project. Run this to execute the unit test:

Configure the smart contract

We have created two controllers and validated the business logic correctly. But, it never actually ran in the blockchain. That is an advantage as you can code and test without depending on a heavy multi-containered environment however we're sure you can't wait to run it in an actual blockchain.

We created a multi-controller smart contract but we need to configure it accordingly to compile it as a package for the blockchain. More details on this here.

In a nutshell, we need to modify the file ./org1.person.config.json to also include the Participant controller and merge it into a single smart contract package.

Modify its contents for this (special attention on the controllers array):

This file will be read by a task (cc:package) in your ./package.json when compiling (before installing the smart contract). More on the default tasks here. These tasks will make your life easier by abstracting some commands behind the scenes, like using Hurley for your Development Environment.

Make sure that you met the pre-requisites and if you are using Linux that you read the common issues page. Then let's start a development blockchain network on Docker and install the smart contract:

Congratulations! You have a smart contract running in your computer!

Make some calls

Now that we have our smart contract running successfully, we can proceed to make some calls directly to the blockchain. We will use Hurley for this task to make things easier!

You can always go to http://localhost:5084/_utils/#database/ch1_person and see the data provisioned in the WorldState for you (the world state is a read-only version of the latest version of the data in the ledger - you don't modify the ledger by changing data there).

  • Notice that sometimes we pass a JSON so it needs to be escaped.
  • This data is directly added to the ledger! So we are officially connected to a blockchain.
  • Hurley allows you to connect easily and switch users, as you can see in the use of the param "-u".

Congratulations you have successfully finished this tutorial! Remember to join the community if you haven't already.

The full source code is here.

Next steps

Build your backend

  • You can understand better Hurley or access our code samples base.
  • If you want to deploy to a persistent blockchain network you can own in the cloud or into production for multi-cloud environments with Forma! Sign up today for a free trial.
Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.

Still need help? Contact Us Contact Us