Skip to content

Commit

Permalink
BFT Block Puller: verifier
Browse files Browse the repository at this point in the history
Signed-off-by: Yoav Tock <[email protected]>
Change-Id: I71aa3fd6f0ea0e4ea529614830ea95dfedb3cd36
  • Loading branch information
tock-ibm committed Nov 20, 2023
1 parent 71b7fda commit 465287f
Show file tree
Hide file tree
Showing 8 changed files with 489 additions and 346 deletions.
2 changes: 2 additions & 0 deletions common/deliver/deliver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
350 changes: 350 additions & 0 deletions common/deliverclient/block_verification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,350 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package deliverclient

import (
"bytes"

"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 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.
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")
}
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 {
if lastBlock.Header == nil {
return nil, errors.Errorf("last verified block header is nil")
}
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 config index [%d] is different than the config block number [%d]", lastBlockConfigIndex, configBlock.Header.Number)
}
} else {
lastBlock = configBlock
}

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 umarshaling 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.New("config block channel ID '%s' does not match expected: '%s'")
}

configEnvelope, err := configtx.UnmarshalConfigEnvelope(payload.Data)
if err != nil {
return errors.WithMessage(err, "error umarshaling 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
}

// Verify channel ID
channelID, err := protoutil.GetChannelIDFromBlock(block)
if err != nil {
return errors.Wrapf(err, "failed getting channel id from block number [%d] on channel [%s]", block.Header.Number, a.channelID)
}

if channelID != a.channelID {
return errors.Errorf("invalid block's channel id. Expected [%s]. Given [%s]", a.channelID, channelID)
}

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]", block.Header.Number, a.channelID)
}

err = a.sigVerifierFunc(block.Header, block.Metadata)
if err == nil {
a.lastBlockHeader = block.Header
a.lastBlockHeaderHash = protoutil.BlockHeaderHash(block.Header)
}

return err
}

// 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) == 0 {
return errors.Errorf("block with id [%d] on channel [%s] does not have metadata", block.Header.Number, a.channelID)
}

configIndex, err := protoutil.GetLastConfigIndexFromBlock(block)
if err != nil {
return errors.Wrapf(err, "failed getting config index from block number [%d] on channel [%s]", block.Header.Number, a.channelID)
}

if protoutil.IsConfigBlock(block) {
if configIndex != block.Header.Number {
return errors.Errorf("block [%d] is a config block but has config index [%d] that is different than expected (own number), on channel [%s]",
block.Header.Number, configIndex, a.channelID)
}
return nil
}

// For a data block: a.configBlockHeader may be nil when we start from common.Config and not a config block
if a.configBlockHeader != nil && configIndex != a.configBlockHeader.Number {
return errors.Errorf("block [%d] has config index [%d] that is different than expected: %d (current config), on channel [%s]",
block.Header.Number, configIndex, a.configBlockHeader.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]", block.Header.Number, a.channelID)
}
}
return nil
}
Loading

0 comments on commit 465287f

Please sign in to comment.