Completing the loop – subscribing to blockchain events

As we have seen in previous chapters, commitments to the shared ledger on a permissioned blockchain require a consensus among the network peers. Hyperledger Fabric in its v1 incarnation has an even more unique process to commit to the ledger: the transaction execution, ordering, and commitment processes are all decoupled from each other and framed as stages in a pipeline where endorsers, orderers, and committers carry out their tasks independent of each other. Therefore, any operation that results in a commitment of a block to the ledger is asynchronous in the Fabric scheme of things. Three of the operations we have implemented in our middleware fall into that category:

  • Channel join
  • Chaincode instantiation
  • Chaincode invoke

In our description of these operations, we stopped at the point where a request is successfully sent to the orderer. But to complete the operation loop, any application that uses our middleware needs to know the final result of the request to drive the application logic forward. Fortunately, Fabric provides a publish/subscribe mechanism for the communication of results of asynchronous operations. This includes events for the commitment of a block, the completion of a transaction (successfully or otherwise), as well as custom events that can be defined and emitted by a chaincode. Here, we will examine block and transaction events, which cover the operations we are interested in.

Fabric offers a mechanism for event subscription in the SDK through an EventHub class, with the relevant subscription methods being registerBlockEvent, registerTxEvent, and registerChaincodeEvent, respectively, to which callback functions can be passed for actions to perform at the middleware layer (or higher) whenever an event is available.

Let's see how we can catch the event of a successful join in our middleware code. Going back to the joinChannel function in join-channel.js, the following code instantiates an EventHub object for a given peer, whose configuration is loaded from config.json. For example, to subscribe to events from the exporter organization's sole peer, the URL our fabric-client instance will listen to (under the covers) is grpcs://localhost:7053:

let eh = client.newEventHub();
eh.setPeerAddr(
ORGS[org][key].events,
{
pem: Buffer.from(data).toString(),
'ssl-target-name-override': ORGS[org][key]['server-hostname']
}
);
eh.connect();
eventhubs.push(eh);

The listener, or callback, for each block event is defined as follows:

var eventPromises = [];
eventhubs.forEach((eh) => {
let txPromise = new Promise((resolve, reject) => {
let handle = setTimeout(reject, 40000);
eh.registerBlockEvent((block) => {
clearTimeout(handle);
if(block.data.data.length === 1) {
var channel_header = block.data.data[0].payload.header.channel_header;
if (channel_header.channel_id === channel_name) {
console.log('The new channel has been successfully joined on peer '+ eh.getPeerAddr());
resolve();
}
else {
console.log('The new channel has not been succesfully joined'),
reject();
}
}
});
});
eventPromises.push(txPromise);
});

Whenever a block event is received, the code matches the expected channel name (tradechannel in our scenario) with the one extracted from the block. (The block payloads are constructed using standard schemas available in the Fabric source code, in the protos folder. Understanding and playing with these formats is left as an exercise to the reader.) We will set a timeout in the code (40 seconds here) to prevent our event subscription logic from waiting indefinitely and holding up the application. Finally, the outcome of a channel join is made contingent, not just on the success of a channel.joinChannel call, but also on the availability of block events, as follows:

let sendPromise = channel.joinChannel(request, 40000);
return Promise.all([sendPromise].concat(eventPromises));

For instantiation and invocation, we register callbacks not for blocks but for specific transactions, which are identified by IDs set during the transaction proposal creation. Code for the subscription can be found in the instantiateChaincode and invokeChaincode functions, in instantiate-chaincode.js and invoke-chaincode.js respectively. A code snippet from the latter illustrates the basic working of transaction event handling:

eh.registerTxEvent(deployId.toString(),
(tx, code) => {
eh.unregisterTxEvent(deployId);
if (code !== 'VALID') {
console.log('The transaction was invalid, code = ' + code);
reject();
} else {
console.log('The transaction has been committed on peer '+ eh.getPeerAddr());
resolve();
}
}
);

The parameters passed to the callback include a handle to the transaction and a status code, which can be checked to see whether the chaincode invocation result was successfully committed to the ledger. Once the event has been received, the event listener is unregistered to free up system resources (our code may also listen to block events in lieu of specific transaction events, but it will then have to parse the block payload and find and interpret information about the transaction that was submitted).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.186.83