149 lines
4.4 KiB
Go
149 lines
4.4 KiB
Go
/*
|
|
Copyright 2021 IBM All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package gateway
|
|
|
|
import (
|
|
"io"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
gp "github.com/hyperledger/fabric-protos-go/gateway"
|
|
ab "github.com/hyperledger/fabric-protos-go/orderer"
|
|
"github.com/hyperledger/fabric-protos-go/peer"
|
|
"github.com/hyperledger/fabric/core/aclmgmt/resources"
|
|
"github.com/hyperledger/fabric/internal/pkg/gateway/event"
|
|
"github.com/hyperledger/fabric/internal/pkg/gateway/ledger"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
// ChaincodeEvents supplies a stream of responses, each containing all the events emitted by the requested chaincode
|
|
// for a specific block. The streamed responses are ordered by ascending block number. Responses are only returned for
|
|
// blocks that contain the requested events, while blocks not containing any of the requested events are skipped. The
|
|
// events within each response message are presented in the same order that the transactions that emitted them appear
|
|
// within the block.
|
|
func (gs *Server) ChaincodeEvents(signedRequest *gp.SignedChaincodeEventsRequest, stream gp.Gateway_ChaincodeEventsServer) error {
|
|
if len(signedRequest.GetRequest()) == 0 {
|
|
return status.Error(codes.InvalidArgument, "a chaincode events request is required")
|
|
}
|
|
|
|
request := &gp.ChaincodeEventsRequest{}
|
|
if err := proto.Unmarshal(signedRequest.GetRequest(), request); err != nil {
|
|
return status.Errorf(codes.InvalidArgument, "invalid chaincode events request: %v", err)
|
|
}
|
|
|
|
signedData := &protoutil.SignedData{
|
|
Data: signedRequest.GetRequest(),
|
|
Identity: request.GetIdentity(),
|
|
Signature: signedRequest.GetSignature(),
|
|
}
|
|
if err := gs.policy.CheckACL(resources.Gateway_ChaincodeEvents, request.GetChannelId(), signedData); err != nil {
|
|
return status.Error(codes.PermissionDenied, err.Error())
|
|
}
|
|
|
|
ledger, err := gs.ledgerProvider.Ledger(request.GetChannelId())
|
|
if err != nil {
|
|
return status.Error(codes.NotFound, err.Error())
|
|
}
|
|
|
|
startBlock, err := chaincodeEventsStartBlock(ledger, request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
isMatch := chaincodeEventMatcher(request)
|
|
|
|
ledgerIter, err := ledger.GetBlocksIterator(startBlock)
|
|
if err != nil {
|
|
return status.Error(codes.Aborted, err.Error())
|
|
}
|
|
|
|
eventsIter := event.NewChaincodeEventsIterator(ledgerIter)
|
|
defer eventsIter.Close()
|
|
|
|
for {
|
|
response, err := eventsIter.Next()
|
|
if err != nil {
|
|
return status.Error(codes.Aborted, err.Error())
|
|
}
|
|
|
|
var matchingEvents []*peer.ChaincodeEvent
|
|
for _, event := range response.GetEvents() {
|
|
if isMatch(event) {
|
|
matchingEvents = append(matchingEvents, event)
|
|
}
|
|
}
|
|
|
|
if len(matchingEvents) == 0 {
|
|
continue
|
|
}
|
|
|
|
response.Events = matchingEvents
|
|
|
|
if err := stream.Send(response); err != nil {
|
|
if err == io.EOF {
|
|
// Stream closed by the client
|
|
return status.Error(codes.Canceled, err.Error())
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
func chaincodeEventMatcher(request *gp.ChaincodeEventsRequest) func(event *peer.ChaincodeEvent) bool {
|
|
chaincodeID := request.GetChaincodeId()
|
|
previousTransactionID := request.GetAfterTransactionId()
|
|
|
|
if len(previousTransactionID) == 0 {
|
|
return func(event *peer.ChaincodeEvent) bool {
|
|
return event.GetChaincodeId() == chaincodeID
|
|
}
|
|
}
|
|
|
|
passedPreviousTransaction := false
|
|
|
|
return func(event *peer.ChaincodeEvent) bool {
|
|
if !passedPreviousTransaction {
|
|
if event.TxId == previousTransactionID {
|
|
passedPreviousTransaction = true
|
|
}
|
|
return false
|
|
}
|
|
|
|
return event.GetChaincodeId() == chaincodeID
|
|
}
|
|
}
|
|
|
|
func chaincodeEventsStartBlock(ledger ledger.Ledger, request *gp.ChaincodeEventsRequest) (uint64, error) {
|
|
afterTransactionID := request.GetAfterTransactionId()
|
|
if len(afterTransactionID) > 0 {
|
|
if block, err := ledger.GetBlockByTxID(afterTransactionID); err == nil {
|
|
return block.GetHeader().GetNumber(), nil
|
|
}
|
|
}
|
|
|
|
return startBlockFromLedgerPosition(ledger, request.GetStartPosition())
|
|
}
|
|
|
|
func startBlockFromLedgerPosition(ledger ledger.Ledger, position *ab.SeekPosition) (uint64, error) {
|
|
switch seek := position.GetType().(type) {
|
|
case nil:
|
|
case *ab.SeekPosition_NextCommit:
|
|
case *ab.SeekPosition_Specified:
|
|
return seek.Specified.GetNumber(), nil
|
|
default:
|
|
return 0, status.Errorf(codes.InvalidArgument, "invalid start position type: %T", seek)
|
|
}
|
|
|
|
ledgerInfo, err := ledger.GetBlockchainInfo()
|
|
if err != nil {
|
|
return 0, status.Error(codes.Aborted, err.Error())
|
|
}
|
|
|
|
return ledgerInfo.GetHeight(), nil
|
|
}
|