Thinktecture Blogs

Developing A Simple Chaincode With .NET Core

/ 8 min read / Hyperledger Fabric

This blog post describes the first steps for developing a simple chaincode (a.k.a. Smart Contract) for Hyperledger Fabric with .NET Core. The goal is to create a small chaincode responsible for a very basic asset holding. It shall be created with two account names and an integer value describing the amount of money within an account. It then shall be possible to query the account for value as well as transfer money from one account to another account.

Please note, that .NET Core is not yet officially supported by Hyperledger Fabric. The current .NET Core package is in development, the API will evolve over time.

Prerequisites

Please make sure you read the blog post about the prerequisites before reading on here. It'll explain all the stuff you need to get it going. If you have everything ready, we can start developing our first chaincode!

Creating the project

The first thing we need to do is create a new project. If you use JetBrains Rider, you can simply go to File -> New -> Console Application. If you prefer a CLI approach use dotnet new console to create a new project. After that, open it in your preferred IDE.

Now, open the project's *.csproj file, find the first <PropertyGroup> and insert <LangVersion>7.3</LangVersion>. If <LangVersion> already exists, replace it. This switches to the latest version of C#. It is not necessary to switch and you can stick to the default of 7.0, but it allows us later to use an asynchronous main function.

Next, open a terminal within the project to install the .NET Chaincode Shim via dotnet add package Thinktecture.HyperledgerFabric.Chaincode --version 1.0.0-prerelease.74. You may take a look at the NuGet Gallery to get the latest version.

Development approach

The package Thinktecture.HyperledgerFabric.Chaincode offers two different approach to develop a chaincode.

  • Low level: Implementing IChaincode to use a low-level approach, giving full access to anything what's possible with the package.
  • High level: Extending ContractBase to use the new contract-based (aka high-level) approach which is a bit more opinionated but eases the workflow a bit.

Within this blog post we're getting the hands dirty by implementing IChaincode and use the low level API. If you are more interested in using the high level API, take a look at this blog post.

Developing the chaincode

Let's start by creating a new class called AssetHolding which implements the IChaincode interface:

public class AssetHolding : IChaincode
{
    public async Task<Response> Init(IChaincodeStub stub)
    {
        throw new NotImplementedException();
    }
    
    public Task<Response> Invoke(IChaincodeStub stub)
    {
        throw new NotImplementedException();
    }
}

Both methods needs to be implemented in order to fulfil the IChaincode interface. The Init() method will be called during chaincode initialization and upgrade. You can use this method to initialise your asset store, e.g. adding some default values.

The Invoke() method is called throughout the lifetime of the chaincode. It will handle all business transaction logic and may affect the asset state.

Both methods have a parameter of type IChaincodeStub. It encapsulates the API between our chaincode implementation and the Fabric peer. For examples, it allows CRUD operations on the asset store.

Implementing Init

As mentioned in the introduction, the chaincode shall be initialised with two accounts names and their initial values. Basically, we send a array like ["accountA", "100", "accountB", "50"] to the chaincode. In order to get the parameters during the initialization, we can use stub.GetFunctionAndParameters(). The result is an object containing Parameters which is basically a List<string> containing all parameters.

An easy way to check and throw when not enough parameters are supplied is to use Parameters.AssertCount(4). If the parameter count is unequal 4, it will throw, thus aborting the chaincode initialization. So far, our Init() looks like this:

public async Task<Response> Init(IChaincodeStub stub)
{
    var functionAndParameters = stub.GetFunctionAndParameters();

    var args = functionAndParameters.Parameters;
    
    args.AssertCount(4);
}

Next step is to convert the two of the parameters into an integer. We can either do it ourselves using int.TryParse() or use args.TryGet<int>() which basically does the same. Let's extends our Init() to the following code:

public async Task<Response> Init(IChaincodeStub stub)
{
    var functionAndParameters = stub.GetFunctionAndParameters();

    var args = functionAndParameters.Parameters;
    
    args.AssertCount(4);
    
    if (!args.TryGet<int>(1, out var aValue) ||
        !args.TryGet<int>(3, out var bValue)) 
    {
        return Shim.Error("Expecting integer value for asset holding");
    }
}

We try to get the second and forth parameter as an integer. If one of the conversions is not successful, we return a Shim.Error() which will notify the caller, that the initialization failed.

Side note: During the initialization phase you'll send a JSON to the chaincode containing the parameters. Due to JSON the parameters could be an integer already. But for the transfer, everything is converted to a ByteString, so the library has access to a List<ByteString> only which then is converted to a List<string>. So we've to convert it back if needed.

If the conversion was successful, we can put the values in the asset store by using stub.PutState() like this:

public async Task<Response> Init(IChaincodeStub stub)
{
    var functionAndParameters = stub.GetFunctionAndParameters();

    var args = functionAndParameters.Parameters;
    
    args.AssertCount(4);
    
    if (!args.TryGet<int>(1, out var aValue) ||
        !args.TryGet<int>(3, out var bValue)) 
    {
        return Shim.Error("Expecting integer value for asset holding");
    }
    
    if (await stub.PutState(args.Get<string>(0), aValue) 
        && await stub.PutState(args.Get<string>(2), bValue)) 
    {
        return Shim.Success();
    }

    return Shim.Error("Error during Chaincode init!");
}

We're using PutState() to update the asset store with the initial account values. If everything is successful, we're using Shim.Success() to return a success response to the caller. If not, we send back an error.

Implementing Invoke

Let's take a closer look how we're going to implement the Invoke() method. The Invoke() method is called throughout the lifetime of the chaincode to handle all your business transaction logic. As we can see, the interface of the method is kind of general. There are no additional parameters except the IChaincodeStub which is the same as we had it in the Init() method to encapsulate the API between our chaincode and the Fabric peer.

