go_study/fabric-main/internal/pkg/gateway/chaincodeevents.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
}