From 58991769bab70f8c8bd75f00f11797db092cb76e Mon Sep 17 00:00:00 2001 From: Yoav Tock Date: Mon, 20 Nov 2023 16:58:49 +0200 Subject: [PATCH 1/2] BFT Block Puller: verifier Signed-off-by: Yoav Tock Change-Id: I71aa3fd6f0ea0e4ea529614830ea95dfedb3cd36 --- common/deliver/deliver.go | 2 + common/deliverclient/block_verification.go | 331 ++++++++++++++++++ common/deliverclient/verifier_assembler.go | 57 +++ .../deliverclient/verifier_assembler_test.go | 77 ++++ .../common/cluster/mocks/verifier_factory.go | 53 --- orderer/common/cluster/util.go | 98 ------ orderer/common/cluster/util_test.go | 194 ---------- orderer/common/follower/block_puller.go | 4 +- 8 files changed, 470 insertions(+), 346 deletions(-) create mode 100644 common/deliverclient/block_verification.go create mode 100644 common/deliverclient/verifier_assembler.go create mode 100644 common/deliverclient/verifier_assembler_test.go delete mode 100644 orderer/common/cluster/mocks/verifier_factory.go diff --git a/common/deliver/deliver.go b/common/deliver/deliver.go index c6b16a4a868..fe0a7ca938a 100644 --- a/common/deliver/deliver.go +++ b/common/deliver/deliver.go @@ -4,6 +4,8 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ +// Package deliver contains an implementation of the server-side handlers of the gRPC delivery service. +// The delivery service runs in the orderer and is used to deliver blocks to peers, as well as to other orderers. package deliver import ( diff --git a/common/deliverclient/block_verification.go b/common/deliverclient/block_verification.go new file mode 100644 index 00000000000..44116be467d --- /dev/null +++ b/common/deliverclient/block_verification.go @@ -0,0 +1,331 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package deliverclient + +import ( + "bytes" + "encoding/hex" + + "github.com/hyperledger/fabric-protos-go/common" + "github.com/hyperledger/fabric/bccsp" + "github.com/hyperledger/fabric/common/configtx" + "github.com/hyperledger/fabric/common/flogging" + "github.com/hyperledger/fabric/protoutil" + "github.com/pkg/errors" +) + +type CloneableUpdatableBlockVerifier interface { + // VerifyBlock checks block integrity and its relation to the chain, and verifies the signatures. + VerifyBlock(block *common.Block) error + + // VerifyBlockAttestation does the same as VerifyBlock, except it assumes block.Data = nil. It therefore does not + // compute the block.Data.Hash() and compares it to the block.Header.DataHash. This is used when the orderer + // delivers a block with header & metadata only, as an attestation of block existence. + VerifyBlockAttestation(block *common.Block) error + + // UpdateConfig sets the config by which blocks are verified. It is assumed that this config block had already been + // verified using the VerifyBlock method immediately prior to calling this method. + UpdateConfig(configBlock *common.Block) error + + // UpdateBlockHeader saves the last block header that was verified and handled successfully. + // This must be called after VerifyBlock and VerifyBlockAttestation and successfully handling the block. + UpdateBlockHeader(block *common.Block) + + // Clone makes a copy from the current verifier, a copy that can keep being updated independently. + Clone() CloneableUpdatableBlockVerifier +} + +// BlockVerificationAssistant verifies the integrity and signatures of a block stream, while keeping a copy of the +// latest configuration. +// +// Every time a config block arrives, it must first be verified using VerifyBlock and then +// used as an argument to the UpdateConfig method. +// The block stream could be composed of either: +// - full blocks, which are verified using the VerifyBlock method, or +// - block attestations (a header+metadata, with nil data) which are verified using the VerifyBlockAttestation method. +// In both cases, config blocks must arrive in full. +type BlockVerificationAssistant struct { + channelID string + + // Creates the sigVerifierFunc whenever the configuration is set or updated. + verifierAssembler *BlockVerifierAssembler + // Verifies block signature(s). Recreated whenever the configuration is set or updated. + sigVerifierFunc protoutil.BlockVerifierFunc + // The current config block header. + // It may be nil if the BlockVerificationAssistant is created from common.Config and not a config block. + // After 'UpdateConfig(*common.Block) error' this field is always set. + configBlockHeader *common.BlockHeader + // The last block header may include the number only, in case we start from common.Config. + lastBlockHeader *common.BlockHeader + // The last block header hash is given when we start from common.Config, and is computed otherwise. + lastBlockHeaderHash []byte + + logger *flogging.FabricLogger +} + +// NewBlockVerificationAssistant creates a new BlockVerificationAssistant from a config block. +// This is used in the orderer, where we always have access to the last config block. +func NewBlockVerificationAssistant(configBlock *common.Block, lastBlock *common.Block, cryptoProvider bccsp.BCCSP, lg *flogging.FabricLogger) (*BlockVerificationAssistant, error) { + if configBlock == nil { + return nil, errors.Errorf("config block is nil") + } + if configBlock.Header == nil { + return nil, errors.Errorf("config block header is nil") + } + if !protoutil.IsConfigBlock(configBlock) { + return nil, errors.New("config block parameter does not carry a config block") + } + configIndex, err := protoutil.GetLastConfigIndexFromBlock(configBlock) + if err != nil { + return nil, errors.WithMessage(err, "error getting config index from config block") + } + if configIndex != configBlock.Header.Number { + return nil, errors.Errorf("config block number [%d] is different than its own config index [%d]", configBlock.Header.Number, configIndex) + } + + if lastBlock == nil { + return nil, errors.New("last block is nil") + } + if lastBlock.Header == nil { + return nil, errors.New("last verified block header is nil") + } + if lastBlock.Header.Number < configBlock.Header.Number { + return nil, errors.Errorf("last verified block number [%d] is smaller than the config block number [%d]", lastBlock.Header.Number, configBlock.Header.Number) + } + lastBlockConfigIndex, err := protoutil.GetLastConfigIndexFromBlock(lastBlock) + if err != nil { + return nil, errors.WithMessage(err, "error getting config index from last verified block") + } + if lastBlockConfigIndex != configBlock.Header.Number { + return nil, errors.Errorf("last verified block [%d] config index [%d] is different than the config block number [%d]", lastBlock.Header.Number, lastBlockConfigIndex, configBlock.Header.Number) + } + + configTx, err := protoutil.ExtractEnvelope(configBlock, 0) + if err != nil { + return nil, errors.WithMessage(err, "error extracting envelope") + } + payload, err := protoutil.UnmarshalPayload(configTx.Payload) + if err != nil { + return nil, errors.WithMessage(err, "error umarshaling envelope to payload") + } + + if payload.Header == nil { + return nil, errors.New("missing channel header") + } + + chdr, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader) + if err != nil { + return nil, errors.WithMessage(err, "error unmarshalling channel header") + } + + configEnvelope, err := configtx.UnmarshalConfigEnvelope(payload.Data) + if err != nil { + return nil, errors.WithMessage(err, "error umarshaling config envelope from payload data") + } + + bva := &BlockVerifierAssembler{ + Logger: lg, + BCCSP: cryptoProvider, + } + verifierFunc, err := bva.VerifierFromConfig(configEnvelope, chdr.GetChannelId()) + if err != nil { + return nil, errors.WithMessage(err, "error creating verifier function") + } + + a := &BlockVerificationAssistant{ + channelID: chdr.GetChannelId(), + verifierAssembler: bva, + sigVerifierFunc: verifierFunc, + configBlockHeader: configBlock.Header, + lastBlockHeader: lastBlock.Header, + lastBlockHeaderHash: protoutil.BlockHeaderHash(lastBlock.Header), + logger: lg, + } + + return a, nil +} + +// NewBlockVerificationAssistantFromConfig creates a new BlockVerificationAssistant from a common.Config. +// This is used in the peer, since when the peer starts from a snapshot we may not have access to the last config-block, +// only to the config object. +func NewBlockVerificationAssistantFromConfig(config *common.Config, lastBlockNumber uint64, lastBlockHeaderHash []byte, channelID string, cryptoProvider bccsp.BCCSP, lg *flogging.FabricLogger) (*BlockVerificationAssistant, error) { + if config == nil { + return nil, errors.Errorf("config is nil") + } + + if len(lastBlockHeaderHash) == 0 { + return nil, errors.Errorf("last block header hash is missing") + } + + bva := &BlockVerifierAssembler{ + Logger: lg, + BCCSP: cryptoProvider, + } + verifierFunc, err := bva.VerifierFromConfig(&common.ConfigEnvelope{Config: config}, channelID) + if err != nil { + return nil, errors.WithMessage(err, "error creating verifier function") + } + + a := &BlockVerificationAssistant{ + channelID: channelID, + verifierAssembler: bva, + sigVerifierFunc: verifierFunc, + lastBlockHeader: &common.BlockHeader{Number: lastBlockNumber}, + lastBlockHeaderHash: lastBlockHeaderHash, + logger: lg, + } + + return a, nil +} + +func (a *BlockVerificationAssistant) Clone() CloneableUpdatableBlockVerifier { + c := &BlockVerificationAssistant{ + channelID: a.channelID, + verifierAssembler: a.verifierAssembler, + sigVerifierFunc: a.sigVerifierFunc, + configBlockHeader: a.configBlockHeader, + lastBlockHeader: a.lastBlockHeader, + lastBlockHeaderHash: a.lastBlockHeaderHash, + logger: a.logger, + } + return c +} + +// UpdateConfig sets the config by which blocks are verified. It is assumed that this config block had already been +// verified using the VerifyBlock method immediately prior to calling this method. +func (a *BlockVerificationAssistant) UpdateConfig(configBlock *common.Block) error { + configTx, err := protoutil.ExtractEnvelope(configBlock, 0) + if err != nil { + return errors.WithMessage(err, "error extracting envelope") + } + + payload, err := protoutil.UnmarshalPayload(configTx.Payload) + if err != nil { + return errors.WithMessage(err, "error unmarshalling envelope to payload") + } + + if payload.Header == nil { + return errors.New("missing channel header") + } + + chdr, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader) + if err != nil { + return errors.WithMessage(err, "error unmarshalling channel header") + } + + if chdr.GetChannelId() != a.channelID { + return errors.Errorf("config block channel ID [%s] does not match expected: [%s]", chdr.GetChannelId(), a.channelID) + } + + configEnvelope, err := configtx.UnmarshalConfigEnvelope(payload.Data) + if err != nil { + return errors.WithMessage(err, "error unmarshalling config envelope from payload data") + } + + verifierFunc, err := a.verifierAssembler.VerifierFromConfig(configEnvelope, chdr.GetChannelId()) + if err != nil { + return errors.WithMessage(err, "error creating verifier function") + } + + a.configBlockHeader = configBlock.Header + a.lastBlockHeader = configBlock.Header + a.lastBlockHeaderHash = protoutil.BlockHeaderHash(configBlock.Header) + a.sigVerifierFunc = verifierFunc + + return nil +} + +// VerifyBlock checks block integrity and its relation to the chain, and verifies the signatures. +func (a *BlockVerificationAssistant) VerifyBlock(block *common.Block) error { + if err := a.verifyHeader(block); err != nil { + return err + } + + if err := a.verifyMetadata(block); err != nil { + return err + } + + dataHash, err := protoutil.BlockDataHash(block.Data) + if err != nil { + return errors.Wrapf(err, "failed to verify transactions are well formed for block with id [%d] on channel [%s]", block.Header.Number, a.channelID) + } + + // Verify that Header.DataHash is equal to the hash of block.Data + // This is to ensure that the header is consistent with the data carried by this block + if !bytes.Equal(dataHash, block.Header.DataHash) { + return errors.Errorf("Header.DataHash is different from Hash(block.Data) for block with id [%d] on channel [%s]; Header: %s, Data: %s", + block.Header.Number, a.channelID, hex.EncodeToString(block.Header.DataHash), hex.EncodeToString(dataHash)) + } + + err = a.sigVerifierFunc(block.Header, block.Metadata) + if err != nil { + return err + } + + a.lastBlockHeader = block.Header + a.lastBlockHeaderHash = protoutil.BlockHeaderHash(block.Header) + + return nil +} + +// VerifyBlockAttestation does the same as VerifyBlock, except it assumes block.Data = nil. It therefore does not +// compute the block.Data.Hash() and compare it to the block.Header.DataHash. This is used when the orderer +// delivers a block with header & metadata only, as an attestation of block existence. +func (a *BlockVerificationAssistant) VerifyBlockAttestation(block *common.Block) error { + if err := a.verifyHeader(block); err != nil { + return err + } + + if err := a.verifyMetadata(block); err != nil { + return err + } + + err := a.sigVerifierFunc(block.Header, block.Metadata) + if err == nil { + a.lastBlockHeader = block.Header + a.lastBlockHeaderHash = protoutil.BlockHeaderHash(block.Header) + } + + return err +} + +// UpdateBlockHeader saves the last block header that was verified and handled successfully. +// This must be called after VerifyBlock and VerifyBlockAttestation and successfully handling the block. +func (a *BlockVerificationAssistant) UpdateBlockHeader(block *common.Block) { + a.lastBlockHeader = block.Header + a.lastBlockHeaderHash = protoutil.BlockHeaderHash(block.Header) +} + +func (a *BlockVerificationAssistant) verifyMetadata(block *common.Block) error { + if block.Metadata == nil || len(block.Metadata.Metadata) < len(common.BlockMetadataIndex_name) { + return errors.Errorf("block with id [%d] on channel [%s] does not have metadata or contains too few entries", block.Header.Number, a.channelID) + } + + return nil +} + +func (a *BlockVerificationAssistant) verifyHeader(block *common.Block) error { + if block == nil { + return errors.Errorf("block must be different from nil, channel=%s", a.channelID) + } + if block.Header == nil { + return errors.Errorf("invalid block, header must be different from nil, channel=%s", a.channelID) + } + + expectedBlockNum := a.lastBlockHeader.Number + 1 + if expectedBlockNum != block.Header.Number { + return errors.Errorf("expected block number is [%d] but actual block number inside block is [%d]", expectedBlockNum, block.Header.Number) + } + + if len(a.lastBlockHeaderHash) != 0 { + if !bytes.Equal(block.Header.PreviousHash, a.lastBlockHeaderHash) { + return errors.Errorf("Header.PreviousHash of block [%d] is different from Hash(block.Header) of previous block, on channel [%s], received: %s, expected: %s", + block.Header.Number, a.channelID, hex.EncodeToString(block.Header.PreviousHash), hex.EncodeToString(a.lastBlockHeaderHash)) + } + } + return nil +} diff --git a/common/deliverclient/verifier_assembler.go b/common/deliverclient/verifier_assembler.go new file mode 100644 index 00000000000..bc3a46f3660 --- /dev/null +++ b/common/deliverclient/verifier_assembler.go @@ -0,0 +1,57 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package deliverclient + +import ( + "github.com/hyperledger/fabric-protos-go/common" + "github.com/hyperledger/fabric/bccsp" + "github.com/hyperledger/fabric/common/channelconfig" + "github.com/hyperledger/fabric/common/flogging" + "github.com/hyperledger/fabric/common/policies" + "github.com/hyperledger/fabric/protoutil" + "github.com/pkg/errors" +) + +func createErrorFunc(err error) protoutil.BlockVerifierFunc { + return func(_ *common.BlockHeader, _ *common.BlockMetadata) error { + return errors.Wrap(err, "failed to initialize block verifier function") + } +} + +// BlockVerifierAssembler creates a BlockVerifier out of a config envelope +type BlockVerifierAssembler struct { + Logger *flogging.FabricLogger + BCCSP bccsp.BCCSP +} + +// VerifierFromConfig creates a BlockVerifier from the given configuration. +func (bva *BlockVerifierAssembler) VerifierFromConfig(configuration *common.ConfigEnvelope, channel string) (protoutil.BlockVerifierFunc, error) { + bundle, err := channelconfig.NewBundle(channel, configuration.Config, bva.BCCSP) + if err != nil { + return createErrorFunc(err), err + } + + policy, exists := bundle.PolicyManager().GetPolicy(policies.BlockValidation) + if !exists { + err := errors.Errorf("no `%s` policy in config block", policies.BlockValidation) + return createErrorFunc(err), err + } + + bftEnabled := bundle.ChannelConfig().Capabilities().ConsensusTypeBFT() + + var consenters []*common.Consenter + if bftEnabled { + cfg, ok := bundle.OrdererConfig() + if !ok { + err := errors.New("no orderer section in config block") + return createErrorFunc(err), err + } + consenters = cfg.Consenters() + } + + return protoutil.BlockSignatureVerifier(bftEnabled, consenters, policy), nil +} diff --git a/common/deliverclient/verifier_assembler_test.go b/common/deliverclient/verifier_assembler_test.go new file mode 100644 index 00000000000..6a3ee69a6c3 --- /dev/null +++ b/common/deliverclient/verifier_assembler_test.go @@ -0,0 +1,77 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package deliverclient_test + +import ( + "fmt" + "os" + "path" + "testing" + + "github.com/hyperledger/fabric-protos-go/common" + "github.com/hyperledger/fabric/bccsp/sw" + "github.com/hyperledger/fabric/common/crypto/tlsgen" + "github.com/hyperledger/fabric/common/deliverclient" + "github.com/hyperledger/fabric/core/config/configtest" + "github.com/hyperledger/fabric/internal/configtxgen/encoder" + "github.com/hyperledger/fabric/internal/configtxgen/genesisconfig" + "github.com/stretchr/testify/require" +) + +func TestBlockVerifierAssembler(t *testing.T) { + certDir := t.TempDir() + tlsCA, err := tlsgen.NewCA() + require.NoError(t, err) + config := genesisconfig.Load(genesisconfig.SampleAppChannelSmartBftProfile, configtest.GetDevConfigDir()) + generateCertificatesSmartBFT(t, config, tlsCA, certDir) + group, err := encoder.NewChannelGroup(config) + require.NoError(t, err) + require.NotNil(t, group) + cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) + require.NoError(t, err) + + t.Run("Good config envelope", func(t *testing.T) { + bva := &deliverclient.BlockVerifierAssembler{BCCSP: cryptoProvider} + verifier, err := bva.VerifierFromConfig(&common.ConfigEnvelope{ + Config: &common.Config{ + ChannelGroup: group, + }, + }, "mychannel") + require.NoError(t, err) + + require.Error(t, verifier(nil, nil)) + }) + + t.Run("Bad config envelope", func(t *testing.T) { + bva := &deliverclient.BlockVerifierAssembler{BCCSP: cryptoProvider} + verifier, err := bva.VerifierFromConfig(&common.ConfigEnvelope{}, "mychannel") + require.EqualError(t, err, "channelconfig Config cannot be nil") + err = verifier(nil, nil) + require.EqualError(t, err, "failed to initialize block verifier function: channelconfig Config cannot be nil") + }) +} + +func generateCertificatesSmartBFT(t *testing.T, confAppSmartBFT *genesisconfig.Profile, tlsCA tlsgen.CA, certDir string) { + for i, c := range confAppSmartBFT.Orderer.ConsenterMapping { + t.Logf("BFT Consenter: %+v", c) + srvC, err := tlsCA.NewServerCertKeyPair(c.Host) + require.NoError(t, err) + srvP := path.Join(certDir, fmt.Sprintf("server%d.crt", i)) + err = os.WriteFile(srvP, srvC.Cert, 0o644) + require.NoError(t, err) + + clnC, err := tlsCA.NewClientCertKeyPair() + require.NoError(t, err) + clnP := path.Join(certDir, fmt.Sprintf("client%d.crt", i)) + err = os.WriteFile(clnP, clnC.Cert, 0o644) + require.NoError(t, err) + + c.Identity = srvP + c.ServerTLSCert = srvP + c.ClientTLSCert = clnP + } +} diff --git a/orderer/common/cluster/mocks/verifier_factory.go b/orderer/common/cluster/mocks/verifier_factory.go deleted file mode 100644 index b968acfaac3..00000000000 --- a/orderer/common/cluster/mocks/verifier_factory.go +++ /dev/null @@ -1,53 +0,0 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. - -package mocks - -import ( - common "github.com/hyperledger/fabric-protos-go/common" - mock "github.com/stretchr/testify/mock" - - protoutil "github.com/hyperledger/fabric/protoutil" -) - -// VerifierFactory is an autogenerated mock type for the VerifierFactory type -type VerifierFactory struct { - mock.Mock -} - -// VerifierFromConfig provides a mock function with given fields: configuration, channel -func (_m *VerifierFactory) VerifierFromConfig(configuration *common.ConfigEnvelope, channel string) (protoutil.BlockVerifierFunc, error) { - ret := _m.Called(configuration, channel) - - var r0 protoutil.BlockVerifierFunc - if rf, ok := ret.Get(0).(func(*common.ConfigEnvelope, string) protoutil.BlockVerifierFunc); ok { - r0 = rf(configuration, channel) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(protoutil.BlockVerifierFunc) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(*common.ConfigEnvelope, string) error); ok { - r1 = rf(configuration, channel) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -type mockConstructorTestingTNewVerifierFactory interface { - mock.TestingT - Cleanup(func()) -} - -// NewVerifierFactory creates a new instance of VerifierFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewVerifierFactory(t mockConstructorTestingTNewVerifierFactory) *VerifierFactory { - mock := &VerifierFactory{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/orderer/common/cluster/util.go b/orderer/common/cluster/util.go index 6cf9e58e74b..e1ad2ab9798 100644 --- a/orderer/common/cluster/util.go +++ b/orderer/common/cluster/util.go @@ -483,14 +483,6 @@ type VerifierFactory interface { VerifierFromConfig(configuration *common.ConfigEnvelope, channel string) (protoutil.BlockVerifierFunc, error) } -// VerificationRegistry registers verifiers and retrieves them. -type VerificationRegistry struct { - LoadVerifier func(chain string) protoutil.BlockVerifierFunc - Logger *flogging.FabricLogger - VerifierFactory VerifierFactory - VerifiersByChannel map[string]protoutil.BlockVerifierFunc -} - //go:generate mockery --dir . --name ChainPuller --case underscore --output mocks/ // ChainPuller pulls blocks from a chain @@ -505,63 +497,6 @@ type ChainPuller interface { Close() } -// RegisterVerifier adds a verifier into the registry if applicable. -func (vr *VerificationRegistry) RegisterVerifier(chain string) { - if _, exists := vr.VerifiersByChannel[chain]; exists { - vr.Logger.Debugf("No need to register verifier for chain %s", chain) - return - } - - v := vr.LoadVerifier(chain) - if v == nil { - vr.Logger.Errorf("Failed loading verifier for chain %s", chain) - return - } - - vr.VerifiersByChannel[chain] = v - vr.Logger.Infof("Registered verifier for chain %s", chain) -} - -// RetrieveVerifier returns a BlockVerifierFunc for the given channel, or nil if not found. -func (vr *VerificationRegistry) RetrieveVerifier(channel string) protoutil.BlockVerifierFunc { - verifier, exists := vr.VerifiersByChannel[channel] - if exists { - return verifier - } - vr.Logger.Errorf("No verifier for channel %s exists", channel) - return nil -} - -// BlockCommitted notifies the VerificationRegistry upon a block commit, which may -// trigger a registration of a verifier out of the block in case the block is a config block. -func (vr *VerificationRegistry) BlockCommitted(block *common.Block, channel string) { - conf, err := ConfigFromBlock(block) - // The block doesn't contain a config block, but is a valid block - if err == errNotAConfig { - vr.Logger.Debugf("Committed block [%d] for channel %s that is not a config block", - block.Header.Number, channel) - return - } - // The block isn't a valid block - if err != nil { - vr.Logger.Errorf("Failed parsing block of channel %s: %v, content: %s", - channel, err, BlockToString(block)) - return - } - - // The block contains a config block - verifier, err := vr.VerifierFactory.VerifierFromConfig(conf, channel) - if err != nil { - vr.Logger.Errorf("Failed creating a verifier from a config block for channel %s: %v, content: %s", - channel, err, BlockToString(block)) - return - } - - vr.VerifiersByChannel[channel] = verifier - - vr.Logger.Debugf("Committed config block [%d] for channel %s", block.Header.Number, channel) -} - // BlockToString returns a string representation of this block. func BlockToString(block *common.Block) string { buff := &bytes.Buffer{} @@ -572,39 +507,6 @@ func BlockToString(block *common.Block) string { // BlockCommitFunc signals a block commit. type BlockCommitFunc func(block *common.Block, channel string) -// BlockVerifierAssembler creates a BlockVerifier out of a config envelope -type BlockVerifierAssembler struct { - Logger *flogging.FabricLogger - BCCSP bccsp.BCCSP -} - -// VerifierFromConfig creates a BlockVerifier from the given configuration. -func (bva *BlockVerifierAssembler) VerifierFromConfig(configuration *common.ConfigEnvelope, channel string) (protoutil.BlockVerifierFunc, error) { - bundle, err := channelconfig.NewBundle(channel, configuration.Config, bva.BCCSP) - if err != nil { - return createErrorFunc(err), err - } - - policy, exists := bundle.PolicyManager().GetPolicy(policies.BlockValidation) - if !exists { - err := errors.New("no policies in config block") - return createErrorFunc(err), err - } - - bftEnabled := bundle.ChannelConfig().Capabilities().ConsensusTypeBFT() - - var consenters []*common.Consenter - if bftEnabled { - cfg, ok := bundle.OrdererConfig() - if !ok { - err := errors.New("no orderer section in config block") - return createErrorFunc(err), err - } - consenters = cfg.Consenters() - } - return protoutil.BlockSignatureVerifier(bftEnabled, consenters, policy), nil -} - // BlockValidationPolicyVerifier verifies signatures based on the block validation policy. type BlockValidationPolicyVerifier struct { Logger *flogging.FabricLogger diff --git a/orderer/common/cluster/util_test.go b/orderer/common/cluster/util_test.go index 7e4e46dec6c..7f0f4a99bca 100644 --- a/orderer/common/cluster/util_test.go +++ b/orderer/common/cluster/util_test.go @@ -14,7 +14,6 @@ import ( "errors" "fmt" "math" - "os" "strings" "sync" "testing" @@ -41,10 +40,7 @@ import ( "github.com/hyperledger/fabric/orderer/common/cluster" "github.com/hyperledger/fabric/orderer/common/cluster/mocks" "github.com/hyperledger/fabric/protoutil" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" ) //go:generate counterfeiter -o mocks/policy.go --fake-name Policy . policy @@ -640,33 +636,6 @@ func TestBlockValidationPolicyVerifier(t *testing.T) { } } -func TestBlockVerifierAssembler(t *testing.T) { - config := genesisconfig.Load(genesisconfig.SampleInsecureSoloProfile, configtest.GetDevConfigDir()) - group, err := encoder.NewChannelGroup(config) - require.NoError(t, err) - require.NotNil(t, group) - cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) - require.NoError(t, err) - - t.Run("Good config envelope", func(t *testing.T) { - bva := &cluster.BlockVerifierAssembler{BCCSP: cryptoProvider} - verifier, err := bva.VerifierFromConfig(&common.ConfigEnvelope{ - Config: &common.Config{ - ChannelGroup: group, - }, - }, "mychannel") - require.NoError(t, err) - - require.Error(t, verifier(nil, nil)) - }) - - t.Run("Bad config envelope", func(t *testing.T) { - bva := &cluster.BlockVerifierAssembler{BCCSP: cryptoProvider} - _, err := bva.VerifierFromConfig(&common.ConfigEnvelope{}, "mychannel") - require.EqualError(t, err, "channelconfig Config cannot be nil") - }) -} - func TestLastConfigBlock(t *testing.T) { blockRetriever := &mocks.BlockRetriever{} blockRetriever.On("Block", uint64(42)).Return(&common.Block{}) @@ -732,169 +701,6 @@ func TestLastConfigBlock(t *testing.T) { } } -func TestVerificationRegistryRegisterVerifier(t *testing.T) { - blockBytes, err := os.ReadFile("testdata/mychannel.block") - require.NoError(t, err) - - block := &common.Block{} - require.NoError(t, proto.Unmarshal(blockBytes, block)) - - mockErr := errors.New("Mock error") - verifier := func(header *common.BlockHeader, metadata *common.BlockMetadata) error { - return mockErr - } - - verifierFactory := &mocks.VerifierFactory{} - verifierFactory.On("VerifierFromConfig", - mock.Anything, "mychannel").Return(verifier, nil) - - registry := &cluster.VerificationRegistry{ - Logger: flogging.MustGetLogger("test"), - VerifiersByChannel: make(map[string]protoutil.BlockVerifierFunc), - VerifierFactory: verifierFactory, - } - - var loadCount int - registry.LoadVerifier = func(chain string) protoutil.BlockVerifierFunc { - require.Equal(t, "mychannel", chain) - loadCount++ - return verifier - } - - v := registry.RetrieveVerifier("mychannel") - require.Nil(t, v) - - registry.RegisterVerifier("mychannel") - v = registry.RetrieveVerifier("mychannel") - require.Same(t, verifier(nil, nil), v(nil, nil)) - require.Equal(t, 1, loadCount) - - // If the verifier exists, this is a no-op - registry.RegisterVerifier("mychannel") - require.Equal(t, 1, loadCount) -} - -func TestVerificationRegistry(t *testing.T) { - blockBytes, err := os.ReadFile("testdata/mychannel.block") - require.NoError(t, err) - - block := &common.Block{} - require.NoError(t, proto.Unmarshal(blockBytes, block)) - - flogging.ActivateSpec("test=DEBUG") - defer flogging.Reset() - - mockErr := errors.New("Mock error") - verifier := func(header *common.BlockHeader, metadata *common.BlockMetadata) error { - return mockErr - } - - for _, testCase := range []struct { - description string - verifiersByChannel map[string]protoutil.BlockVerifierFunc - blockCommitted *common.Block - channelCommitted string - channelRetrieved string - expectedVerifier protoutil.BlockVerifierFunc - verifierFromConfig protoutil.BlockVerifierFunc - verifierFromConfigErr error - loggedMessages map[string]struct{} - }{ - { - description: "bad block", - blockCommitted: &common.Block{}, - channelRetrieved: "foo", - channelCommitted: "foo", - loggedMessages: map[string]struct{}{ - "Failed parsing block of channel foo: empty block, content: " + - "{\n\t\"data\": null,\n\t\"header\": null,\n\t\"metadata\": null\n}\n": {}, - "No verifier for channel foo exists": {}, - }, - expectedVerifier: nil, - }, - { - description: "not a config block", - blockCommitted: createBlockChain(5, 5)[0], - channelRetrieved: "foo", - channelCommitted: "foo", - loggedMessages: map[string]struct{}{ - "No verifier for channel foo exists": {}, - "Committed block [5] for channel foo that is not a config block": {}, - }, - expectedVerifier: nil, - }, - { - description: "valid block but verifier from config fails", - blockCommitted: block, - verifierFromConfigErr: errors.New("invalid MSP config"), - channelRetrieved: "bar", - channelCommitted: "bar", - loggedMessages: map[string]struct{}{ - "Failed creating a verifier from a " + - "config block for channel bar: invalid MSP config, " + - "content: " + cluster.BlockToString(block): {}, - "No verifier for channel bar exists": {}, - }, - expectedVerifier: nil, - }, - { - description: "valid block and verifier from config succeeds but wrong channel retrieved", - blockCommitted: block, - verifierFromConfig: verifier, - channelRetrieved: "foo", - channelCommitted: "bar", - loggedMessages: map[string]struct{}{ - "No verifier for channel foo exists": {}, - "Committed config block [0] for channel bar": {}, - }, - expectedVerifier: nil, - verifiersByChannel: make(map[string]protoutil.BlockVerifierFunc), - }, - { - description: "valid block and verifier from config succeeds", - blockCommitted: block, - verifierFromConfig: verifier, - channelRetrieved: "bar", - channelCommitted: "bar", - loggedMessages: map[string]struct{}{ - "Committed config block [0] for channel bar": {}, - }, - expectedVerifier: verifier, - verifiersByChannel: make(map[string]protoutil.BlockVerifierFunc), - }, - } { - t.Run(testCase.description, func(t *testing.T) { - verifierFactory := &mocks.VerifierFactory{} - verifierFactory.On("VerifierFromConfig", - mock.Anything, testCase.channelCommitted).Return(testCase.verifierFromConfig, testCase.verifierFromConfigErr) - - registry := &cluster.VerificationRegistry{ - Logger: flogging.MustGetLogger("test"), - VerifiersByChannel: testCase.verifiersByChannel, - VerifierFactory: verifierFactory, - } - - loggedEntriesByMethods := make(map[string]struct{}) - // Configure the logger to collect the message logged - registry.Logger = registry.Logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error { - loggedEntriesByMethods[entry.Message] = struct{}{} - return nil - })) - - registry.BlockCommitted(testCase.blockCommitted, testCase.channelCommitted) - verifier := registry.RetrieveVerifier(testCase.channelRetrieved) - - require.Equal(t, testCase.loggedMessages, loggedEntriesByMethods) - if testCase.expectedVerifier == nil { - require.Nil(t, verifier) - } else { - require.NotNil(t, verifier) - require.Same(t, testCase.expectedVerifier(nil, nil), verifier(nil, nil)) - } - }) - } -} - func injectAdditionalTLSCAEndpointPair(t *testing.T, block *common.Block, endpoint string, tlsCA []byte, orgName string) { // Unwrap the layers until we reach the orderer addresses env, err := protoutil.ExtractEnvelope(block, 0) diff --git a/orderer/common/follower/block_puller.go b/orderer/common/follower/block_puller.go index 32fec7505ad..0fa40c35da3 100644 --- a/orderer/common/follower/block_puller.go +++ b/orderer/common/follower/block_puller.go @@ -9,6 +9,8 @@ package follower import ( "encoding/pem" + "github.com/hyperledger/fabric/common/deliverclient" + "github.com/hyperledger/fabric-protos-go/common" "github.com/hyperledger/fabric/bccsp" "github.com/hyperledger/fabric/common/flogging" @@ -75,7 +77,7 @@ func NewBlockPullerCreator( factory := &BlockPullerCreator{ channelID: channelID, bccsp: bccsp, - blockSigVerifierFactory: &cluster.BlockVerifierAssembler{ + blockSigVerifierFactory: &deliverclient.BlockVerifierAssembler{ Logger: logger, BCCSP: bccsp, }, From 9bb4f00cca64fc0e15d13a99fbbe1cb7f799c4f6 Mon Sep 17 00:00:00 2001 From: May Rosenbaum Date: Thu, 23 Nov 2023 15:08:48 +0200 Subject: [PATCH 2/2] BFT Block Puller: block verification unit tests Signed-off-by: May Rosenbaum Signed-off-by: Yoav Tock Change-Id: I073a6086cbdb7cfa316b4a638cbb302b57e9d262 --- .../deliverclient/block_verification_test.go | 669 ++++++++++++++++++ 1 file changed, 669 insertions(+) create mode 100644 common/deliverclient/block_verification_test.go diff --git a/common/deliverclient/block_verification_test.go b/common/deliverclient/block_verification_test.go new file mode 100644 index 00000000000..e9e0bc46c8d --- /dev/null +++ b/common/deliverclient/block_verification_test.go @@ -0,0 +1,669 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package deliverclient + +import ( + "encoding/hex" + "fmt" + "os" + "path" + "testing" + + "github.com/hyperledger/fabric-protos-go/common" + "github.com/hyperledger/fabric/bccsp/sw" + "github.com/hyperledger/fabric/common/crypto/tlsgen" + "github.com/hyperledger/fabric/common/flogging" + "github.com/hyperledger/fabric/core/config/configtest" + "github.com/hyperledger/fabric/internal/configtxgen/encoder" + "github.com/hyperledger/fabric/internal/configtxgen/genesisconfig" + "github.com/hyperledger/fabric/protoutil" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +func TestNewBlockVerificationAssistantFromConfig(t *testing.T) { + certDir := t.TempDir() + tlsCA, err := tlsgen.NewCA() + require.NoError(t, err) + configProfile := genesisconfig.Load(genesisconfig.SampleAppChannelSmartBftProfile, configtest.GetDevConfigDir()) + generateCertificatesSmartBFT(t, configProfile, tlsCA, certDir) + group, err := encoder.NewChannelGroup(configProfile) + require.NoError(t, err) + require.NotNil(t, group) + cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) + require.NoError(t, err) + + block := blockWithGroups(group, "channel1", 1) + blockHeaderHash := protoutil.BlockHeaderHash(block.Header) + + config := &common.Config{ChannelGroup: group} + logger := flogging.MustGetLogger("logger") + blockVerifierAssembler := &BlockVerifierAssembler{Logger: logger, BCCSP: cryptoProvider} + require.NoError(t, err) + + t.Run("creating new block verification assistant succeed", func(t *testing.T) { + assistant, err := NewBlockVerificationAssistantFromConfig(config, 1, blockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + require.Equal(t, "channel1", assistant.channelID) + require.Equal(t, blockVerifierAssembler, assistant.verifierAssembler) + require.Nil(t, assistant.configBlockHeader) + require.Equal(t, &common.BlockHeader{Number: 1}, assistant.lastBlockHeader) + require.Equal(t, blockHeaderHash, assistant.lastBlockHeaderHash) + require.Equal(t, logger, assistant.logger) + err = assistant.sigVerifierFunc(block.Header, block.Metadata) + require.EqualError(t, err, "signature set did not satisfy policy") + }) + + t.Run("config is nil", func(t *testing.T) { + assistant, err := NewBlockVerificationAssistantFromConfig(nil, 1, blockHeaderHash, "channel1", cryptoProvider, logger) + require.Error(t, err) + require.Nil(t, assistant) + require.Contains(t, err.Error(), "config is nil") + }) + + t.Run("last block header hash is nil", func(t *testing.T) { + assistant, err := NewBlockVerificationAssistantFromConfig(config, 1, nil, "channel1", cryptoProvider, logger) + require.Error(t, err) + require.Nil(t, assistant) + require.Contains(t, err.Error(), "last block header hash is missing") + }) +} + +func TestNewBlockVerificationAssistantFromConfigBlock(t *testing.T) { + certDir := t.TempDir() + tlsCA, err := tlsgen.NewCA() + require.NoError(t, err) + configProfile := genesisconfig.Load(genesisconfig.SampleAppChannelSmartBftProfile, configtest.GetDevConfigDir()) + generateCertificatesSmartBFT(t, configProfile, tlsCA, certDir) + group, err := encoder.NewChannelGroup(configProfile) + require.NoError(t, err) + require.NotNil(t, group) + cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) + require.NoError(t, err) + + configBlock := blockWithGroups(group, "channel1", 0) + lastBlock := nonConfigBlock("channel1", 10) + nextBlock := nonConfigBlock("channel1", 11) + logger := flogging.MustGetLogger("logger") + + blockVerifierAssembler := &BlockVerifierAssembler{Logger: logger, BCCSP: cryptoProvider} + require.NoError(t, err) + + t.Run("success: creating new block verification assistant succeed", func(t *testing.T) { + assistant, err := NewBlockVerificationAssistant(configBlock, lastBlock, cryptoProvider, logger) + require.NoError(t, err) + require.Equal(t, "channel1", assistant.channelID) + require.Equal(t, blockVerifierAssembler, assistant.verifierAssembler) + require.Equal(t, configBlock.Header, assistant.configBlockHeader) + require.Equal(t, lastBlock.Header, assistant.lastBlockHeader) + require.Equal(t, protoutil.BlockHeaderHash(lastBlock.Header), assistant.lastBlockHeaderHash) + require.Equal(t, logger, assistant.logger) + err = assistant.sigVerifierFunc(nextBlock.Header, nextBlock.Metadata) + require.EqualError(t, err, "signature set did not satisfy policy") + }) + + t.Run("config block is nil", func(t *testing.T) { + assistant, err := NewBlockVerificationAssistant(nil, lastBlock, cryptoProvider, logger) + require.Error(t, err) + require.Nil(t, assistant) + require.EqualError(t, err, "config block is nil") + }) + + t.Run("last block header is nil", func(t *testing.T) { + lastBlock2 := nonConfigBlock("channel1", 10) + lastBlock2.Header = nil + assistant, err := NewBlockVerificationAssistant(configBlock, lastBlock2, cryptoProvider, logger) + require.Error(t, err) + require.Nil(t, assistant) + require.EqualError(t, err, "last verified block header is nil") + }) + + t.Run("last block config index mismatch", func(t *testing.T) { + configBlock1 := blockWithGroups(group, "channel1", 3) + assistant, err := NewBlockVerificationAssistant(configBlock1, lastBlock, cryptoProvider, logger) + require.Error(t, err) + require.Nil(t, assistant) + require.EqualError(t, err, "last verified block [10] config index [0] is different than the config block number [3]") + }) + + t.Run("last block number mismatch", func(t *testing.T) { + configBlock1 := blockWithGroups(group, "channel1", 3) + lastBlock2 := nonConfigBlock("channel1", 2) + assistant, err := NewBlockVerificationAssistant(configBlock1, lastBlock2, cryptoProvider, logger) + require.Error(t, err) + require.Nil(t, assistant) + require.EqualError(t, err, "last verified block number [2] is smaller than the config block number [3]") + }) +} + +func TestBlockVerificationAssistant_VerifyBlock(t *testing.T) { + certDir := t.TempDir() + tlsCA, err := tlsgen.NewCA() + require.NoError(t, err) + configProfile := genesisconfig.Load(genesisconfig.SampleAppChannelSmartBftProfile, configtest.GetDevConfigDir()) + generateCertificatesSmartBFT(t, configProfile, tlsCA, certDir) + group, err := encoder.NewChannelGroup(configProfile) + require.NoError(t, err) + require.NotNil(t, group) + config := &common.Config{ChannelGroup: group} + cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) + require.NoError(t, err) + logger := flogging.MustGetLogger("logger") + + t.Run("success: verify config block ", func(t *testing.T) { + block := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + err = assistant.VerifyBlock(block) + require.NoError(t, err) + require.Equal(t, assistant.lastBlockHeader, block.Header) + require.Equal(t, assistant.lastBlockHeaderHash, protoutil.BlockHeaderHash(block.Header)) + }) + + t.Run("success: verify non config block", func(t *testing.T) { + block := nonConfigBlock("channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + err = assistant.VerifyBlock(block) + require.NoError(t, err) + }) + + t.Run("invalid block: empty", func(t *testing.T) { + block := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + block.Data.Data = [][]byte{} + + err = assistant.VerifyBlock(block) + require.Error(t, err) + require.EqualError(t, err, "failed to verify transactions are well formed for block with id [1] on channel [channel1]: empty block") + + block.Data.Data = nil + + err = assistant.VerifyBlock(block) + require.Error(t, err) + require.EqualError(t, err, "failed to verify transactions are well formed for block with id [1] on channel [channel1]: empty block") + + block.Data = nil + + err = assistant.VerifyBlock(block) + require.Error(t, err) + require.EqualError(t, err, "failed to verify transactions are well formed for block with id [1] on channel [channel1]: empty block") + + err = assistant.VerifyBlock(nil) + require.Error(t, err) + require.EqualError(t, err, "block must be different from nil, channel=channel1") + }) + + t.Run("invalid block TXs: invalid TXs", func(t *testing.T) { + block := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + block.Data.Data = [][]byte{{0x66, 0x77}, {0x88, 0x99}} + + err = assistant.VerifyBlock(block) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to verify transactions are well formed for block with id [1] on channel [channel1]: transaction 0 is invalid:") + }) + + t.Run("bad metadata", func(t *testing.T) { + block := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + block.Metadata = nil + + err = assistant.VerifyBlock(block) + require.Error(t, err) + require.EqualError(t, err, "block with id [1] on channel [channel1] does not have metadata or contains too few entries") + + block.Metadata = &common.BlockMetadata{Metadata: nil} + require.Error(t, err) + require.EqualError(t, err, "block with id [1] on channel [channel1] does not have metadata or contains too few entries") + }) + + t.Run("invalid header: empty", func(t *testing.T) { + block := nonConfigBlock("channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + block.Header.Number = 2 + err = assistant.VerifyBlock(block) + require.Error(t, err) + require.EqualError(t, err, "expected block number is [1] but actual block number inside block is [2]") + + block.Header = nil + err = assistant.VerifyBlock(block) + require.Error(t, err) + require.EqualError(t, err, "invalid block, header must be different from nil, channel=channel1") + }) + + t.Run("invalid header: wrong number", func(t *testing.T) { + block := nonConfigBlock("channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + block.Header.Number = 2 + err = assistant.VerifyBlock(block) + require.Error(t, err) + require.EqualError(t, err, "expected block number is [1] but actual block number inside block is [2]") + }) + + t.Run("Header.DataHash is different from Hash(block.Data)", func(t *testing.T) { + block := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + expectedDataHash := block.Header.DataHash + block.Header.DataHash = []byte{4, 5, 6} + + err = assistant.VerifyBlock(block) + require.Error(t, err) + require.EqualError(t, err, fmt.Sprintf("Header.DataHash is different from Hash(block.Data) for block with id [1] on channel [channel1]; Header: 040506, Data: %s", hex.EncodeToString(expectedDataHash))) + }) + + t.Run("Header.PreviousHash is different from expected", func(t *testing.T) { + block := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{2, 3, 4} + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + block.Header.DataHash = []byte{4, 5, 6} + + err = assistant.VerifyBlock(block) + require.Error(t, err) + require.EqualError(t, err, "Header.PreviousHash of block [1] is different from Hash(block.Header) of previous block, on channel [channel1], received: 010203, expected: 020304") + }) + + t.Run("sig verifier func return policy error", func(t *testing.T) { + block := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnPolicyError() + + err = assistant.VerifyBlock(block) + require.Error(t, err) + require.EqualError(t, err, errors.Errorf("signature set did not satisfy policy").Error()) + }) + + t.Run("non config block with tx not well formed", func(t *testing.T) { + block := nonConfigBlock("channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + txEnv := &common.Envelope{ + Payload: protoutil.MarshalOrPanic(&common.Payload{ + Header: &common.Header{ + ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{ + Type: int32(common.HeaderType_ENDORSER_TRANSACTION), + ChannelId: "channel1", + }), + }, + }), + } + block.Data.Data[0] = protoutil.MarshalOrPanic(txEnv) + + err = assistant.VerifyBlock(block) + require.Error(t, err) + require.Equal(t, err.Error(), errors.Errorf("failed to verify transactions are well formed for block with id [%d] on channel [%s]: transaction 0 has no signature", block.Header.Number, assistant.channelID).Error()) + }) +} + +func TestBlockVerificationAssistant_UpdateConfig(t *testing.T) { + certDir := t.TempDir() + tlsCA, err := tlsgen.NewCA() + require.NoError(t, err) + configProfile := genesisconfig.Load(genesisconfig.SampleAppChannelSmartBftProfile, configtest.GetDevConfigDir()) + generateCertificatesSmartBFT(t, configProfile, tlsCA, certDir) + group, err := encoder.NewChannelGroup(configProfile) + require.NoError(t, err) + require.NotNil(t, group) + cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) + require.NoError(t, err) + + t.Run("update config succeed", func(t *testing.T) { + configBlock := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + config := &common.Config{ChannelGroup: group} + logger := flogging.MustGetLogger("logger") + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + + expectedVerifierFunc, err := assistant.verifierAssembler.VerifierFromConfig(&common.ConfigEnvelope{Config: config}, "channel1") + require.NoError(t, err) + + err = assistant.UpdateConfig(configBlock) + require.NoError(t, err) + require.Equal(t, assistant.configBlockHeader, configBlock.Header) + require.Equal(t, assistant.lastBlockHeader, configBlock.Header) + require.Equal(t, assistant.lastBlockHeaderHash, protoutil.BlockHeaderHash(configBlock.Header)) + require.Equal(t, assistant.sigVerifierFunc(configBlock.Header, configBlock.Metadata).Error(), expectedVerifierFunc(configBlock.Header, configBlock.Metadata).Error()) + }) + + t.Run("block data is corrupt", func(t *testing.T) { + configBlock := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + config := &common.Config{ChannelGroup: group} + logger := flogging.MustGetLogger("logger") + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + + configTx, err := protoutil.ExtractEnvelope(configBlock, 0) + require.NoError(t, err) + configTx.Payload = []byte{1, 2, 3, 4} + configBlock.Data.Data[0] = protoutil.MarshalOrPanic(configTx) + err = assistant.UpdateConfig(configBlock) + require.Error(t, err) + require.Contains(t, err.Error(), "error unmarshalling envelope to payload: error unmarshalling Payload: proto:") + + configBlock.Data.Data = nil + + err = assistant.UpdateConfig(configBlock) + require.Error(t, err) + require.EqualError(t, err, "error extracting envelope: envelope index out of bounds") + + configBlock.Data = nil + + err = assistant.UpdateConfig(configBlock) + require.Error(t, err) + require.EqualError(t, err, "error extracting envelope: block data is nil") + }) + + t.Run("missing channel header", func(t *testing.T) { + configBlock := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + config := &common.Config{ChannelGroup: group} + logger := flogging.MustGetLogger("logger") + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + + configBlock.Data.Data[0] = protoutil.MarshalOrPanic(&common.Envelope{ + Payload: protoutil.MarshalOrPanic(&common.Payload{ + Header: nil, + }), + }) + + err = assistant.UpdateConfig(configBlock) + require.Error(t, err) + require.EqualError(t, err, "missing channel header") + }) + + t.Run("config block channel id is different from the assistant channel id", func(t *testing.T) { + configBlock := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + config := &common.Config{ChannelGroup: group} + logger := flogging.MustGetLogger("logger") + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel2", cryptoProvider, logger) + require.NoError(t, err) + + blockChannelId, err := protoutil.GetChannelIDFromBlock(configBlock) + require.NoError(t, err) + + err = assistant.UpdateConfig(configBlock) + require.Error(t, err) + require.Equal(t, err.Error(), errors.Errorf("config block channel ID [%s] does not match expected: [%s]", blockChannelId, assistant.channelID).Error()) + }) +} + +func TestBlockVerificationAssistant_UpdateBlockHeader(t *testing.T) { + certDir := t.TempDir() + tlsCA, err := tlsgen.NewCA() + require.NoError(t, err) + configProfile := genesisconfig.Load(genesisconfig.SampleAppChannelSmartBftProfile, configtest.GetDevConfigDir()) + generateCertificatesSmartBFT(t, configProfile, tlsCA, certDir) + group, err := encoder.NewChannelGroup(configProfile) + require.NoError(t, err) + require.NotNil(t, group) + cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) + require.NoError(t, err) + + block := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + config := &common.Config{ChannelGroup: group} + logger := flogging.MustGetLogger("logger") + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel2", cryptoProvider, logger) + require.NoError(t, err) + + assistant.UpdateBlockHeader(block) + require.Equal(t, assistant.lastBlockHeader, block.Header) + require.Equal(t, assistant.lastBlockHeaderHash, protoutil.BlockHeaderHash(block.Header)) +} + +func TestBlockVerificationAssistant_VerifyBlockAttestation(t *testing.T) { + certDir := t.TempDir() + tlsCA, err := tlsgen.NewCA() + require.NoError(t, err) + configProfile := genesisconfig.Load(genesisconfig.SampleAppChannelSmartBftProfile, configtest.GetDevConfigDir()) + generateCertificatesSmartBFT(t, configProfile, tlsCA, certDir) + group, err := encoder.NewChannelGroup(configProfile) + require.NoError(t, err) + require.NotNil(t, group) + cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) + require.NoError(t, err) + + t.Run("verify non config block attestation with data succeed", func(t *testing.T) { + block := nonConfigBlock("channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + config := &common.Config{ChannelGroup: group} + logger := flogging.MustGetLogger("logger") + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + err = assistant.VerifyBlockAttestation(block) + require.NoError(t, err) + require.Equal(t, assistant.lastBlockHeader, block.Header) + require.Equal(t, assistant.lastBlockHeaderHash, protoutil.BlockHeaderHash(block.Header)) + }) + + t.Run("verify non block attestation with nil data succeed", func(t *testing.T) { + block := nonConfigBlock("channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + config := &common.Config{ChannelGroup: group} + logger := flogging.MustGetLogger("logger") + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + block.Data = nil + + err = assistant.VerifyBlockAttestation(block) + require.NoError(t, err) + }) + + t.Run("nil metadata", func(t *testing.T) { + block := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + config := &common.Config{ChannelGroup: group} + logger := flogging.MustGetLogger("logger") + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnNoError() + + block.Metadata = nil + + err = assistant.VerifyBlockAttestation(block) + require.Error(t, err) + require.EqualError(t, err, "block with id [1] on channel [channel1] does not have metadata or contains too few entries") + }) + + t.Run("sig verifier func return policy error", func(t *testing.T) { + block := blockWithGroups(group, "channel1", 1) + lastBlockHeaderHash := []byte{1, 2, 3} + config := &common.Config{ChannelGroup: group} + logger := flogging.MustGetLogger("logger") + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + assistant.sigVerifierFunc = sigVerifierFuncReturnPolicyError() + + err = assistant.VerifyBlockAttestation(block) + require.Error(t, err) + require.EqualError(t, err, errors.Errorf("signature set did not satisfy policy").Error()) + }) +} + +func TestBlockVerificationAssistant_Clone(t *testing.T) { + certDir := t.TempDir() + tlsCA, err := tlsgen.NewCA() + require.NoError(t, err) + configProfile := genesisconfig.Load(genesisconfig.SampleAppChannelSmartBftProfile, configtest.GetDevConfigDir()) + generateCertificatesSmartBFT(t, configProfile, tlsCA, certDir) + group, err := encoder.NewChannelGroup(configProfile) + require.NoError(t, err) + require.NotNil(t, group) + cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) + require.NoError(t, err) + + lastBlockHeaderHash := []byte{1, 2, 3} + config := &common.Config{ChannelGroup: group} + logger := flogging.MustGetLogger("logger") + + assistant, err := NewBlockVerificationAssistantFromConfig(config, 0, lastBlockHeaderHash, "channel1", cryptoProvider, logger) + require.NoError(t, err) + cloned := assistant.Clone().(*BlockVerificationAssistant) + + require.Equal(t, assistant.channelID, cloned.channelID) + require.Equal(t, assistant.verifierAssembler, cloned.verifierAssembler) + require.Equal(t, assistant.configBlockHeader, cloned.configBlockHeader) + require.Equal(t, assistant.lastBlockHeader, cloned.lastBlockHeader) + require.Equal(t, assistant.lastBlockHeaderHash, cloned.lastBlockHeaderHash) + require.Equal(t, assistant.logger, cloned.logger) +} + +func generateCertificatesSmartBFT(t *testing.T, confAppSmartBFT *genesisconfig.Profile, tlsCA tlsgen.CA, certDir string) { + for i, c := range confAppSmartBFT.Orderer.ConsenterMapping { + t.Logf("BFT Consenter: %+v", c) + srvC, err := tlsCA.NewServerCertKeyPair(c.Host) + require.NoError(t, err) + srvP := path.Join(certDir, fmt.Sprintf("server%d.crt", i)) + err = os.WriteFile(srvP, srvC.Cert, 0o644) + require.NoError(t, err) + + clnC, err := tlsCA.NewClientCertKeyPair() + require.NoError(t, err) + clnP := path.Join(certDir, fmt.Sprintf("client%d.crt", i)) + err = os.WriteFile(clnP, clnC.Cert, 0o644) + require.NoError(t, err) + + c.Identity = srvP + c.ServerTLSCert = srvP + c.ClientTLSCert = clnP + } +} + +func blockWithGroups(groups *common.ConfigGroup, channelID string, blockNumber uint64) *common.Block { + block := protoutil.NewBlock(blockNumber, []byte{1, 2, 3}) + block.Data = &common.BlockData{ + Data: [][]byte{ + protoutil.MarshalOrPanic(&common.Envelope{ + Payload: protoutil.MarshalOrPanic(&common.Payload{ + Data: protoutil.MarshalOrPanic(&common.ConfigEnvelope{ + Config: &common.Config{ + ChannelGroup: groups, + }, + }), + Header: &common.Header{ + ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{ + Type: int32(common.HeaderType_CONFIG), + ChannelId: channelID, + }), + }, + }), + }), + }, + } + block.Header.DataHash = protoutil.ComputeBlockDataHash(block.Data) + block.Metadata.Metadata[common.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic(&common.Metadata{ + Value: protoutil.MarshalOrPanic(&common.OrdererBlockMetadata{ + LastConfig: &common.LastConfig{ + Index: uint64(blockNumber), + }, + }), + }) + + return block +} + +func nonConfigBlock(channelID string, blockNumber uint64) *common.Block { + block := protoutil.NewBlock(blockNumber, []byte{1, 2, 3}) + + txEnv := &common.Envelope{ + Payload: protoutil.MarshalOrPanic(&common.Payload{ + Header: &common.Header{ + ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{ + Type: int32(common.HeaderType_ENDORSER_TRANSACTION), + ChannelId: channelID, + }), + }, + }), + Signature: []byte{1, 2, 3}, + } + + block.Data = &common.BlockData{ + Data: [][]byte{ + protoutil.MarshalOrPanic(txEnv), + }, + } + + block.Header.DataHash = protoutil.ComputeBlockDataHash(block.Data) + protoutil.InitBlockMetadata(block) + + return block +} + +func sigVerifierFuncReturnNoError() func(header *common.BlockHeader, metadata *common.BlockMetadata) error { + return func(header *common.BlockHeader, metadata *common.BlockMetadata) error { + return nil + } +} + +func sigVerifierFuncReturnPolicyError() func(header *common.BlockHeader, metadata *common.BlockMetadata) error { + return func(header *common.BlockHeader, metadata *common.BlockMetadata) error { + return errors.New("signature set did not satisfy policy") + } +}