Overview

Skill Level: Intermediate

Node.JS in Typescript; GoLang chaincode

In this tutorial, I will go through my experience on implementing simple Private Data Collections (PDC) feature from Hyperledger Fabric 1.2+, as I did for a client project. Specifically, I will be using Fabric 1.4. I did this work ~July 2019.

Ingredients

This tutorial is a rough guide to implementing PDC functionality, it can be used when starting to build a system, or after already having the groundwork laid out and implementing solely this feature.

 

This tutorial assumes that you understand and know how to do the following:

1.     Understand the fundamentals of blockchain and distributed ledgers, as here

2.     Set up a basic network, can be found here

3.     Integrate with a network via an SDK, see #2 above.

 

This tutorial will be using Node.js SDK with Typescript, and as such will rely on standard coding practices and tools

Step-by-step

  1. Gateway Definition

    There are multiple ways to leverage the gateway feature, for the purposes of my project I could cache the gateway to improve performance, but this is purely optional and depends on your use-case. Below are code snippets for my approach.

    Caching Gateway: on load of server we can create a gateway and save it for later use. Gateway Network Discovery was not used as it cannot be used for PDC in this use-case where each organization had 1 peer; I had to always connect to the same peer to look into the PDC.  

     

    <NodeJS>

    private static async openGatewayConnection(): Promise<void> {

            // Create a new gateway for connecting to our peer node.

            this.Gateway = new Gateway();

            // service discovery is disabled in this case, and we will write this for a local network. At deployment asLocalhost is set to false

            const gatewayDiscovery = { ‘enabled’: false, ‘asLocalhost’: true };

     

            // create a wallet

            const walletPath = path.join(process.cwd(), ‘/wallet’);

            const wallet = FileSystemWallet(walletPath);

     

            const gateWayOptions = {

                wallet,

                identity: config.ADMIN_USERNAME, // this is the username of an admin user on the peer

                discovery: gatewayDiscovery

            };

            // load the connection profile from wherever is ideal for you. In my case it came from a database.

            const cp = this.magicFunctionToGetCP();

            await this.Gateway.connect(cp, gateWayOptions);

     

            // get the network for the channel of interest

            this.Network = await this.Gateway.getNetwork(config.CHANNELS.CHANNEL_NAME);

            // Get the contract from the network.

            this.Contract  = this.Network.getContract(config.CHAINCODES.CHAIN_NAME);

     }

     

     

    If you do not want to cache your gateway service, it can be instantiated at invoke time.

    Note that “FileSystemWallet” and “Gateway” were imported as follows:

     

    <NodeJS>

    const { FileSystemWallet, Gateway } = require(‘fabric-network’);

     

    For further information on implementing the Gateway and Gateway Network Discovery, see the Fabric documentation: Here

     

  2. Create a basic invoke function

    <NodeJS>

    public async invoke(functionName: string, args: string[]): Promise<string> {

            // create the transaction object based on the function name you want to invoke

            // The contact object comes from the class above.

            const transaction = await PvsClass.Contract.createTransaction(functionName);

            // if you want to register the transaction / block event, you can do it here

            const response = await transaction.submit(…args);

            // the returned object needs to be parsed

            return JSON.parse(response.toString(‘utf8’));

    }

  3. Add transient map, as needed

    Sample code:

    <NodeJS>

    if (pvtArgs) {

          // create object from Buffer

          const tmap = {‘asset’ : new Buffer(JSON.stringify(pvtArgs))};

          await transaction.setTransient(tmap);

    }

     

    Note that the above method was written in such a way that the single Invoke method can be called with private data, if applicable. Therefore a single method can handle all invoke calls, with private data added to a transient map if passed. The tmap has a key ‘asset’, this can be anything but must match what is used in the chaincode.

     

    The entire function now looks like this:

     

    <NodeJS>

    public async invoke(functionName: string, args: string[]): Promise<string> {

            // create the transaction object based on the function name you want to invoke

     // The contact object comes from the class above.

            const transaction = await PvsClass.Contract.createTransaction(functionName);

            if (pvtArgs) {

                // create object from Buffer

                const tmap = {‘asset’ : new Buffer(JSON.stringify(pvtArgs))};

                await transaction.setTransient(tmap);

            }

            // if you want to register the transaction / block event, you can do it here

            const response = await transaction.submit(…args);

            // the returned object needs to be parsed

            return JSON.parse(response.toString(‘utf8’));

    }

  4. Write to Private Ledger

    Access transient map in chaincode (GoLang). We will write the object to the Private and the Channel ledgers. A channel ledger is shared to all peers. A private ledger is shared only to the peers in that PDC (as defined by the PDC).

     

    <GoLang>

    func createDoc(stub shim.ChaincodeStubInterface, args string) pb.Response {

        // Get Transient map

        transientMap, err := stub.GetTransient()

     

        // Do some error checks

        if err != nil {

            return shim.Error(“Error getting transient: ” + err.Error())

        }

        // Note that the chaincode has to access the same key: ‘asset’

        if _, ok := transientMap[“asset”]; !ok {

            return shim.Error(“asset must be a key in the transient map”)

        }

        if len(transientMap[“asset”]) == 0 {

            return shim.Error(“asset in transient map must be a non-empty JSON string”)

        }

     

        // Unmarshal the transient object to a struct

        var newObject MyStruct

        err := json.Unmarshal([]byte(transientMap[“asset”], &newObject)

        if err != nil {

            return shim.Error(err.Error())

        }

        // Unmarshal the args object to a struct

        var oldObject MyStruct

        err := json.Unmarshal([]byte(args, &oldObject)

        if err != nil {

            return shim.Error(err.Error())

        }

      

        // Write to Private & Channel Ledger

        // First to the private

        bytesPvt, err := json.Marshal(newObject)

        if err != nil {

            LOGGER.Error(“Error converting to bytes:”, err)

            return nil, err

        }

     

        err = stub.PutPrivateData(newObject.PDCCollectionName, newObject.keyField, bytesPvt)

        if err != nil {

            LOGGER.Error(“Error invoking on chaincode:”, err)

            return nil, err

        }

     

        // Second to the channel

        bytesChn, err := json.Marshal(oldObject)

        if err != nil {

            LOGGER.Error(“Error converting to bytes:”, err)

            return nil, err

        }

     

        err = stub.PutState(oldObject.keyField, bytesChn)

        if err != nil {

            LOGGER.Error(“Error invoking on chaincode:”, err)

            return nil, err

        }

         return bytesChn, nil

    }

     

    Note: this is just an example of how to leverage the Transient Map

  5. Querying

    To query this same PDC is very similar to querying the channel ledger. In fact, the Node.js code is identical with the exception that a PDC name must be provided as an argument. In this use-case, the chaincode always checked if a PDC name was specified and would check the PDC if applicable. This removed the need to specify the PDC name in Node.js.

    Below is a GoLang method that explicitly retrieved data from the PDC:

     

     <GoLang>

    func getPvtDocByTxnId(stub shim.ChaincodeStubInterface, id string, pdcName string) (pb.Response, error) {

        LOGGER.Info(“Entering getPvtDocByTxnId “)

        var jsonResp string

     

        valAsbytes, err := stub.GetPrivateData(pdcName, id)

     

        if err != nil {

            jsonResp = “{\”Error\”:\”Failed to get state for ” + id + “\”}”

            return shim.Error(jsonResp), errors.New(jsonResp)

        } else if valAsbytes == nil {

            jsonResp = “{\”Error\”:\”Txn does not exist: ” + id + “\”}”

            return shim.Error(jsonResp), errors.New(jsonResp)

        }

     

        return shim.Success(valAsbytes), nil

    }

     

     

     

  6. FYI, PDC Definition

    The PDC must be defined during chaincode instantiation and can look like this:

     

     <JSON>

    [

      {

        “name”: “org1”,

        “policy”: {

          “identities”: [

            {

              “role”: {

                “name”: “member”,

                “mspId”: “org1”

              }

            }

          ],

          “policy”: {

            “1-of”: [

              {

                “signed-by”: 0

              }

            ]

          }

        },

        “requiredPeerCount”: 0,

        “maxPeerCount”: 1

      }, {

        “name”: “org2”,

        “policy”: {

          “identities”: [

            {

              “role”: {

                “name”: “member”,

                “mspId”: “org2”

              }

            }

          ],

          “policy”: {

            “1-of”: [

              {

                “signed-by”: 0

              }

            ]

          }

        },

        “requiredPeerCount”: 0,

        “maxPeerCount”: 1

      }

    ]

     

     

    Please refer to Fabric documentation for more in-depth details on PDC definitions: Here

Join The Discussion