337 lines
9.0 KiB
Go
337 lines
9.0 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package smartbft_test
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"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/common/flogging"
|
|
mocks2 "github.com/hyperledger/fabric/orderer/consensus/mocks"
|
|
"github.com/hyperledger/fabric/orderer/consensus/smartbft"
|
|
"github.com/hyperledger/fabric/orderer/consensus/smartbft/mocks"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func noopUpdateLastHash(_ *cb.Block) types.Reconfig { return types.Reconfig{} }
|
|
|
|
func TestSynchronizerSync(t *testing.T) {
|
|
blockBytes, err := ioutil.ReadFile("testdata/mychannel.block")
|
|
require.NoError(t, err)
|
|
|
|
goodConfigBlock := &cb.Block{}
|
|
require.NoError(t, proto.Unmarshal(blockBytes, goodConfigBlock))
|
|
|
|
b99 := makeBlockWithMetadata(99, 42, &smartbftprotos.ViewMetadata{ViewId: 1, LatestSequence: 12})
|
|
b100 := makeBlockWithMetadata(100, 42, &smartbftprotos.ViewMetadata{ViewId: 1, LatestSequence: 13})
|
|
b101 := makeConfigBlockWithMetadata(goodConfigBlock, 101, &smartbftprotos.ViewMetadata{ViewId: 2, LatestSequence: 1})
|
|
b102 := makeBlockWithMetadata(102, 101, &smartbftprotos.ViewMetadata{ViewId: 2, LatestSequence: 3})
|
|
|
|
blockNum2configSqn := map[uint64]uint64{
|
|
99: 7,
|
|
100: 7,
|
|
101: 8,
|
|
102: 8,
|
|
}
|
|
|
|
t.Run("no remotes", func(t *testing.T) {
|
|
bp := &mocks.FakeBlockPuller{}
|
|
fakeCS := &mocks2.FakeConsenterSupport{}
|
|
fakeCS.ChannelIDReturns("mychannel")
|
|
fakeCS.HeightReturns(100)
|
|
fakeCS.BlockReturns(b99)
|
|
fakeCS.SequenceReturns(blockNum2configSqn[99])
|
|
|
|
l := flogging.NewFabricLogger(zap.NewExample())
|
|
|
|
decision := &types.SyncResponse{
|
|
Latest: types.Decision{},
|
|
}
|
|
syn := &smartbft.Synchronizer{
|
|
LatestConfig: func() (types.Configuration, []uint64) {
|
|
return types.Configuration{}, nil
|
|
},
|
|
BlockToDecision: func(block *cb.Block) *types.Decision {
|
|
if block == b99 {
|
|
return &decision.Latest
|
|
}
|
|
return nil
|
|
},
|
|
Logger: l,
|
|
BlockPuller: bp,
|
|
ClusterSize: 4,
|
|
Support: fakeCS,
|
|
OnCommit: noopUpdateLastHash,
|
|
}
|
|
|
|
d := syn.Sync()
|
|
assert.Equal(t, *decision, d)
|
|
})
|
|
|
|
t.Run("all nodes present", func(t *testing.T) {
|
|
bp := &mocks.FakeBlockPuller{}
|
|
bp.HeightsByEndpointsReturns(
|
|
map[string]uint64{
|
|
"example.com:1": 100,
|
|
"example.com:2": 102,
|
|
"example.com:3": 103,
|
|
"example.com:4": 200, // byzantine, lying
|
|
},
|
|
nil,
|
|
)
|
|
bp.PullBlockReturnsOnCall(0, b100)
|
|
bp.PullBlockReturnsOnCall(1, b101)
|
|
bp.PullBlockReturnsOnCall(2, b102)
|
|
|
|
height := uint64(100)
|
|
ledger := map[uint64]*cb.Block{99: b99}
|
|
|
|
fakeCS := &mocks2.FakeConsenterSupport{}
|
|
fakeCS.ChannelIDReturns("mychannel")
|
|
fakeCS.HeightCalls(func() uint64 { return height })
|
|
fakeCS.SequenceCalls(func() uint64 { return blockNum2configSqn[height-1] })
|
|
fakeCS.WriteConfigBlockCalls(func(b *cb.Block, m []byte) {
|
|
ledger[height] = b
|
|
height++
|
|
})
|
|
fakeCS.WriteBlockCalls(func(b *cb.Block, m []byte) {
|
|
ledger[height] = b
|
|
height++
|
|
})
|
|
fakeCS.BlockCalls(func(sqn uint64) *cb.Block {
|
|
return ledger[sqn]
|
|
})
|
|
|
|
decision := &types.SyncResponse{
|
|
Latest: types.Decision{},
|
|
}
|
|
syn := &smartbft.Synchronizer{
|
|
BlockToDecision: func(block *cb.Block) *types.Decision {
|
|
if block == b102 {
|
|
return &decision.Latest
|
|
}
|
|
return nil
|
|
},
|
|
Logger: flogging.NewFabricLogger(zap.NewExample()),
|
|
BlockPuller: bp,
|
|
ClusterSize: 4,
|
|
Support: fakeCS,
|
|
OnCommit: noopUpdateLastHash,
|
|
}
|
|
|
|
d := syn.Sync()
|
|
assert.Equal(t, *decision, d)
|
|
})
|
|
|
|
t.Run("3/4 nodes present", func(t *testing.T) {
|
|
bp := &mocks.FakeBlockPuller{}
|
|
bp.HeightsByEndpointsReturns(
|
|
map[string]uint64{
|
|
"example.com:1": 100,
|
|
"example.com:2": 102,
|
|
"example.com:4": 200, // byzantine, lying
|
|
},
|
|
nil,
|
|
)
|
|
bp.PullBlockReturnsOnCall(0, b100)
|
|
bp.PullBlockReturnsOnCall(1, b101)
|
|
bp.PullBlockReturnsOnCall(2, b102)
|
|
|
|
height := uint64(100)
|
|
ledger := map[uint64]*cb.Block{99: b99}
|
|
|
|
fakeCS := &mocks2.FakeConsenterSupport{}
|
|
fakeCS.ChannelIDReturns("mychannel")
|
|
fakeCS.HeightCalls(func() uint64 { return height })
|
|
fakeCS.SequenceCalls(func() uint64 { return blockNum2configSqn[height-1] })
|
|
fakeCS.WriteConfigBlockCalls(func(b *cb.Block, m []byte) {
|
|
ledger[height] = b
|
|
height++
|
|
})
|
|
fakeCS.WriteBlockCalls(func(b *cb.Block, m []byte) {
|
|
ledger[height] = b
|
|
height++
|
|
})
|
|
fakeCS.BlockCalls(func(sqn uint64) *cb.Block {
|
|
return ledger[sqn]
|
|
})
|
|
|
|
decision := &types.SyncResponse{
|
|
Latest: types.Decision{},
|
|
}
|
|
syn := &smartbft.Synchronizer{
|
|
BlockToDecision: func(block *cb.Block) *types.Decision {
|
|
if block == b101 {
|
|
return &decision.Latest
|
|
}
|
|
return nil
|
|
},
|
|
Logger: flogging.NewFabricLogger(zap.NewExample()),
|
|
BlockPuller: bp,
|
|
ClusterSize: 4,
|
|
Support: fakeCS,
|
|
OnCommit: noopUpdateLastHash,
|
|
}
|
|
|
|
d := syn.Sync()
|
|
assert.Equal(t, *decision, d)
|
|
})
|
|
|
|
t.Run("2/4 nodes present", func(t *testing.T) {
|
|
bp := &mocks.FakeBlockPuller{}
|
|
bp.HeightsByEndpointsReturns(
|
|
map[string]uint64{
|
|
"example.com:1": 101,
|
|
"example.com:4": 200, // byzantine, lying
|
|
},
|
|
nil,
|
|
)
|
|
bp.PullBlockReturnsOnCall(0, b100)
|
|
bp.PullBlockReturnsOnCall(1, b101)
|
|
bp.PullBlockReturnsOnCall(2, b102)
|
|
|
|
height := uint64(100)
|
|
ledger := map[uint64]*cb.Block{99: b99}
|
|
|
|
fakeCS := &mocks2.FakeConsenterSupport{}
|
|
fakeCS.ChannelIDReturns("mychannel")
|
|
fakeCS.HeightCalls(func() uint64 { return height })
|
|
fakeCS.SequenceCalls(func() uint64 { return blockNum2configSqn[height-1] })
|
|
fakeCS.WriteConfigBlockCalls(func(b *cb.Block, m []byte) {
|
|
ledger[height] = b
|
|
height++
|
|
})
|
|
fakeCS.WriteBlockCalls(func(b *cb.Block, m []byte) {
|
|
ledger[height] = b
|
|
height++
|
|
})
|
|
fakeCS.BlockCalls(func(sqn uint64) *cb.Block {
|
|
return ledger[sqn]
|
|
})
|
|
|
|
decision := &types.SyncResponse{
|
|
Latest: types.Decision{},
|
|
}
|
|
syn := &smartbft.Synchronizer{
|
|
BlockToDecision: func(block *cb.Block) *types.Decision {
|
|
if block == b100 {
|
|
return &decision.Latest
|
|
}
|
|
return nil
|
|
},
|
|
Logger: flogging.NewFabricLogger(zap.NewExample()),
|
|
BlockPuller: bp,
|
|
ClusterSize: 4,
|
|
Support: fakeCS,
|
|
OnCommit: noopUpdateLastHash,
|
|
}
|
|
|
|
d := syn.Sync()
|
|
assert.Equal(t, *decision, d)
|
|
})
|
|
|
|
t.Run("1/4 nodes present", func(t *testing.T) {
|
|
bp := &mocks.FakeBlockPuller{}
|
|
bp.HeightsByEndpointsReturns(
|
|
map[string]uint64{
|
|
"example.com:1": 100,
|
|
},
|
|
nil,
|
|
)
|
|
bp.PullBlockReturnsOnCall(0, b100)
|
|
bp.PullBlockReturnsOnCall(1, b101)
|
|
bp.PullBlockReturnsOnCall(2, b102)
|
|
|
|
height := uint64(100)
|
|
ledger := map[uint64]*cb.Block{99: b99}
|
|
|
|
fakeCS := &mocks2.FakeConsenterSupport{}
|
|
fakeCS.ChannelIDReturns("mychannel")
|
|
fakeCS.HeightCalls(func() uint64 { return height })
|
|
fakeCS.SequenceCalls(func() uint64 { return blockNum2configSqn[height-1] })
|
|
fakeCS.WriteConfigBlockCalls(func(b *cb.Block, m []byte) {
|
|
ledger[height] = b
|
|
height++
|
|
})
|
|
fakeCS.WriteBlockCalls(func(b *cb.Block, m []byte) {
|
|
ledger[height] = b
|
|
height++
|
|
})
|
|
fakeCS.BlockCalls(func(sqn uint64) *cb.Block {
|
|
return ledger[sqn]
|
|
})
|
|
|
|
decision := &types.SyncResponse{
|
|
Latest: types.Decision{},
|
|
}
|
|
syn := &smartbft.Synchronizer{
|
|
LatestConfig: func() (types.Configuration, []uint64) {
|
|
return types.Configuration{}, nil
|
|
},
|
|
BlockToDecision: func(block *cb.Block) *types.Decision {
|
|
if block == b99 {
|
|
return &decision.Latest
|
|
}
|
|
return nil
|
|
},
|
|
Logger: flogging.NewFabricLogger(zap.NewExample()),
|
|
BlockPuller: bp,
|
|
ClusterSize: 4,
|
|
Support: fakeCS,
|
|
OnCommit: noopUpdateLastHash,
|
|
}
|
|
|
|
d := syn.Sync()
|
|
assert.Equal(t, *decision, d)
|
|
})
|
|
}
|
|
|
|
func makeBlockWithMetadata(sqnNum, lastConfigIndex uint64, viewMetadata *smartbftprotos.ViewMetadata) *cb.Block {
|
|
block := protoutil.NewBlock(sqnNum, nil)
|
|
block.Metadata.Metadata[cb.BlockMetadataIndex_LAST_CONFIG] = protoutil.MarshalOrPanic(
|
|
&cb.Metadata{
|
|
Value: protoutil.MarshalOrPanic(&cb.LastConfig{Index: lastConfigIndex}),
|
|
},
|
|
)
|
|
block.Metadata.Metadata[cb.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic(&cb.Metadata{
|
|
Value: protoutil.MarshalOrPanic(&cb.OrdererBlockMetadata{
|
|
ConsenterMetadata: protoutil.MarshalOrPanic(viewMetadata),
|
|
LastConfig: &cb.LastConfig{
|
|
Index: sqnNum,
|
|
},
|
|
}),
|
|
})
|
|
return block
|
|
}
|
|
|
|
func makeConfigBlockWithMetadata(configBlock *cb.Block, sqnNum uint64, viewMetadata *smartbftprotos.ViewMetadata) *cb.Block {
|
|
block := proto.Clone(configBlock).(*cb.Block)
|
|
block.Header.Number = sqnNum
|
|
|
|
block.Metadata.Metadata[cb.BlockMetadataIndex_LAST_CONFIG] = protoutil.MarshalOrPanic(
|
|
&cb.Metadata{
|
|
Value: protoutil.MarshalOrPanic(&cb.LastConfig{Index: sqnNum}),
|
|
},
|
|
)
|
|
block.Metadata.Metadata[cb.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic(&cb.Metadata{
|
|
Value: protoutil.MarshalOrPanic(&cb.OrdererBlockMetadata{
|
|
ConsenterMetadata: protoutil.MarshalOrPanic(viewMetadata),
|
|
LastConfig: &cb.LastConfig{
|
|
Index: sqnNum,
|
|
},
|
|
}),
|
|
})
|
|
return block
|
|
}
|