Basically, we do the same as we did with Init(). We can use IChaincodeStub.GetFunctionAndParameters() to get the actual function called and their parameters, e.g. like that:

public Task<Response> Invoke(IChaincodeStub stub)
{
    var functionAndParameters = stub.GetFunctionAndParamaters();
    
    if (functionAndParameters.Function == 'myfn1') 
    {
      return MyFn1(stub, functionAndParameters.Parameters);
    }
    
    if (functionAndParameters.Function == 'myfn2') 
    {
      return MyFn2(stub, functionAndParameters.Parameters);
    }
    
    // Rinse and repeat for every function
}

Depending on your use case, you could have a lot of if statements (or if you prefer switch-case). That's why the library comes with a handy class called ChaincodeInvocationMap. It allows to register functions with a signature of Task<ByteString> FunctionName(IChaincodeStub stub, Parameters parameters) and assign a custom name. This allows us to write the code from above like this:

private readonly ChaincodeInvocationMap _invocationMap;

public AssetHolding()
{
    _invocationMap = new ChaincodeInvocationMap
    {
        {"Transfer", InternalTransfer},
        {"Query", InternalQuery}
    };
}

public Task<Response> Invoke(IChaincodeStub stub)
{
    return _invocationMap.Invoke(stub);
}

Please note, we haven't implemented the methods InternalTransfer() and InternalQuery(), yet. Using the ChaincodeInvocationMap we have a clean solution to handle a bunch of methods used for our chaincode.

The next thing we want to do is to implement InternalTransfer().

private async Task<ByteString> InternalTransfer(IChaincodeStub stub, Parameters args)
{
    args.AssertCount(3);

    var accountA = args.Get<string>(0);
    var accountB = args.Get<string>(1);

    if (string.IsNullOrEmpty(accountA) || string.IsNullOrEmpty(accountB))
        throw new Exception("Asset holding must not be empty");

    var aValue = await stub.TryGetState<int>(accountA);
    if (!aValue.HasValue) throw new Exception("Failed to get state of asset holder A");

    var bValue = await stub.TryGetState<int>(accountB);
    if (!bValue.HasValue) throw new Exception("Failed to get state of asset holder B");

    if (!args.TryGet<int>(2, out var amount))
        throw new Exception("Expecting integer value for amount to be transferred");

    aValue -= amount;
    bValue += amount;

    await stub.PutState(accountA, aValue);
    await stub.PutState(accountB, bValue);

    return ByteString.Empty;
}

Remember, our chaincode shall transfer money from one account to another account. For that, we need three arguments:

  • accountA: the account to take the money from
  • accountB: The account to put the money
  • amount: the amount of money to be transferred

To ensure we have three arguments, we can use AssertCount(3) again, like we did in Init().  After that, before we can transfer the money, we need to get the current accounts state from asset store. To do so, we can use IChaincodeStub.GetState() or IChaincodeStub.TryGetState() to retrieve it. The difference between the methods is that TryGetState() will never throw an exception but simply return false upon an error (which could be that converting to the target type is not possible or some communication error with the asset store in general).

When the state has been retrieved we can subtract the amount from accountA and add it to accountB. It now depends on the use case, if we want to check further, for example if we don't want to allow a negative account balance or something.

After we've transferred the money, we need to update the asset store with the new value by using stub.PutState().  At last, we return an empty ByteString to indicate that nothing went wrong and we have nothing to return either.

So far so good. To conclude the implementation of our chaincode, we need to implement InternalQuery() as well. The purpose of this method will be, that it takes one parameter representing the account name. We then get the state of the account from the asset store and return the bytes to the caller.

Pretty sure you'll be able to implement this your own now. For reference, you can take a look at this sample for the implementation - which is just one possibility of course.

private async Task<ByteString> InternalQuery(IChaincodeStub stub, Parameters args)
{
    args.AssertCount(1);

    var a = args[0];

    var aValueBytes = await stub.GetState(a);

    if (aValueBytes == null) throw new Exception($"Failed to get state of asset holder {a}");

    return aValueBytes;
}

Allright! We've finished the implementation of our chaincode. Last but not least we need to implement the Main() method within Program.cs to get everything up and running:

static async Task Main(string[] args)
{
    using (var provider = ChaincodeProviderConfiguration.Configure<AssetHolding>(args))
    {
        var shim = provider.GetRequiredService<Shim>();
        await shim.Start();
    }
}
Side note: If you didn't switch the C# language level to at least 7.1, you can't use an async Main() method. Just switch it back to void Main() and instead of await shim.Start() you have to use Shim.Start().Wait();

Within out main method we are configuring the ChaincodeProviderConfiguration which helps us to bootstrap all the components we need to run our chaincode. It incorporates Microsoft.Extensions.DependencyInjection to create a DI container. If you want to put in our own classes into the container, you can use an overload of Configure() which has a setup function to setup your stuff. Pay attention that Configure() is a generic method whereas the generic parameter is own chaincode. You do not need to pass an instance of the chaincode, everything will be created through DI.

After configuring, we get the class Shim from the container which is like the startup class for our chaincode and handles the most basic things to get it up and running. The Shim provides a method called Start() returning a Task to await. This method will never finish unless it crashes horribly.

Finally, we've implemented our first chaincode for Hyperledger Fabric using the .NET Chaincode Shim. In order to get it working within a dev network, please take a look at this blog post.

Manuel Rauber

Manuel Rauber

Manuel loves creating cross-platform applications with HTML5 and JavaScript in the frontend, and blockchain technologies like Hyperledger Fabric in the backend.