184 lines
7.4 KiB
Go
184 lines
7.4 KiB
Go
/*
|
|
Copyright IBM Corp. 2017 All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package follower
|
|
|
|
import (
|
|
"encoding/pem"
|
|
|
|
"github.com/hyperledger/fabric-protos-go/common"
|
|
"github.com/hyperledger/fabric/bccsp"
|
|
"github.com/hyperledger/fabric/common/flogging"
|
|
"github.com/hyperledger/fabric/internal/pkg/identity"
|
|
"github.com/hyperledger/fabric/orderer/common/cluster"
|
|
"github.com/hyperledger/fabric/orderer/common/localconfig"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
//go:generate counterfeiter -o mocks/channel_puller.go -fake-name ChannelPuller . ChannelPuller
|
|
|
|
// ChannelPuller pulls blocks for a channel
|
|
type ChannelPuller interface {
|
|
PullBlock(seq uint64) *common.Block
|
|
HeightsByEndpoints() (map[string]uint64, error)
|
|
UpdateEndpoints(endpoints []cluster.EndpointCriteria)
|
|
Close()
|
|
}
|
|
|
|
// BlockPullerCreator creates a ChannelPuller on demand.
|
|
// It also maintains a link to a block signature verifier, and exposes a method to update it on incoming config blocks.
|
|
// The ChannelPuller generated by this factory always accesses the updated verifier, since it is generated
|
|
// with a link to the factory's VerifyBlockSequence method.
|
|
type BlockPullerCreator struct {
|
|
JoinBlock *common.Block
|
|
channelID string
|
|
bccsp bccsp.BCCSP
|
|
blockSigVerifierFactory cluster.VerifierFactory // Creates a new block signature verifier
|
|
blockSigVerifier protoutil.BlockVerifierFunc // The current block signature verifier, from the latest channel config
|
|
clusterConfig localconfig.Cluster
|
|
signer identity.SignerSerializer
|
|
der *pem.Block
|
|
stdDialer *cluster.StandardDialer
|
|
ClusterVerifyBlocks ClusterVerifyBlocksFunc // Default: cluster.VerifyBlocks, or a mock for testing
|
|
vb protoutil.VerifierBuilder
|
|
}
|
|
|
|
// ClusterVerifyBlocksFunc is a function that matches the signature of cluster.VerifyBlocks, and allows mocks for testing.
|
|
type ClusterVerifyBlocksFunc func(blockBuff []*common.Block, signatureVerifier protoutil.BlockVerifierFunc, vb protoutil.VerifierBuilder) error
|
|
|
|
// NewBlockPullerCreator creates a new BlockPullerCreator, using the configuration details that do not change during
|
|
// the life cycle of the orderer.
|
|
func NewBlockPullerCreator(
|
|
channelID string,
|
|
logger *flogging.FabricLogger,
|
|
signer identity.SignerSerializer,
|
|
baseDialer *cluster.PredicateDialer,
|
|
clusterConfig localconfig.Cluster,
|
|
bccsp bccsp.BCCSP,
|
|
) (*BlockPullerCreator, error) {
|
|
stdDialer := &cluster.StandardDialer{
|
|
Config: baseDialer.Config,
|
|
}
|
|
stdDialer.Config.AsyncConnect = false
|
|
stdDialer.Config.SecOpts.VerifyCertificate = nil
|
|
|
|
der, _ := pem.Decode(stdDialer.Config.SecOpts.Certificate)
|
|
if der == nil {
|
|
return nil, errors.Errorf("client certificate isn't in PEM format: %v",
|
|
string(stdDialer.Config.SecOpts.Certificate))
|
|
}
|
|
|
|
factory := &BlockPullerCreator{
|
|
channelID: channelID,
|
|
bccsp: bccsp,
|
|
blockSigVerifierFactory: &cluster.BlockVerifierAssembler{
|
|
Logger: logger,
|
|
BCCSP: bccsp,
|
|
},
|
|
clusterConfig: clusterConfig,
|
|
signer: signer,
|
|
stdDialer: stdDialer,
|
|
der: der,
|
|
ClusterVerifyBlocks: cluster.VerifyBlocksBFT, // The default block sequence verification method.
|
|
vb: cluster.BlockVerifierBuilder(bccsp),
|
|
}
|
|
|
|
return factory, nil
|
|
}
|
|
|
|
// BlockPuller creates a block puller on demand, taking the endpoints from the config block.
|
|
func (creator *BlockPullerCreator) BlockPuller(configBlock *common.Block, stopChannel chan struct{}) (ChannelPuller, error) {
|
|
// Extract the TLS CA certs and endpoints from the join-block
|
|
endpoints, err := cluster.EndpointconfigFromConfigBlock(configBlock, creator.bccsp)
|
|
if err != nil {
|
|
return nil, errors.WithMessage(err, "error extracting endpoints from config block")
|
|
}
|
|
|
|
creator.JoinBlock = configBlock
|
|
|
|
bp := &cluster.BlockPuller{
|
|
VerifyBlockSequence: creator.VerifyBlockSequence,
|
|
Logger: flogging.MustGetLogger("orderer.common.cluster.puller").With("channel", creator.channelID),
|
|
RetryTimeout: creator.clusterConfig.ReplicationRetryTimeout,
|
|
MaxTotalBufferBytes: creator.clusterConfig.ReplicationBufferSize,
|
|
MaxPullBlockRetries: uint64(creator.clusterConfig.ReplicationMaxRetries),
|
|
FetchTimeout: creator.clusterConfig.ReplicationPullTimeout,
|
|
Endpoints: endpoints,
|
|
Signer: creator.signer,
|
|
TLSCert: creator.der.Bytes,
|
|
Channel: creator.channelID,
|
|
Dialer: creator.stdDialer,
|
|
StopChannel: stopChannel,
|
|
}
|
|
|
|
return bp, nil
|
|
}
|
|
|
|
// UpdateVerifierFromConfigBlock creates a new block signature verifier from the config block and updates the internal
|
|
// link to said verifier.
|
|
func (creator *BlockPullerCreator) UpdateVerifierFromConfigBlock(configBlock *common.Block) error {
|
|
configEnv, err := cluster.ConfigFromBlock(configBlock)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "failed to extract config envelope from block")
|
|
}
|
|
verifier, err := creator.blockSigVerifierFactory.VerifierFromConfig(configEnv, creator.channelID)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "failed to construct a block signature verifier from config envelope")
|
|
}
|
|
creator.blockSigVerifier = verifier
|
|
return nil
|
|
}
|
|
|
|
// VerifyBlockSequence verifies a sequence of blocks, using the internal block signature verifier. It also bootstraps
|
|
// the block sig verifier form the genesis block if it does not exist, and skips verifying the genesis block.
|
|
func (creator *BlockPullerCreator) VerifyBlockSequence(blocks []*common.Block, _ string) error {
|
|
if len(blocks) == 0 {
|
|
return errors.New("buffer is empty")
|
|
}
|
|
if blocks[0] == nil {
|
|
return errors.New("first block is nil")
|
|
}
|
|
if blocks[0].Header == nil {
|
|
return errors.New("first block header is nil")
|
|
}
|
|
if blocks[0].Header.Number == 0 {
|
|
if creator.JoinBlock != nil && creator.JoinBlock.Header.Number == 0 {
|
|
// If we have joined with a genesis block,
|
|
// replace the genesis block we got from the network
|
|
// with our own.
|
|
blocks[0] = creator.JoinBlock
|
|
}
|
|
configEnv, err := cluster.ConfigFromBlock(blocks[0])
|
|
if err != nil {
|
|
return errors.WithMessage(err, "failed to extract config envelope from genesis block")
|
|
}
|
|
// Bootstrap the verifier from the genesis block, as it will be used to verify
|
|
// the subsequent blocks in the batch.
|
|
creator.blockSigVerifier, err = creator.blockSigVerifierFactory.VerifierFromConfig(configEnv, creator.channelID)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "failed to construct a block signature verifier from genesis block")
|
|
}
|
|
blocksAfterGenesis := blocks[1:]
|
|
if len(blocksAfterGenesis) == 0 {
|
|
return nil
|
|
}
|
|
// TODO: we should revisit this as in theory a malicious node can give an incorrect genesis block.
|
|
// However, if that happens, then the onboarding node would deviate from the correct nodes and it will be detected by us
|
|
// due to follower chain comparing the block it is joined with with the block it pulled by doing:
|
|
// if c.joinBlock != nil && !proto.Equal(c.ledgerResources.Block(c.joinBlock.Header.Number).Data, c.joinBlock.Data) {
|
|
// c.logger.Panicf("Join block (%d) we pulled mismatches block we joined with", c.joinBlock.Header.Number)
|
|
// }
|
|
return creator.ClusterVerifyBlocks(blocksAfterGenesis, creator.blockSigVerifier, creator.vb)
|
|
}
|
|
|
|
if creator.blockSigVerifier == nil {
|
|
return errors.New("nil block signature verifier")
|
|
}
|
|
|
|
return creator.ClusterVerifyBlocks(blocks, creator.blockSigVerifier, creator.vb)
|
|
}
|