github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/forks/forkchoice/newest_test.go (about) 1 package forkchoice 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 "github.com/stretchr/testify/mock" 8 "github.com/stretchr/testify/require" 9 10 hs "github.com/koko1123/flow-go-1/consensus/hotstuff" 11 "github.com/koko1123/flow-go-1/consensus/hotstuff/forks" 12 "github.com/koko1123/flow-go-1/consensus/hotstuff/forks/finalizer" 13 "github.com/koko1123/flow-go-1/consensus/hotstuff/mocks" 14 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 15 "github.com/koko1123/flow-go-1/model/flow" 16 mockfinalizer "github.com/koko1123/flow-go-1/module/mock" 17 ) 18 19 type ViewPair struct { 20 qcView uint64 21 blockView uint64 22 } 23 24 type Blocks struct { 25 blockMap map[uint64]*model.Block 26 blockList []*model.Block 27 } 28 29 type QCs struct { 30 qcMap map[uint64]*flow.QuorumCertificate 31 qcList []*flow.QuorumCertificate 32 } 33 34 // NOTATION: 35 // [a, b] is a block at view "b" with a QC with view "a", 36 // e.g., [1, 2] means a block at view "2" with an included QC for view "1" 37 38 // HAPPY PATH 39 // As the leader of 6: we receive [1, 2], [2, 3], [3, 4], [4, 5] and receive enough votes to build QC for block 5. 40 // the fork choice should return block and qc for 5 41 func TestHappyPath(t *testing.T) { 42 curView := uint64(6) 43 44 f, notifier, root := initNewestForkChoice(t, 1) // includes genesis block (v1) 45 46 p1 := ViewPair{1, 2} 47 p2 := ViewPair{2, 3} 48 p3 := ViewPair{3, 4} 49 p4 := ViewPair{4, 5} 50 51 blocks := generateBlocks(root.QC, p1, p2, p3, p4) 52 for _, block := range blocks.blockList { 53 err := f.AddBlock(block) 54 require.NoError(t, err) 55 } 56 57 preferedQC := makeQC(5, blocks.blockMap[5].BlockID) 58 err := f.AddQC(preferedQC) 59 require.NoError(t, err) 60 61 // the fork choice should return block and qc for view 5 62 choiceQC, choiceBlock, err := f.MakeForkChoice(curView) 63 require.NoError(t, err) 64 require.Equal(t, preferedQC, choiceQC) 65 require.Equal(t, blocks.blockMap[choiceQC.View], choiceBlock) 66 notifier.AssertCalled(t, "OnForkChoiceGenerated", curView, preferedQC) 67 } 68 69 // NOT ENOUGH VOTES TO BUILD QC FOR LATEST FORK: fork with newest QC 70 // As the leader of 6: we receive [1,2], [2,3], [3,4], [4,5] but we don't receive enough votes to build a qc for block 5. 71 // the fork choice should return block and qc for 4 72 func TestNoQcForLatestMainFork(t *testing.T) { 73 curView := uint64(6) 74 75 f, notifier, root := initNewestForkChoice(t, 1) // includes genesis block (v1) 76 77 p1 := ViewPair{1, 2} 78 p2 := ViewPair{2, 3} 79 p3 := ViewPair{3, 4} 80 p4 := ViewPair{4, 5} 81 82 blocks := generateBlocks(root.QC, p1, p2, p3, p4) 83 for _, block := range blocks.blockList { 84 err := f.AddBlock(block) 85 require.NoError(t, err) 86 } 87 88 // the fork choice should return block and qc for view 5 89 preferedQC := makeQC(4, blocks.blockMap[4].BlockID) 90 choiceQC, choiceBlock, err := f.MakeForkChoice(curView) 91 require.NoError(t, err) 92 require.Equal(t, preferedQC, choiceQC) 93 require.Equal(t, blocks.blockMap[preferedQC.View], choiceBlock) 94 notifier.AssertCalled(t, "OnForkChoiceGenerated", curView, preferedQC) 95 } 96 97 // NEWEST OVER LONGEST: HAPPY PATH: extend newest 98 // As the leader of 7: we receive [1,2], [2,3], [3,4], [4,5], [2,6], and receive enough votes to build QC for block 6. 99 // the fork choice should return block and qc for 6 100 func TestNewestOverLongestHappyPath(t *testing.T) { 101 curView := uint64(7) 102 103 f, notifier, root := initNewestForkChoice(t, 1) // includes genesis block (v1) 104 105 p1 := ViewPair{1, 2} 106 p2 := ViewPair{2, 3} 107 p3 := ViewPair{3, 4} 108 p4 := ViewPair{4, 5} 109 p5 := ViewPair{2, 6} 110 111 blocks := generateBlocks(root.QC, p1, p2, p3, p4, p5) 112 for _, block := range blocks.blockList { 113 err := f.AddBlock(block) 114 require.NoError(t, err) 115 } 116 117 preferedQC := makeQC(6, blocks.blockMap[6].BlockID) 118 err := f.AddQC(preferedQC) 119 require.NoError(t, err) 120 121 choiceQC, choiceBlock, err := f.MakeForkChoice(curView) 122 require.NoError(t, err) 123 require.Equal(t, preferedQC, choiceQC) 124 require.Equal(t, blocks.blockMap[preferedQC.View], choiceBlock) 125 notifier.AssertCalled(t, "OnForkChoiceGenerated", curView, preferedQC) 126 } 127 128 // NEWEST OVER LONGEST: NOT ENOUGH VOTES TO BUILD ON TOP OF NEWEST FORK: fork from newest 129 // As the leader of 7: we receive [1,2], [2,3], [3,4], [4,5], [2,6], but we don't receive enough votes to build a qc for block 6. 130 // the fork choice should return block and qc for 4 131 func TestNewestOverLongestForkFromNewest(t *testing.T) { 132 curView := uint64(7) 133 134 f, notifier, root := initNewestForkChoice(t, 1) // includes genesis block (v1) 135 136 p1 := ViewPair{1, 2} 137 p2 := ViewPair{2, 3} 138 p3 := ViewPair{3, 4} 139 p4 := ViewPair{4, 5} 140 p5 := ViewPair{2, 6} 141 142 blocks := generateBlocks(root.QC, p1, p2, p3, p4, p5) 143 for _, block := range blocks.blockList { 144 err := f.AddBlock(block) 145 require.NoError(t, err) 146 } 147 148 // the fork choice should return block and qc for view 4 149 preferedQC := makeQC(4, blocks.blockMap[4].BlockID) 150 notifier.On("OnForkChoiceGenerated", curView, preferedQC).Return().Once() 151 choiceQC, choiceBlock, err := f.MakeForkChoice(curView) 152 require.NoError(t, err) 153 require.Equal(t, preferedQC, choiceQC) 154 require.Equal(t, blocks.blockMap[preferedQC.View], choiceBlock) 155 notifier.AssertCalled(t, "OnForkChoiceGenerated", curView, preferedQC) 156 } 157 158 // FORK BELOW LOCKED: NOT ENOUGH VOTES TO BUILD ON TOP OF NEWEST FORK: fork from newest 159 // As the leader of 8: we receive [1,2], [2,3], [3,4], [4,5], [2,6], [6,7], but we don't receive enough votes to build a qc for block 7. 160 // the fork choice should return block and qc for 6 161 func TestForkBelowLockedForkFromNewest(t *testing.T) { 162 curView := uint64(8) 163 164 f, notifier, root := initNewestForkChoice(t, 1) // includes genesis block (v1) 165 166 p1 := ViewPair{1, 2} 167 p2 := ViewPair{2, 3} 168 p3 := ViewPair{3, 4} 169 p4 := ViewPair{4, 5} 170 p5 := ViewPair{2, 6} 171 p6 := ViewPair{6, 7} 172 173 blocks := generateBlocks(root.QC, p1, p2, p3, p4, p5, p6) 174 for _, block := range blocks.blockList { 175 err := f.AddBlock(block) 176 require.NoError(t, err) 177 } 178 179 // the fork choice should return block and qc for view 6 180 preferedQC := makeQC(6, blocks.blockMap[6].BlockID) 181 choiceQC, choiceBlock, err := f.MakeForkChoice(curView) 182 require.NoError(t, err) 183 require.Equal(t, preferedQC, choiceQC) 184 require.Equal(t, blocks.blockMap[choiceQC.View], choiceBlock) 185 notifier.AssertCalled(t, "OnForkChoiceGenerated", curView, preferedQC) 186 } 187 188 // FORK BELOW LOCKED: HAPPY PATH: extend newest 189 // As the leader of 8: we receive [1,2], [2,3], [3,4], [4,5], [2,6], [6,7], and receive enough votes to build QC for block 7. 190 // the fork choice should return block and qc for 7 191 func TestForkBelowLockedHappyPath(t *testing.T) { 192 curView := uint64(8) 193 194 f, notifier, root := initNewestForkChoice(t, 1) // includes genesis block (v1) 195 196 p1 := ViewPair{1, 2} 197 p2 := ViewPair{2, 3} 198 p3 := ViewPair{3, 4} 199 p4 := ViewPair{4, 5} 200 p5 := ViewPair{2, 6} 201 p6 := ViewPair{6, 7} 202 203 blocks := generateBlocks(root.QC, p1, p2, p3, p4, p5, p6) 204 for _, block := range blocks.blockList { 205 err := f.AddBlock(block) 206 require.NoError(t, err) 207 } 208 209 preferedQC := makeQC(7, blocks.blockMap[7].BlockID) 210 err := f.AddQC(preferedQC) 211 require.NoError(t, err) 212 213 // the fork choice should return block and qc for view 7 214 choiceQC, choiceBlock, err := f.MakeForkChoice(curView) 215 require.NoError(t, err) 216 require.Equal(t, preferedQC, choiceQC) 217 require.Equal(t, blocks.blockMap[choiceQC.View], choiceBlock) 218 notifier.AssertCalled(t, "OnForkChoiceGenerated", curView, preferedQC) 219 } 220 221 // Verifies notification callbacks 222 func TestOnQcIncorporated(t *testing.T) { 223 notifier := &mocks.Consumer{} 224 notifier.On("OnBlockIncorporated", mock.Anything).Return(nil) 225 226 finalizationCallback := &mockfinalizer.Finalizer{} 227 finalizationCallback.On("MakeFinal", mock.Anything).Return(nil) 228 finalizationCallback.On("MakeValid", mock.Anything).Return(nil) 229 230 // construct Finalizer 231 root := makeRootBlock(t, 1) 232 fnlzr, _ := finalizer.New(root, finalizationCallback, notifier) 233 234 // construct ForkChoice, it will trigger OnQcIncorporated 235 notifier.On("OnQcIncorporated", root.QC).Return(nil) 236 fc, _ := NewNewestForkChoice(fnlzr, notifier) 237 assert.NotNil(t, fc) 238 239 f := forks.New(fnlzr, fc) 240 241 p1 := ViewPair{1, 2} 242 p2 := ViewPair{2, 3} 243 244 blocks := generateBlocks(root.QC, p1, p2) 245 246 for _, block := range blocks.blockList { 247 // each call to AddBlock will trigger 'OnQcIncorporated' 248 notifier.On("OnQcIncorporated", block.QC).Return(nil) 249 err := f.AddBlock(block) 250 require.NoError(t, err) 251 } 252 253 preferedQC := makeQC(3, blocks.blockMap[3].BlockID) 254 // call to AddQC will trigger 'OnQcIncorporated' 255 notifier.On("OnQcIncorporated", preferedQC).Return(nil) 256 err := f.AddQC(preferedQC) 257 require.NoError(t, err) 258 notifier.AssertExpectations(t) 259 } 260 261 func generateBlocks(rootQC *flow.QuorumCertificate, viewPairs ...ViewPair) *Blocks { 262 blocks := &Blocks{ 263 blockMap: make(map[uint64]*model.Block), 264 } 265 qcs := &QCs{ 266 qcMap: make(map[uint64]*flow.QuorumCertificate), 267 } 268 var lastBlockView uint64 269 for _, viewPair := range viewPairs { 270 var qc *flow.QuorumCertificate 271 if viewPair.qcView == 1 { 272 qc = rootQC 273 } else { 274 existedQc, exists := qcs.qcMap[viewPair.qcView] 275 if !exists { 276 qc = makeQC(viewPair.qcView, blocks.blockMap[lastBlockView].BlockID) 277 } else { 278 qc = existedQc 279 } 280 } 281 qcs.AddQC(qc) 282 283 block := makeBlock(viewPair.blockView, qcs.qcMap[viewPair.qcView], flow.ZeroID) 284 lastBlockView = block.View 285 blocks.AddBlock(block) 286 } 287 288 return blocks 289 } 290 291 func initNewestForkChoice(t *testing.T, view uint64) (hs.Forks, *mocks.Consumer, *forks.BlockQC) { 292 notifier := &mocks.Consumer{} 293 notifier.On("OnBlockIncorporated", mock.Anything).Return(nil) 294 notifier.On("OnFinalizedBlock", mock.Anything).Return(nil) 295 notifier.On("OnForkChoiceGenerated", mock.Anything, mock.Anything).Return().Once() 296 297 finalizationCallback := &mockfinalizer.Finalizer{} 298 finalizationCallback.On("MakeFinal", mock.Anything).Return(nil) 299 finalizationCallback.On("MakeValid", mock.Anything).Return(nil) 300 301 // construct Finalizer 302 root := makeRootBlock(t, view) 303 fnlzr, _ := finalizer.New(root, finalizationCallback, notifier) 304 305 // construct ForkChoice 306 notifier.On("OnQcIncorporated", mock.Anything).Return(nil) 307 fc, _ := NewNewestForkChoice(fnlzr, notifier) 308 309 f := forks.New(fnlzr, fc) 310 311 return f, notifier, root 312 } 313 314 func makeQC(view uint64, blockID flow.Identifier) *flow.QuorumCertificate { 315 return &flow.QuorumCertificate{ 316 View: view, 317 BlockID: blockID, 318 } 319 } 320 321 func (b *Blocks) AddBlock(block *model.Block) { 322 b.blockMap[block.View] = block 323 b.blockList = append(b.blockList, block) 324 } 325 326 func (q *QCs) AddQC(qc *flow.QuorumCertificate) { 327 q.qcMap[qc.View] = qc 328 q.qcList = append(q.qcList, qc) 329 }