go_study/fabric-main/internal/pkg/gateway/registry.go

515 lines
18 KiB
Go

/*
Copyright 2021 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package gateway
import (
"fmt"
"math/rand"
"sort"
"strings"
"sync"
"github.com/golang/protobuf/proto"
dp "github.com/hyperledger/fabric-protos-go/discovery"
"github.com/hyperledger/fabric-protos-go/gossip"
"github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/common/channelconfig"
"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/core/scc"
gossipapi "github.com/hyperledger/fabric/gossip/api"
gossipcommon "github.com/hyperledger/fabric/gossip/common"
gossipdiscovery "github.com/hyperledger/fabric/gossip/discovery"
"github.com/hyperledger/fabric/internal/pkg/gateway/ledger"
"github.com/pkg/errors"
)
type Discovery interface {
Config(channel string) (*dp.ConfigResult, error)
IdentityInfo() gossipapi.PeerIdentitySet
PeersForEndorsement(channel gossipcommon.ChannelID, interest *peer.ChaincodeInterest) (*dp.EndorsementDescriptor, error)
PeersOfChannel(gossipcommon.ChannelID) gossipdiscovery.Members
}
type registry struct {
localEndorser *endorser
discovery Discovery
logger *flogging.FabricLogger
endpointFactory *endpointFactory
remoteEndorsers map[string]*endorser
broadcastClients sync.Map // orderer address (string) -> client connection (orderer)
channelInitialized map[string]bool
configLock sync.RWMutex
channelOrderers sync.Map // channel (string) -> orderer addresses (endpointConfig)
systemChaincodes scc.BuiltinSCCs
localProvider ledger.Provider
}
type endorserState struct {
peer *dp.Peer
endorser *endorser
height uint64
}
// Returns an endorsementPlan for the given chaincode on a channel.
func (reg *registry) endorsementPlan(channel string, interest *peer.ChaincodeInterest, preferredEndorser *endorser) (*plan, error) {
descriptor, err := reg.discovery.PeersForEndorsement(gossipcommon.ChannelID(channel), interest)
if err != nil {
logger.Errorw("PeersForEndorsement failed.", "error", err, "channel", channel, "ChaincodeInterest", proto.MarshalTextString(interest))
return nil, errors.Wrap(err, "no combination of peers can be derived which satisfy the endorsement policy")
}
// There are two parts to an endorsement plan:
// 1) List of endorsers in each group
// 2) Layouts consisting of a number of endorsers from each group
// Firstly, process the endorsers by group
// Create a map of groupIds to list of endorsers, sorted by decreasing block height
// Also build a map of endorser PKI ID to group ID
groupEndorsers := map[string][]*endorser{}
var preferredGroup string
var unavailableEndorsers []string
for group, peers := range descriptor.GetEndorsersByGroups() {
var groupPeers []*endorserState
for _, peer := range peers.GetPeers() {
// extract block height
msg := &gossip.GossipMessage{}
err = proto.Unmarshal(peer.GetStateInfo().GetPayload(), msg)
if err != nil {
return nil, err
}
height := msg.GetStateInfo().GetProperties().GetLedgerHeight()
// extract endpoint
err = proto.Unmarshal(peer.GetMembershipInfo().GetPayload(), msg)
if err != nil {
return nil, err
}
member := msg.GetAliveMsg().GetMembership()
// find the endorser in the registry for this endpoint
endorser := reg.lookupEndorser(member.GetEndpoint(), member.GetPkiId(), channel)
if endorser == nil {
unavailableEndorsers = append(unavailableEndorsers, member.GetEndpoint())
continue
}
if endorser == preferredEndorser {
preferredGroup = group
}
groupPeers = append(groupPeers, &endorserState{peer: peer, endorser: endorser, height: height})
}
// sort by decreasing height
sort.Slice(groupPeers, sorter(groupPeers, reg.localEndorser.address))
if len(groupPeers) > 0 {
var endorsers []*endorser
for _, peer := range groupPeers {
endorsers = append(endorsers, peer.endorser)
}
groupEndorsers[group] = endorsers
}
}
// Second, process the layouts
// Group them by groupId and arrange them so that layouts containing the 'preferredOrg' are at the front of the list
var preferredLayouts []*layout
var otherLayouts []*layout
layout:
for _, lo := range descriptor.GetLayouts() {
hasPreferredGroup := false
quantityByGroup := map[string]int{}
for group, quantity := range lo.GetQuantitiesByGroup() {
// if there's no entry in this map, meaning there aren't any available endorsers for this group
if len(groupEndorsers[group]) < int(quantity) {
// The number of available endorsers less than the quantity required, so abandon this layout
continue layout
}
quantityByGroup[group] = int(quantity)
if group == preferredGroup {
hasPreferredGroup = true
}
}
if len(quantityByGroup) == 0 {
// empty layout
break
}
if hasPreferredGroup {
preferredLayouts = append(preferredLayouts, &layout{required: quantityByGroup})
} else {
otherLayouts = append(otherLayouts, &layout{required: quantityByGroup})
}
}
// shuffle the layouts - keep the preferred ones first
rand.Shuffle(len(preferredLayouts), func(i, j int) { preferredLayouts[i], preferredLayouts[j] = preferredLayouts[j], preferredLayouts[i] })
rand.Shuffle(len(otherLayouts), func(i, j int) { otherLayouts[i], otherLayouts[j] = otherLayouts[j], otherLayouts[i] })
layouts := append(preferredLayouts, otherLayouts...)
if len(layouts) == 0 {
return nil, fmt.Errorf("failed to select a set of endorsers that satisfy the endorsement policy due to unavailability of peers: %v", unavailableEndorsers)
}
return newPlan(layouts, groupEndorsers), nil
}
// planForOrgs generates an endorsement plan with a single layout requiring one peer from each of the given orgs for the given chaincode on a channel.
func (reg *registry) planForOrgs(channel string, chaincode string, endorsingOrgs []string) (*plan, error) {
endorsersByOrg := reg.endorsersByOrg(channel, chaincode)
required := map[string]int{}
groupEndorsers := map[string][]*endorser{}
missingOrgs := []string{}
for _, org := range endorsingOrgs {
if es, ok := endorsersByOrg[org]; ok {
for _, e := range es {
groupEndorsers[org] = append(groupEndorsers[org], e.endorser)
}
required[org] = 1
} else {
missingOrgs = append(missingOrgs, org)
}
}
if len(missingOrgs) > 0 {
return nil, fmt.Errorf("failed to find any endorsing peers for org(s): %s", strings.Join(missingOrgs, ", "))
}
return newPlan([]*layout{{required: required}}, groupEndorsers), nil
}
func (reg *registry) endorsersByOrg(channel string, chaincode string) map[string][]*endorserState {
endorsersByOrg := make(map[string][]*endorserState)
for _, member := range reg.channelMembers(channel) {
endorser := reg.lookupEndorser(member.PreferredEndpoint(), member.PKIid, channel)
if endorser == nil {
continue
}
if reg.hasChaincode(member, chaincode) {
endorsersByOrg[endorser.mspid] = append(endorsersByOrg[endorser.mspid], &endorserState{endorser: endorser, height: member.Properties.GetLedgerHeight()})
}
}
// sort by decreasing height in each org
for _, es := range endorsersByOrg {
sort.Slice(es, sorter(es, reg.localEndorser.address))
}
return endorsersByOrg
}
func (reg *registry) channelMembers(channel string) gossipdiscovery.Members {
members := reg.discovery.PeersOfChannel(gossipcommon.ChannelID(channel))
// Ensure local endorser ledger height is up-to-date
for _, member := range members {
if reg.isLocalEndorserID(member.PKIid) {
if ledgerHeight, ok := reg.localLedgerHeight(channel); ok {
member.Properties.LedgerHeight = ledgerHeight
}
break
}
}
return members
}
func (reg *registry) isLocalEndorserID(pkiID gossipcommon.PKIidType) bool {
return !pkiID.IsNotSameFilter(reg.localEndorser.pkiid)
}
func (reg *registry) localLedgerHeight(channel string) (height uint64, ok bool) {
ledger, err := reg.localProvider.Ledger(channel)
if err != nil {
reg.logger.Warnw("local endorser is not a member of channel", "channel", channel, "err", err)
return 0, false
}
info, err := ledger.GetBlockchainInfo()
if err != nil {
logger.Errorw("failed to get local ledger info", "err", err)
return 0, false
}
return info.GetHeight(), true
}
// evaluator returns a plan representing a single endorsement, preferably from local org, if available
// targetOrgs specifies the orgs that are allowed receive the request, due to private data restrictions
func (reg *registry) evaluator(channel string, chaincode string, targetOrgs []string) (*plan, error) {
endorsersByOrg := reg.endorsersByOrg(channel, chaincode)
// If no targetOrgs are specified (i.e. no restrictions), then populate with all available orgs
if len(targetOrgs) == 0 {
for org := range endorsersByOrg {
targetOrgs = append(targetOrgs, org)
}
}
localOrgEndorsers := []*endorserState{}
otherOrgEndorsers := []*endorserState{}
for _, org := range targetOrgs {
if es, ok := endorsersByOrg[org]; ok {
if org == reg.localEndorser.mspid {
localOrgEndorsers = es
} else {
otherOrgEndorsers = append(otherOrgEndorsers, es...)
}
}
}
// sort all the 'other orgs' endorsers by decreasing block height
sort.Slice(otherOrgEndorsers, sorter(otherOrgEndorsers, ""))
var allEndorsers []*endorser
for _, e := range append(localOrgEndorsers, otherOrgEndorsers...) {
allEndorsers = append(allEndorsers, e.endorser)
}
if len(allEndorsers) > 0 {
layout := []*layout{{required: map[string]int{"g1": 1}}} // single layout, one group, one endorsement
groupEndorsers := map[string][]*endorser{"g1": allEndorsers}
return newPlan(layout, groupEndorsers), nil
}
return nil, fmt.Errorf("no peers available to evaluate chaincode %s in channel %s", chaincode, channel)
}
func sorter(e []*endorserState, host string) func(i, j int) bool {
return func(i, j int) bool {
if e[i].height == e[j].height {
// prefer host peer
return e[i].endorser.address == host
}
return e[i].height > e[j].height
}
}
// Returns a set of broadcastClients that can order a transaction for the given channel.
func (reg *registry) orderers(channel string) ([]*orderer, int, error) {
var orderers []*orderer
var ordererEndpoints []*endpointConfig
addr, exists := reg.channelOrderers.Load(channel)
// if it doesn't exist, get the orderers config for this channel
if exists {
ordererEndpoints = addr.([]*endpointConfig)
} else {
// no entry in the map - get the orderer config from discovery
channelOrderers, err := reg.config(channel)
if err != nil {
return nil, 0, err
}
// A config update may have saved this first, in which case don't overwrite it.
addr, _ = reg.channelOrderers.LoadOrStore(channel, channelOrderers)
ordererEndpoints = addr.([]*endpointConfig)
}
for _, ep := range ordererEndpoints {
entry, exists := reg.broadcastClients.Load(ep.address)
if !exists {
// this orderer is new - connect to it and add to the broadcastClients registry
client, err := reg.endpointFactory.newOrderer(ep.address, ep.mspid, ep.tlsRootCerts)
if err != nil {
// Failed to connect to this orderer for some reason. Log the problem and skip to the next one.
reg.logger.Warnw("Failed to connect to orderer", "address", ep.address, "err", err)
continue
}
var loaded bool
entry, loaded = reg.broadcastClients.LoadOrStore(ep.address, client)
if loaded {
// another goroutine got there first, close this new connection
err = client.closeConnection()
if err != nil {
// Failed to close this new connection. Log the problem.
reg.logger.Warnw("Failed to close connection to orderer", "address", client.endpointConfig.logAddress, "err", err)
}
} else {
reg.logger.Infow("Added orderer to registry", "address", client.endpointConfig.logAddress)
}
}
orderers = append(orderers, entry.(*orderer))
}
return orderers, len(ordererEndpoints), nil
}
func (reg *registry) lookupEndorser(endpoint string, pkiID gossipcommon.PKIidType, channel string) *endorser {
lookup := func() (*endorser, bool) {
reg.configLock.RLock()
defer reg.configLock.RUnlock()
// find the endorser in the registry for this endpoint
if reg.isLocalEndorserID(pkiID) {
logger.Debugw("Found local endorser", "pkiID", pkiID)
return reg.localEndorser, false
}
if endpoint == "" {
reg.logger.Warnw("No endpoint for endorser with PKI ID %s", "pkiID", pkiID.String())
return nil, false
}
if e, ok := reg.remoteEndorsers[endpoint]; ok {
logger.Debugw("Found remote endorser", "endpoint", endpoint)
return e, false
}
// not found - try to connect
return nil, true
}
endorser, connect := lookup()
if connect {
reg.logger.Infow("Attempting to connect to endorser", "endpoint", endpoint)
// for efficiency, try to connect to all peers in the channel in one pass
err := reg.connectChannelPeers(channel, true)
if err != nil {
logger.Errorw("Failed to reconnect", "endpoint", endpoint, "err", err)
}
endorser, _ = lookup() // if it failed to reconnect, this will be nil
}
return endorser
}
// Connect to peers in channel, and add to the registry. If a reconnection is required, then it must be removed
// from the registry first, using removeEndorser().
func (reg *registry) connectChannelPeers(channel string, force bool) error {
reg.configLock.Lock() // take a write lock to populate the registry maps
defer reg.configLock.Unlock()
if !force && reg.channelInitialized[channel] {
return nil
}
// get the remoteEndorsers for the channel
peers := map[string]string{}
for _, member := range reg.channelMembers(channel) {
id := member.PKIid.String()
peers[id] = member.PreferredEndpoint()
}
config, err := reg.discovery.Config(channel)
if err != nil {
return fmt.Errorf("failed to get config for channel [%s]: %w", channel, err)
}
for mspid, infoset := range reg.discovery.IdentityInfo().ByOrg() {
var tlsRootCerts [][]byte
if mspInfo, ok := config.GetMsps()[mspid]; ok {
tlsRootCerts = append(tlsRootCerts, mspInfo.GetTlsRootCerts()...)
tlsRootCerts = append(tlsRootCerts, mspInfo.GetTlsIntermediateCerts()...)
}
for _, info := range infoset {
pkiID := info.PKIId
if address, ok := peers[pkiID.String()]; ok {
// add the peer to the peer map - except the local peer, which seems to have an empty address
if _, ok := reg.remoteEndorsers[address]; !ok && len(address) > 0 {
// this peer is new - connect to it and add to the remoteEndorsers registry
endorser, err := reg.endpointFactory.newEndorser(pkiID, address, mspid, tlsRootCerts)
if err != nil {
return err
}
reg.remoteEndorsers[address] = endorser
reg.logger.Infof("Added peer to registry: %s", address)
}
}
}
}
reg.channelInitialized[channel] = true
return nil
}
// removeEndorser closes the connection and removes from the registry, but if the next call to discovery returns that
// peer in its plan, then it will attempt to reconnect and add it back. This could happen if the peer had gone down,
// but the gossip has not notified the discovery service yet.
func (reg *registry) removeEndorser(endorser *endorser) {
if endorser == reg.localEndorser {
// nothing to close
return
}
reg.configLock.Lock()
defer reg.configLock.Unlock()
err := endorser.closeConnection()
if err != nil {
reg.logger.Errorw("Failed to close connection to endorser", "address", endorser.address, "mspid", endorser.mspid, "err", err)
}
delete(reg.remoteEndorsers, endorser.address)
}
func (reg *registry) config(channel string) ([]*endpointConfig, error) {
config, err := reg.discovery.Config(channel)
if err != nil {
return nil, fmt.Errorf("failed to get config for channel [%s]: %w", channel, err)
}
var channelOrderers []*endpointConfig
for mspid, eps := range config.GetOrderers() {
var tlsRootCerts [][]byte
if mspInfo, ok := config.GetMsps()[mspid]; ok {
tlsRootCerts = append(tlsRootCerts, mspInfo.GetTlsRootCerts()...)
tlsRootCerts = append(tlsRootCerts, mspInfo.GetTlsIntermediateCerts()...)
}
for _, ep := range eps.Endpoint {
address := fmt.Sprintf("%s:%d", ep.Host, ep.Port)
channelOrderers = append(channelOrderers, &endpointConfig{address: address, mspid: mspid, tlsRootCerts: tlsRootCerts})
}
}
return channelOrderers, nil
}
func (reg *registry) configUpdate(bundle *channelconfig.Bundle) {
if ordererConfig, ok := bundle.OrdererConfig(); ok {
// orderer config has changed - process the bundle
channel := bundle.ConfigtxValidator().ChannelID()
reg.logger.Infow("Updating orderer config", "channel", channel)
var channelOrderers []*endpointConfig
for _, org := range ordererConfig.Organizations() {
mspid := org.MSPID()
msp := org.MSP()
tlsRootCerts := append([][]byte{}, msp.GetTLSRootCerts()...)
tlsRootCerts = append(tlsRootCerts, msp.GetTLSIntermediateCerts()...)
for _, address := range org.Endpoints() {
channelOrderers = append(channelOrderers, &endpointConfig{address: address, mspid: mspid, tlsRootCerts: tlsRootCerts})
reg.logger.Debugw("Channel orderer", "address", address, "mspid", mspid)
}
}
if len(channelOrderers) > 0 {
reg.closeStaleOrdererConnections(channel, channelOrderers)
reg.channelOrderers.Store(channel, channelOrderers)
}
}
}
func (reg *registry) closeStaleOrdererConnections(channel string, channelOrderers []*endpointConfig) {
// Load the list of orderers that is about to be overwritten, if loaded is false, then another goroutine got there first
oldList, loaded := reg.channelOrderers.LoadAndDelete(channel)
if loaded {
currentEndpoints := map[string]struct{}{}
reg.channelOrderers.Range(func(key, value interface{}) bool {
for _, ep := range value.([]*endpointConfig) {
currentEndpoints[ep.address] = struct{}{}
}
return true
})
for _, ep := range channelOrderers {
currentEndpoints[ep.address] = struct{}{}
}
// if there are any in the oldEndpoints that are not in the currentEndpoints, then remove from registry and close connection
for _, ep := range oldList.([]*endpointConfig) {
if _, exists := currentEndpoints[ep.address]; !exists {
client, found := reg.broadcastClients.LoadAndDelete(ep.address)
if found {
err := client.(*orderer).closeConnection()
if err != nil {
reg.logger.Errorw("Failed to close connection to orderer", "address", ep.logAddress, "mspid", ep.mspid, "err", err)
}
}
}
}
}
}
func (reg *registry) hasChaincode(member gossipdiscovery.NetworkMember, chaincodeName string) bool {
for _, installedChaincode := range member.Properties.GetChaincodes() {
if installedChaincode.GetName() == chaincodeName {
return true
}
}
return reg.systemChaincodes.IsSysCC(chaincodeName)
}