go_study/fabric-main/orderer/consensus/smartbft/verifier_test.go

625 lines
21 KiB
Go

/*
Copyright IBM Corp. 2017 All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package smartbft_test
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
"sync/atomic"
"testing"
"github.com/SmartBFT-Go/consensus/pkg/types"
"github.com/SmartBFT-Go/consensus/smartbftprotos"
"github.com/golang/protobuf/proto"
cb "github.com/hyperledger/fabric-protos-go/common"
"github.com/hyperledger/fabric-protos-go/msp"
"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/orderer/consensus/smartbft"
"github.com/hyperledger/fabric/orderer/consensus/smartbft/mocks"
"github.com/hyperledger/fabric/protoutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
var hashOfZero = hex.EncodeToString(sha256.New().Sum(nil))
func TestNodeIdentitiesByID(t *testing.T) {
m := make(smartbft.NodeIdentitiesByID)
for id := uint64(0); id < 4; id++ {
m[id] = protoutil.MarshalOrPanic(&msp.SerializedIdentity{
IdBytes: []byte(fmt.Sprintf("%d", id)),
Mspid: "OrdererOrg",
})
sID := &msp.SerializedIdentity{}
err := proto.Unmarshal(m[id], sID)
assert.NoError(t, err)
id2, ok := m.IdentityToID(m[id])
assert.True(t, ok)
assert.Equal(t, id, id2)
}
_, ok := m.IdentityToID(protoutil.MarshalOrPanic(&msp.SerializedIdentity{
IdBytes: []byte(fmt.Sprintf("%d", 4)),
Mspid: "OrdererOrg",
}))
assert.False(t, ok)
_, ok = m.IdentityToID([]byte{1, 2, 3})
assert.False(t, ok)
}
func TestVerifySignature(t *testing.T) {
logger := flogging.MustGetLogger("test")
cv := &mocks.ConsenterVerifier{}
cv.On("Evaluate", mock.Anything).Return(errors.New("bad signature"))
v := &smartbft.Verifier{
Logger: logger,
ConsenterVerifier: cv,
RuntimeConfig: &atomic.Value{},
}
rtc := smartbft.RuntimeConfig{
ID2Identities: map[uint64][]byte{3: {0, 2, 4, 6}},
}
v.RuntimeConfig.Store(rtc)
t.Run("identity doesn't exist", func(t *testing.T) {
err := v.VerifySignature(types.Signature{
ID: 2,
})
assert.EqualError(t, err, "node with id of 2 doesn't exist")
})
t.Run("signature doesn't verify", func(t *testing.T) {
err := v.VerifySignature(types.Signature{
ID: 3,
})
assert.EqualError(t, err, "bad signature")
})
}
func TestVerifyConsenterSig(t *testing.T) {
logger := flogging.MustGetLogger("test")
lastBlock := makeNonConfigBlock(19, 10)
lastConfigBlock := makeConfigBlock(10)
ac := &mocks.AccessController{}
ac.On("Evaluate", mock.Anything).Return(nil)
ledger := &mocks.Ledger{}
ledger.On("Height").Return(uint64(20))
ledger.On("Block", uint64(19)).Return(lastBlock)
ledger.On("Block", uint64(10)).Return(lastConfigBlock)
sequencer := &mocks.Sequencer{}
sequencer.On("Sequence").Return(uint64(12))
reqInspector := &smartbft.RequestInspector{
ValidateIdentityStructure: func(_ *msp.SerializedIdentity) error {
return nil
},
}
cv := &mocks.ConsenterVerifier{}
cv.On("Evaluate", mock.Anything).Return(errors.New("bad signature"))
lastHash := hex.EncodeToString(protoutil.BlockHeaderHash(lastBlock.Header))
for _, testCase := range []struct {
description string
verificationSequence uint64
lastBlock *cb.Block
id2Identity map[uint64][]byte
signatureMutator func(types.Signature) types.Signature
lastConfigBlockNum uint64
bftMetadataMutator func([]byte) []byte
proposalMutator func(proposal types.Proposal) types.Proposal
ordererBlockMetadataMutator func(metadata *cb.OrdererBlockMetadata)
expectedErr string
}{
{
description: "No consenter in mapping",
expectedErr: "node with id of 3 doesn't exist",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
},
{
description: "Bad signature format",
expectedErr: "malformed signature format: asn1: structure error: tags don't match (16 vs " +
"{class:0 tag:1 length:2 isCompound:false}) {optional:false explicit:false application:" +
"false private:false defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} Signature @2",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
id2Identity: map[uint64][]byte{3: {0, 2, 4, 6}},
signatureMutator: func(signature types.Signature) types.Signature {
return types.Signature{
ID: signature.ID,
Value: signature.Value,
Msg: []byte{1, 2, 3},
}
},
},
{
description: "metadata doesn't match proposal",
expectedErr: "consenter metadata in OrdererBlockMetadata doesn't match proposal",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
id2Identity: map[uint64][]byte{3: {0, 2, 4, 6}},
signatureMutator: func(signature types.Signature) types.Signature {
sig := smartbft.Signature{}
_ = sig.Unmarshal(signature.Msg)
sig.OrdererBlockMetadata = nil
return types.Signature{
ID: signature.ID,
Value: signature.Value,
Msg: sig.Marshal(),
}
},
},
{
description: "block header doesn't match proposal",
expectedErr: "mismatched block header",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
id2Identity: map[uint64][]byte{3: {0, 2, 4, 6}},
signatureMutator: func(signature types.Signature) types.Signature {
sig := smartbft.Signature{}
_ = sig.Unmarshal(signature.Msg)
sig.BlockHeader = nil
return types.Signature{
ID: signature.ID,
Value: signature.Value,
Msg: sig.Marshal(),
}
},
},
{
description: "nonce different than what was used for signing",
expectedErr: "bad signature",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
id2Identity: map[uint64][]byte{3: {0, 2, 4, 6}},
signatureMutator: func(signature types.Signature) types.Signature {
sig := smartbft.Signature{}
_ = sig.Unmarshal(signature.Msg)
// sig.Nonce = nil
return types.Signature{
ID: signature.ID,
Value: signature.Value,
Msg: sig.Marshal(),
}
},
},
{
description: "orderer block metadata is malformed",
expectedErr: "malformed orderer metadata in signature: proto: cannot parse invalid wire-format data",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
id2Identity: map[uint64][]byte{3: {0, 2, 4, 6}},
signatureMutator: func(signature types.Signature) types.Signature {
sig := smartbft.Signature{}
_ = sig.Unmarshal(signature.Msg)
sig.OrdererBlockMetadata = []byte{1, 2, 3}
return types.Signature{
ID: signature.ID,
Value: signature.Value,
Msg: sig.Marshal(),
}
},
},
{
description: "signature doesn't verify",
expectedErr: "bad signature",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
id2Identity: map[uint64][]byte{3: {0, 2, 4, 6}},
},
{
description: "malformed proposal",
expectedErr: "bad payload and metadata tuple: asn1: structure error: " +
"tags don't match (16 vs {class:0 tag:1 length:2 isCompound:false}) " +
"{optional:false explicit:false application:false private:false " +
"defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false " +
"omitEmpty:false} ByteBufferTuple @2",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
id2Identity: map[uint64][]byte{3: {0, 2, 4, 6}},
proposalMutator: func(proposal types.Proposal) types.Proposal {
proposal.Payload = []byte{1, 2, 3}
return proposal
},
},
{
description: "empty proposal payload",
expectedErr: "proposal payload cannot be nil",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
id2Identity: map[uint64][]byte{3: {0, 2, 4, 6}},
proposalMutator: func(proposal types.Proposal) types.Proposal {
proposal.Payload = nil
return proposal
},
},
{
description: "metadata too short",
expectedErr: "block metadata is of size 4 but should be of size 5",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
id2Identity: map[uint64][]byte{3: {0, 2, 4, 6}},
proposalMutator: func(proposal types.Proposal) types.Proposal {
block, _ := smartbft.ProposalToBlock(proposal)
block.Metadata.Metadata = make([][]byte, len(cb.BlockMetadataIndex_name)-1)
bbt := &smartbft.ByteBufferTuple{}
_ = bbt.FromBytes(proposal.Payload)
bbt.B = protoutil.MarshalOrPanic(block.Metadata)
proposal.Payload = bbt.ToBytes()
return proposal
},
},
{
description: "malformed signature metadata",
expectedErr: "malformed signature metadata: proto: cannot parse invalid wire-format data",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
id2Identity: map[uint64][]byte{3: {0, 2, 4, 6}},
proposalMutator: func(proposal types.Proposal) types.Proposal {
block, _ := smartbft.ProposalToBlock(proposal)
block.Metadata.Metadata[cb.BlockMetadataIndex_SIGNATURES] = []byte{1, 2, 3}
bbt := &smartbft.ByteBufferTuple{}
_ = bbt.FromBytes(proposal.Payload)
bbt.B = protoutil.MarshalOrPanic(block.Metadata)
proposal.Payload = bbt.ToBytes()
return proposal
},
},
{
description: "malformed OrdererBlockMetadata",
expectedErr: "malformed orderer metadata in block: proto: cannot parse invalid wire-format data",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
id2Identity: map[uint64][]byte{3: {0, 2, 4, 6}},
proposalMutator: func(proposal types.Proposal) types.Proposal {
block, _ := smartbft.ProposalToBlock(proposal)
md := &cb.Metadata{}
_ = proto.Unmarshal(block.Metadata.Metadata[cb.BlockMetadataIndex_SIGNATURES], md)
md.Value = []byte{1, 2, 3}
block.Metadata.Metadata[cb.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic(md)
bbt := &smartbft.ByteBufferTuple{}
_ = bbt.FromBytes(proposal.Payload)
bbt.B = protoutil.MarshalOrPanic(block.Metadata)
proposal.Payload = bbt.ToBytes()
return proposal
},
},
{
description: "mismatched OrdererBlockMetadata",
expectedErr: "signature's OrdererBlockMetadata and OrdererBlockMetadata extracted from block do not match",
lastBlock: lastBlock,
lastConfigBlockNum: lastConfigBlock.Header.Number,
id2Identity: map[uint64][]byte{3: {0, 2, 4, 6}},
proposalMutator: func(proposal types.Proposal) types.Proposal {
block, _ := smartbft.ProposalToBlock(proposal)
md := &cb.Metadata{}
_ = proto.Unmarshal(block.Metadata.Metadata[cb.BlockMetadataIndex_SIGNATURES], md)
obm := &cb.OrdererBlockMetadata{}
_ = proto.Unmarshal(md.Value, obm)
obm.LastConfig.Index++
md.Value = protoutil.MarshalOrPanic(obm)
block.Metadata.Metadata[cb.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic(md)
bbt := &smartbft.ByteBufferTuple{}
_ = bbt.FromBytes(proposal.Payload)
bbt.B = protoutil.MarshalOrPanic(block.Metadata)
proposal.Payload = bbt.ToBytes()
return proposal
},
},
} {
t.Run(testCase.description, func(t *testing.T) {
ss := &mocks.SignerSerializer{}
ss.On("Sign", mock.Anything).Return([]byte{1, 2, 3}, nil).Once()
ss.On("Serialize", mock.Anything).Return([]byte{0, 2, 4, 6}, nil)
s := &smartbft.Signer{
LastConfigBlockNum: func(_ *cb.Block) uint64 {
return lastConfigBlock.Header.Number
},
SignerSerializer: ss,
Logger: flogging.MustGetLogger("test"),
ID: 3,
}
rtc := smartbft.RuntimeConfig{
ID2Identities: testCase.id2Identity,
LastBlock: testCase.lastBlock,
LastConfigBlock: lastConfigBlock,
LastCommittedBlockHash: lastHash,
}
runtimeConfig := &atomic.Value{}
runtimeConfig.Store(rtc)
assembler := &smartbft.Assembler{
VerificationSeq: func() uint64 {
return testCase.verificationSequence
},
Logger: logger,
RuntimeConfig: runtimeConfig,
}
md := protoutil.MarshalOrPanic(&smartbftprotos.ViewMetadata{
LatestSequence: 1,
ViewId: 2,
})
proposal := assembler.AssembleProposal(md, [][]byte{nonConfigTx})
signature := *s.SignProposal(proposal, nil)
if testCase.signatureMutator != nil {
signature = testCase.signatureMutator(signature)
}
v := &smartbft.Verifier{
RuntimeConfig: runtimeConfig,
Logger: logger,
Ledger: ledger,
VerificationSequencer: sequencer,
AccessController: ac,
ConsenterVerifier: cv,
ReqInspector: reqInspector,
}
if testCase.proposalMutator != nil {
proposal = testCase.proposalMutator(proposal)
}
_, err := v.VerifyConsenterSig(signature, proposal)
assert.Error(t, err)
assert.Equal(t, testCase.expectedErr, strings.ReplaceAll(err.Error(), "\u00a0", " "))
})
}
}
func TestVerifyProposal(t *testing.T) {
logger := flogging.MustGetLogger("test")
lastBlock := makeNonConfigBlock(19, 10)
notLastBlock := makeNonConfigBlock(18, 10)
lastConfigBlock := makeConfigBlock(10)
ledger := &mocks.Ledger{}
ledger.On("Height").Return(uint64(20))
ledger.On("Block", uint64(19)).Return(lastBlock)
ledger.On("Block", uint64(10)).Return(lastConfigBlock)
sequencer := &mocks.Sequencer{}
sequencer.On("Sequence").Return(uint64(12))
ac := &mocks.AccessController{}
ac.On("Evaluate", mock.Anything).Return(nil)
cv := &mocks.ConsenterVerifier{}
reqInspector := &smartbft.RequestInspector{
ValidateIdentityStructure: func(_ *msp.SerializedIdentity) error {
return nil
},
}
lastHash := hex.EncodeToString(protoutil.BlockHeaderHash(lastBlock.Header))
for _, testCase := range []struct {
description string
verificationSequence uint64
lastBlock *cb.Block
lastConfigBlock *cb.Block
bftMetadataMutator func([]byte) []byte
ordererBlockMetadataMutator func(metadata *cb.OrdererBlockMetadata)
expectedErr string
}{
{
description: "green path",
verificationSequence: 12,
lastBlock: lastBlock,
lastConfigBlock: lastConfigBlock,
bftMetadataMutator: noopMutator,
ordererBlockMetadataMutator: noopOrdererBlockMetadataMutator,
},
{
description: "wrong verification sequence 1",
verificationSequence: 11,
lastBlock: lastBlock,
lastConfigBlock: lastConfigBlock,
bftMetadataMutator: noopMutator,
ordererBlockMetadataMutator: noopOrdererBlockMetadataMutator,
expectedErr: "expected verification sequence 12, but proposal has 11",
},
{
description: "wrong verification sequence 2",
verificationSequence: 12,
lastBlock: lastBlock,
lastConfigBlock: protoutil.NewBlock(666, nil),
bftMetadataMutator: noopMutator,
ordererBlockMetadataMutator: noopOrdererBlockMetadataMutator,
expectedErr: "last config in block orderer metadata points to 666 but our persisted last config is 10",
},
{
description: "wrong verification sequence 3",
verificationSequence: 12,
lastBlock: notLastBlock,
lastConfigBlock: lastConfigBlock,
bftMetadataMutator: noopMutator,
ordererBlockMetadataMutator: noopOrdererBlockMetadataMutator,
expectedErr: fmt.Sprintf("previous header hash is %s but expected %s",
hex.EncodeToString(protoutil.BlockHeaderHash(notLastBlock.Header)),
hex.EncodeToString(protoutil.BlockHeaderHash(lastBlock.Header))),
},
{
description: "corrupt metadata",
verificationSequence: 12,
lastBlock: lastBlock,
lastConfigBlock: lastConfigBlock,
bftMetadataMutator: func([]byte) []byte {
return []byte{1, 2, 3}
},
ordererBlockMetadataMutator: noopOrdererBlockMetadataMutator,
expectedErr: "failed unmarshaling smartbft metadata from proposal: proto: cannot parse invalid wire-format data",
},
{
description: "corrupt metadata",
verificationSequence: 12,
lastBlock: lastBlock,
lastConfigBlock: lastConfigBlock,
bftMetadataMutator: func([]byte) []byte {
return protoutil.MarshalOrPanic(&smartbftprotos.ViewMetadata{LatestSequence: 100, ViewId: 2})
},
ordererBlockMetadataMutator: noopOrdererBlockMetadataMutator,
expectedErr: "expected metadata in block to be [view_id:2 latest_sequence:100] but got [view_id:2 latest_sequence:1]",
},
{
description: "No last config",
verificationSequence: 12,
lastBlock: lastBlock,
lastConfigBlock: lastConfigBlock,
bftMetadataMutator: noopMutator,
ordererBlockMetadataMutator: func(metadata *cb.OrdererBlockMetadata) {
metadata.LastConfig = nil
},
expectedErr: "last config is nil",
},
{
description: "Mismatched last config",
verificationSequence: 12,
lastBlock: lastBlock,
lastConfigBlock: lastConfigBlock,
bftMetadataMutator: noopMutator,
ordererBlockMetadataMutator: func(metadata *cb.OrdererBlockMetadata) {
metadata.LastConfig.Index = 666
},
expectedErr: "last config in block orderer metadata points to 666 but our persisted last config is 10",
},
{
description: "Corrupt inner BFT metadata",
verificationSequence: 12,
lastBlock: lastBlock,
lastConfigBlock: lastConfigBlock,
bftMetadataMutator: noopMutator,
ordererBlockMetadataMutator: func(metadata *cb.OrdererBlockMetadata) {
metadata.ConsenterMetadata = []byte{1, 2, 3}
},
expectedErr: "failed unmarshaling smartbft metadata from block: proto: cannot parse invalid wire-format data",
},
{
description: "Mismatching inner BFT metadata",
verificationSequence: 12,
lastBlock: lastBlock,
lastConfigBlock: lastConfigBlock,
bftMetadataMutator: noopMutator,
ordererBlockMetadataMutator: func(metadata *cb.OrdererBlockMetadata) {
metadata.ConsenterMetadata = protoutil.MarshalOrPanic(&smartbftprotos.ViewMetadata{LatestSequence: 666})
},
expectedErr: "expected metadata in block to be [view_id:2 latest_sequence:1] but got [view_id:0 latest_sequence:666]",
},
} {
t.Run(testCase.description, func(t *testing.T) {
runtimeConfig := &atomic.Value{}
rtc := smartbft.RuntimeConfig{
LastCommittedBlockHash: lastHash,
LastBlock: testCase.lastBlock,
LastConfigBlock: testCase.lastConfigBlock,
}
runtimeConfig.Store(rtc)
assembler := &smartbft.Assembler{
RuntimeConfig: &atomic.Value{},
VerificationSeq: func() uint64 {
return testCase.verificationSequence
},
Logger: logger,
}
assembler.RuntimeConfig.Store(smartbft.RuntimeConfig{
LastConfigBlock: testCase.lastConfigBlock,
LastBlock: testCase.lastBlock,
})
md := protoutil.MarshalOrPanic(&smartbftprotos.ViewMetadata{
LatestSequence: 1,
ViewId: 2,
})
proposal := assembler.AssembleProposal(md, [][]byte{nonConfigTx})
// Maybe mutate the BFT metadata
proposal.Metadata = testCase.bftMetadataMutator(proposal.Metadata)
// Unwrap the OrdererBlockMetadata
tuple := &smartbft.ByteBufferTuple{}
_ = tuple.FromBytes(proposal.Payload)
blockMD := &cb.BlockMetadata{}
assert.NoError(t, proto.Unmarshal(tuple.B, blockMD))
sigMD := &cb.Metadata{}
assert.NoError(t, proto.Unmarshal(blockMD.Metadata[cb.BlockMetadataIndex_SIGNATURES], sigMD))
ordererMetadataFromSignature := &cb.OrdererBlockMetadata{}
assert.NoError(t, proto.Unmarshal(sigMD.Value, ordererMetadataFromSignature))
// Mutate the OrdererBlockMetadata
testCase.ordererBlockMetadataMutator(ordererMetadataFromSignature)
// And fold it back into the block
sigMD.Value = protoutil.MarshalOrPanic(ordererMetadataFromSignature)
blockMD.Metadata[cb.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic(sigMD)
tuple.B = protoutil.MarshalOrPanic(blockMD)
proposal.Payload = tuple.ToBytes()
runtimeConfig = &atomic.Value{}
rtc.LastConfigBlock = lastConfigBlock
runtimeConfig.Store(rtc)
v := &smartbft.Verifier{
RuntimeConfig: runtimeConfig,
Logger: logger,
Ledger: ledger,
VerificationSequencer: sequencer,
AccessController: ac,
ConsenterVerifier: cv,
ReqInspector: reqInspector,
}
reqInfo, err := v.VerifyProposal(proposal)
if testCase.expectedErr == "" {
assert.NoError(t, err)
assert.NotNil(t, reqInfo)
assert.Len(t, reqInfo, 1)
assert.Equal(t, hashOfZero, reqInfo[0].ClientID)
assert.Equal(t, hashOfZero, reqInfo[0].ID)
return
}
assert.Error(t, err)
assert.Equal(t, testCase.expectedErr, strings.ReplaceAll(err.Error(), "\u00a0", " "))
assert.Nil(t, reqInfo)
})
}
}
func noopMutator(b []byte) []byte {
return b
}
func noopOrdererBlockMetadataMutator(_ *cb.OrdererBlockMetadata) {
}