Step-by-step
-
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
-
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’));
}
-
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’));
}
-
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
-
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
}
-
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