Preface
Recently, while working on a cross-chain adapter, I needed to use the Go SDK to connect to a Fabric network on a local chain and listen for events. Therefore, I’ve decided to summarize the events supported by Fabric and the listening methods provided by the SDK.
Fabric Events
Events are a way for clients to interact with the Fabric network. As shown in the image above, after executing a transaction in the Fabric network, because it is done asynchronously, the client cannot obtain the submitted transaction status (whether it was accepted). Therefore, Fabric’s Peer nodes provide an event mechanism, allowing clients to listen to block events through the gRPC interface. Since Fabric v1.1, event registration occurs at the channel level rather than the Peer node, allowing for more fine-grained control.
Event Types
Events are primarily triggered by the Ledger and containers holding chaincode contracts. Fabric supports four types of events:
- BlockEvent: Used to monitor new blocks added to Fabric
- ChaincodeEvent: Used to monitor events published in chaincode, i.e., user-defined events
- TxStatusEvent: Used to monitor transaction completion on nodes
- FilteredBlockEvent: Used to monitor summarized block information
In the Fabric Go SDK, these events are handled through the following event listeners:
func (c *Client) RegisterBlockEvent(filter ...fab.BlockFilter) (fab.Registration, <-chan *fab.BlockEvent, error)
func (c *Client) RegisterChaincodeEvent(ccID, eventFilter string) (fab.Registration, <-chan *fab.CCEvent, error)
func (c *Client) RegisterFilteredBlockEvent() (fab.Registration, <-chan *fab.FilteredBlockEvent, error)
func (c *Client) RegisterTxStatusEvent(txID string) (fab.Registration, <-chan *fab.TxStatusEvent, error)
After listening is complete, func (c *Client) Unregister(reg fab.Registration)
should be used to unregister and remove the event channel.
gRPC Communication
The SDK communicates with Peer nodes via gRPC. The source code can be found at fabric-protos/peer/events.proto
It defines the following messages:
- FilteredBlock, used for FilteredBlockEvent
- FilteredTransaction and FilteredTransaction, used for FilteredTransactionEvent
- FilteredChaincodeAction, used for ChaincodeEvent
- BlockAndPrivateData, used for private data
The Response is as follows:
// DeliverResponse
message DeliverResponse {
oneof Type {
common.Status status = 1;
common.Block block = 2;
FilteredBlock filtered_block = 3;
BlockAndPrivateData block_and_private_data = 4;
}
}
And three gRPC communication interfaces:
service Deliver {
// Deliver first requires an Envelope of type ab.DELIVER_SEEK_INFO with
// Payload data as a marshaled orderer.SeekInfo message,
// then a stream of block replies is received
rpc Deliver (stream common.Envelope) returns (stream DeliverResponse) {
}
// DeliverFiltered first requires an Envelope of type ab.DELIVER_SEEK_INFO with
// Payload data as a marshaled orderer.SeekInfo message,
// then a stream of **filtered** block replies is received
rpc DeliverFiltered (stream common.Envelope) returns (stream DeliverResponse) {
}
// DeliverWithPrivateData first requires an Envelope of type ab.DELIVER_SEEK_INFO with
// Payload data as a marshaled orderer.SeekInfo message,
// then a stream of block and private data replies is received
rpc DeliverWithPrivateData (stream common.Envelope) returns (stream DeliverResponse) {
}
}
The entire process is shown in the image above. In the Go SDK, a Dispatcher is implemented to convert event registration requests from the application into event subscription requests and send them to the Peer node via DeliverClient. The DeliverServer in the Peer node receives the subscription request, calls deliverBlocks to enter a loop, reads blocks from the Ledger, generates events, and finally sends them to the client. The Dispatcher in the client then converts these into event responses subscribed by the application.
Event Implementation Process
The event implementation process requires two steps:
- Call the
SetEvent
method in the chaincode - Implement an event listener in the client using the Go SDK
SetEvent Method
Method definition:
func (s *ChaincodeStub) SetEvent(name string, payload []byte) error
Usage example:
func (s *SmartContract) Invoke(stub shim.ChaincodeStubInterface) sc.Response {
err = stub.PutState(key, value)
if err != nil {
return shim.Error(fmt.Sprintf("unable put state (%s), error: %v", key, err))
}
// Payload needs to be converted to byte format
eventPayload := "Event Information"
payloadAsBytes := []byte(eventPayload)
// SetEvent method is typically placed after operations interacting with the ledger, such as PutState, DelState
err = stub.SetEvent("<event name>", payloadAsBytes)
if (eventErr != nil) {
return shim.Error(fmt.Sprintf("Failed to trigger event"))
}
return shim.Success(nil)
}
Client Event Listener
// Implement a chaincode event listener
// Pass in the corresponding parameters. The eventId here must match the <event name> in the chaincode to achieve listening
reg, eventChannel, err := eventClient.RegisterChaincodeEvent(chaincodeID, eventID)
if err != nil {
log.Fatalf("Failed to register block event: %v\n", err)
return
}
// Unregister and remove the event channel
defer eventClient.Unregister(reg)
Conclusion
The above is a basic introduction to monitoring events on the Fabric network using the Go SDK. I am currently reviewing the Fabric Go SDK source code and will supplement this with some interpretations in the future.