280 lines
7.0 KiB
Go
280 lines
7.0 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package discovery
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/hyperledger/fabric-protos-go/peer"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/hyperledger/fabric-protos-go/discovery"
|
|
"github.com/hyperledger/fabric-protos-go/gossip"
|
|
"github.com/hyperledger/fabric-protos-go/msp"
|
|
"github.com/hyperledger/fabric/cmd/common"
|
|
discoveryclient "github.com/hyperledger/fabric/discovery/client"
|
|
"github.com/hyperledger/fabric/gossip/protoext"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// NewEndorsersCmd creates a new EndorsersCmd
|
|
func NewEndorsersCmd(stub Stub, parser ResponseParser) *EndorsersCmd {
|
|
return &EndorsersCmd{
|
|
stub: stub,
|
|
parser: parser,
|
|
}
|
|
}
|
|
|
|
// EndorsersCmd executes a command that retrieves endorsers for a chaincode invocation chain
|
|
type EndorsersCmd struct {
|
|
stub Stub
|
|
server *string
|
|
channel *string
|
|
chaincodes *[]string
|
|
collections *map[string]string
|
|
noPrivReads *[]string
|
|
parser ResponseParser
|
|
}
|
|
|
|
// SetCollections sets the collections to be the given collections
|
|
func (pc *EndorsersCmd) SetCollections(collections *map[string]string) {
|
|
pc.collections = collections
|
|
}
|
|
|
|
// SetNoPrivateReads sets the collections that are expected not to have private reads
|
|
func (pc *EndorsersCmd) SetNoPrivateReads(noPrivReads *[]string) {
|
|
pc.noPrivReads = noPrivReads
|
|
}
|
|
|
|
// SetChaincodes sets the chaincodes to be the given chaincodes
|
|
func (pc *EndorsersCmd) SetChaincodes(chaincodes *[]string) {
|
|
pc.chaincodes = chaincodes
|
|
}
|
|
|
|
// SetServer sets the server
|
|
func (pc *EndorsersCmd) SetServer(server *string) {
|
|
pc.server = server
|
|
}
|
|
|
|
// SetChannel sets the channel
|
|
func (pc *EndorsersCmd) SetChannel(channel *string) {
|
|
pc.channel = channel
|
|
}
|
|
|
|
// Execute executes the command
|
|
func (pc *EndorsersCmd) Execute(conf common.Config) error {
|
|
if pc.channel == nil || *pc.channel == "" {
|
|
return errors.New("no channel specified")
|
|
}
|
|
|
|
if pc.server == nil || *pc.server == "" {
|
|
return errors.New("no server specified")
|
|
}
|
|
|
|
server := *pc.server
|
|
channel := *pc.channel
|
|
|
|
ccAndCol := &chaincodesAndCollections{
|
|
Chaincodes: pc.chaincodes,
|
|
Collections: pc.collections,
|
|
NoPrivReads: pc.noPrivReads,
|
|
}
|
|
cc2collections, err := ccAndCol.parseInput()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var ccCalls []*peer.ChaincodeCall
|
|
|
|
for _, cc := range *ccAndCol.Chaincodes {
|
|
ccCalls = append(ccCalls, &peer.ChaincodeCall{
|
|
Name: cc,
|
|
CollectionNames: cc2collections[cc],
|
|
NoPrivateReads: ccAndCol.noPrivateReads(cc),
|
|
})
|
|
}
|
|
|
|
req, err := discoveryclient.NewRequest().OfChannel(channel).AddEndorsersQuery(&peer.ChaincodeInterest{Chaincodes: ccCalls})
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed creating request")
|
|
}
|
|
|
|
res, err := pc.stub.Send(server, conf, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return pc.parser.ParseResponse(channel, res)
|
|
}
|
|
|
|
// EndorserResponseParser parses endorsement responses from the peer
|
|
type EndorserResponseParser struct {
|
|
io.Writer
|
|
}
|
|
|
|
// ParseResponse parses the given response for the given channel
|
|
func (parser *EndorserResponseParser) ParseResponse(channel string, res ServiceResponse) error {
|
|
rawResponse := res.Raw()
|
|
if len(rawResponse.Results) == 0 {
|
|
return errors.New("empty results")
|
|
}
|
|
|
|
if e := rawResponse.Results[0].GetError(); e != nil {
|
|
return errors.Errorf("server returned: %s", e.Content)
|
|
}
|
|
|
|
ccQueryRes := rawResponse.Results[0].GetCcQueryRes()
|
|
if ccQueryRes == nil {
|
|
return errors.Errorf("server returned response of unexpected type: %v", reflect.TypeOf(rawResponse.Results[0]))
|
|
}
|
|
|
|
jsonBytes, _ := json.MarshalIndent(parseEndorsementDescriptors(ccQueryRes.Content), "", "\t")
|
|
fmt.Fprintln(parser.Writer, string(jsonBytes))
|
|
return nil
|
|
}
|
|
|
|
type chaincodesAndCollections struct {
|
|
Chaincodes *[]string
|
|
Collections *map[string]string
|
|
NoPrivReads *[]string
|
|
}
|
|
|
|
func (ec *chaincodesAndCollections) noPrivateReads(chaincodeName string) bool {
|
|
for _, cc := range *ec.NoPrivReads {
|
|
if chaincodeName == cc {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (ec *chaincodesAndCollections) existsInChaincodes(chaincodeName string) bool {
|
|
for _, cc := range *ec.Chaincodes {
|
|
if chaincodeName == cc {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (ec *chaincodesAndCollections) parseInput() (map[string][]string, error) {
|
|
var emptyChaincodes []string
|
|
if ec.Chaincodes == nil {
|
|
ec.Chaincodes = &emptyChaincodes
|
|
}
|
|
|
|
if ec.NoPrivReads == nil {
|
|
ec.NoPrivReads = &emptyChaincodes
|
|
}
|
|
|
|
var emptyCollections map[string]string
|
|
if ec.Collections == nil {
|
|
ec.Collections = &emptyCollections
|
|
}
|
|
|
|
res := make(map[string][]string)
|
|
|
|
for _, cc := range *ec.Chaincodes {
|
|
res[cc] = nil
|
|
}
|
|
|
|
for _, cc := range *ec.NoPrivReads {
|
|
if !ec.existsInChaincodes(cc) {
|
|
return nil, errors.Errorf("chaincode %s is specified as not containing private data reads but should be explicitly defined via a chaincode flag", cc)
|
|
}
|
|
}
|
|
|
|
for cc, collections := range *ec.Collections {
|
|
if !ec.existsInChaincodes(cc) {
|
|
return nil, errors.Errorf("a collection specified chaincode %s but it wasn't specified with a chaincode flag", cc)
|
|
}
|
|
res[cc] = strings.Split(collections, ",")
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func parseEndorsementDescriptors(descriptors []*discovery.EndorsementDescriptor) []endorsermentDescriptor {
|
|
var res []endorsermentDescriptor
|
|
for _, desc := range descriptors {
|
|
endorsersByGroups := make(map[string][]endorser)
|
|
for grp, endorsers := range desc.EndorsersByGroups {
|
|
for _, p := range endorsers.Peers {
|
|
endorsersByGroups[grp] = append(endorsersByGroups[grp], endorserFromRaw(p))
|
|
}
|
|
}
|
|
res = append(res, endorsermentDescriptor{
|
|
Chaincode: desc.Chaincode,
|
|
Layouts: desc.Layouts,
|
|
EndorsersByGroups: endorsersByGroups,
|
|
})
|
|
}
|
|
return res
|
|
}
|
|
|
|
type endorser struct {
|
|
MSPID string
|
|
LedgerHeight uint64
|
|
Endpoint string
|
|
Identity string
|
|
}
|
|
|
|
type endorsermentDescriptor struct {
|
|
Chaincode string
|
|
EndorsersByGroups map[string][]endorser
|
|
Layouts []*discovery.Layout
|
|
}
|
|
|
|
func endorserFromRaw(p *discovery.Peer) endorser {
|
|
sId := &msp.SerializedIdentity{}
|
|
proto.Unmarshal(p.Identity, sId)
|
|
return endorser{
|
|
MSPID: sId.Mspid,
|
|
Endpoint: endpointFromEnvelope(p.MembershipInfo),
|
|
LedgerHeight: ledgerHeightFromEnvelope(p.StateInfo),
|
|
Identity: string(sId.IdBytes),
|
|
}
|
|
}
|
|
|
|
func endpointFromEnvelope(env *gossip.Envelope) string {
|
|
if env == nil {
|
|
return ""
|
|
}
|
|
aliveMsg, _ := protoext.EnvelopeToGossipMessage(env)
|
|
if aliveMsg == nil {
|
|
return ""
|
|
}
|
|
if !protoext.IsAliveMsg(aliveMsg.GossipMessage) {
|
|
return ""
|
|
}
|
|
if aliveMsg.GetAliveMsg().Membership == nil {
|
|
return ""
|
|
}
|
|
return aliveMsg.GetAliveMsg().Membership.Endpoint
|
|
}
|
|
|
|
func ledgerHeightFromEnvelope(env *gossip.Envelope) uint64 {
|
|
if env == nil {
|
|
return 0
|
|
}
|
|
stateInfoMsg, _ := protoext.EnvelopeToGossipMessage(env)
|
|
if stateInfoMsg == nil {
|
|
return 0
|
|
}
|
|
if !protoext.IsStateInfoMsg(stateInfoMsg.GossipMessage) {
|
|
return 0
|
|
}
|
|
if stateInfoMsg.GetStateInfo().Properties == nil {
|
|
return 0
|
|
}
|
|
return stateInfoMsg.GetStateInfo().Properties.LedgerHeight
|
|
}
|