github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/forks/forkchoice/forks_test.go (about) 1 package forkchoice 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/mock" 10 "github.com/stretchr/testify/require" 11 12 "github.com/koko1123/flow-go-1/consensus/hotstuff" 13 "github.com/koko1123/flow-go-1/consensus/hotstuff/forks" 14 "github.com/koko1123/flow-go-1/consensus/hotstuff/forks/finalizer" 15 "github.com/koko1123/flow-go-1/consensus/hotstuff/mocks" 16 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 17 "github.com/koko1123/flow-go-1/model/flow" 18 mockfinalizer "github.com/koko1123/flow-go-1/module/mock" 19 "github.com/onflow/flow-go/crypto/hash" 20 ) 21 22 // TestForks_Initialization tests that Forks correctly reports trusted Root 23 func TestForks_Initialization(t *testing.T) { 24 forks, _, _, root := initForks(t, 1) 25 26 assert.Equal(t, forks.FinalizedView(), uint64(1)) 27 assert.Equal(t, forks.FinalizedBlock(), root.Block) 28 29 assert.Equal(t, forks.GetBlocksForView(0), []*model.Block{}) 30 assert.Equal(t, forks.GetBlocksForView(1), []*model.Block{root.Block}) 31 assert.Equal(t, forks.GetBlocksForView(2), []*model.Block{}) 32 33 b, found := forks.GetBlock(root.Block.BlockID) 34 assert.True(t, found, "Missing trusted Root ") 35 assert.Equal(t, root.Block, b) 36 } 37 38 // TestForks_AddBlock verifies that Block can be added 39 func TestForks_AddBlock(t *testing.T) { 40 forks, _, notifier, root := initForks(t, 1) 41 42 block02 := makeBlock(2, root.QC, flow.ZeroID) 43 notifier.On("OnBlockIncorporated", block02).Return().Once() 44 err := forks.AddBlock(block02) 45 if err != nil { 46 assert.Fail(t, err.Error()) 47 } 48 notifier.AssertExpectations(t) 49 50 assert.Equal(t, forks.GetBlocksForView(2), []*model.Block{block02}) 51 b, found := forks.GetBlock(block02.BlockID) 52 assert.True(t, found) 53 assert.Equal(t, block02, b) 54 } 55 56 // TestForks_3ChainFinalization tests happy-path direct 3-chain finalization 57 func TestForks_3ChainFinalization(t *testing.T) { 58 forks, finCallback, notifier, root := initForks(t, 1) // includes genesis block (v1) 59 60 block2 := makeBlock(2, root.QC, flow.ZeroID) 61 notifier.On("OnBlockIncorporated", block2).Return().Once() 62 addBlock2Forks(t, block2, forks) 63 notifier.AssertExpectations(t) 64 65 block3 := makeBlock(3, qc(block2.View, block2.BlockID), flow.ZeroID) 66 notifier.On("OnBlockIncorporated", block3).Return().Once() 67 notifier.On("OnQcIncorporated", block3.QC).Return().Once() 68 addBlock2Forks(t, block3, forks) 69 notifier.AssertExpectations(t) 70 71 // creates direct 3-chain on genesis block (1), which is already finalized 72 block4 := makeBlock(4, qc(block3.View, block3.BlockID), flow.ZeroID) 73 notifier.On("OnBlockIncorporated", block4).Return().Once() 74 notifier.On("OnQcIncorporated", block4.QC).Return().Once() 75 addBlock2Forks(t, block4, forks) 76 notifier.AssertExpectations(t) 77 78 // creates direct 3-chain on block (2) => finalize (2) 79 block5 := makeBlock(5, qc(block4.View, block4.BlockID), flow.ZeroID) 80 notifier.On("OnBlockIncorporated", block5).Return().Once() 81 notifier.On("OnQcIncorporated", block5.QC).Return().Once() 82 notifier.On("OnFinalizedBlock", block2).Return().Once() 83 finCallback.On("MakeFinal", block2.BlockID).Return(nil).Once() 84 finCallback.On("MakeValid", mock.Anything).Return(nil) 85 addBlock2Forks(t, block5, forks) 86 notifier.AssertExpectations(t) 87 finCallback.AssertExpectations(t) 88 89 // creates direct 3-chain on block (3) => finalize (3) 90 block6 := makeBlock(6, qc(block5.View, block5.BlockID), flow.ZeroID) 91 notifier.On("OnBlockIncorporated", block6).Return().Once() 92 notifier.On("OnQcIncorporated", block6.QC).Return().Once() 93 notifier.On("OnFinalizedBlock", block3).Return().Once() 94 finCallback.On("MakeFinal", block3.BlockID).Return(nil).Once() 95 finCallback.On("MakeValid", mock.Anything).Return(nil) 96 addBlock2Forks(t, block6, forks) 97 notifier.AssertExpectations(t) 98 finCallback.AssertExpectations(t) 99 } 100 101 func addBlock2Forks(t *testing.T, block *model.Block, forks hotstuff.Forks) { 102 err := forks.AddBlock(block) 103 if err != nil { 104 assert.Fail(t, err.Error()) 105 } 106 verifyStored(t, block, forks) 107 } 108 109 // verifyStored verifies that block is stored in forks 110 func verifyStored(t *testing.T, block *model.Block, forks hotstuff.Forks) { 111 b, found := forks.GetBlock(block.BlockID) 112 assert.True(t, found) 113 assert.Equal(t, block, b) 114 115 found = false 116 siblings := forks.GetBlocksForView(block.View) 117 assert.True(t, len(siblings) > 0) 118 for _, b := range forks.GetBlocksForView(block.View) { 119 if b != block { 120 continue 121 } 122 if found { // we already found block in slice, i.e. this is a duplicate 123 assert.Fail(t, fmt.Sprintf("Duplicate block: %v", block.BlockID)) 124 } 125 found = true 126 } 127 assert.True(t, found, fmt.Sprintf("Did not find block: %v", block.BlockID)) 128 } 129 130 func initForks(t *testing.T, view uint64) (*forks.Forks, *mockfinalizer.Finalizer, *mocks.Consumer, *forks.BlockQC) { 131 notifier := &mocks.Consumer{} 132 finalizationCallback := &mockfinalizer.Finalizer{} 133 finalizationCallback.On("MakeValid", mock.Anything).Return(nil) 134 135 // construct Finalizer 136 root := makeRootBlock(t, view) 137 notifier.On("OnBlockIncorporated", root.Block).Return().Once() 138 fnlzr, err := finalizer.New(root, finalizationCallback, notifier) 139 require.NoError(t, err) 140 141 // construct ForkChoice 142 notifier.On("OnQcIncorporated", root.QC).Return().Once() 143 fc, err := NewNewestForkChoice(fnlzr, notifier) 144 require.NoError(t, err) 145 146 notifier.AssertExpectations(t) 147 return forks.New(fnlzr, fc), finalizationCallback, notifier, root 148 } 149 150 func makeRootBlock(t *testing.T, view uint64) *forks.BlockQC { 151 // construct Finalizer with Genesis Block 152 genesisBlock := makeBlock(view, nil, flow.ZeroID) 153 genesisQC := qc(view, genesisBlock.BlockID) 154 root := forks.BlockQC{Block: genesisBlock, QC: genesisQC} 155 return &root 156 } 157 158 func qc(view uint64, id flow.Identifier) *flow.QuorumCertificate { 159 return &flow.QuorumCertificate{View: view, BlockID: id} 160 } 161 162 func makeBlock(blockView uint64, blockQC *flow.QuorumCertificate, payloadHash flow.Identifier) *model.Block { 163 if blockQC == nil { 164 blockQC = qc(0, flow.Identifier{}) 165 } 166 id := computeID(blockView, blockQC, payloadHash) 167 return &model.Block{ 168 BlockID: id, 169 View: blockView, 170 QC: blockQC, 171 PayloadHash: payloadHash, 172 } 173 } 174 175 // computeID is an INCOMPLETE STUB needed so we can test Forks. 176 // When computing the Block's ID, this implementation only considers 177 // the fields used by Forks. 178 // TODO need full implementation 179 func computeID(view uint64, qc *flow.QuorumCertificate, payloadHash flow.Identifier) flow.Identifier { 180 id := make([]byte, 0) 181 182 viewBytes := make([]byte, 8) 183 binary.BigEndian.PutUint64(viewBytes, view) 184 id = append(id, viewBytes...) 185 186 qcView := make([]byte, 8) 187 binary.BigEndian.PutUint64(qcView, qc.View) 188 id = append(id, qcView...) 189 id = append(id, qc.BlockID[:]...) 190 191 id = append(id, payloadHash[:]...) 192 193 var h [hash.HashLenSHA3_256]byte 194 hash.ComputeSHA3_256(&h, id) 195 196 return flow.HashToID(h[:]) 197 }