github.com/MetalBlockchain/metalgo@v1.11.9/snow/engine/snowman/bootstrap/storage_test.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package bootstrap 5 6 import ( 7 "bytes" 8 "context" 9 "testing" 10 11 "github.com/stretchr/testify/require" 12 13 "github.com/MetalBlockchain/metalgo/database" 14 "github.com/MetalBlockchain/metalgo/database/memdb" 15 "github.com/MetalBlockchain/metalgo/ids" 16 "github.com/MetalBlockchain/metalgo/snow/choices" 17 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman" 18 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman/snowmantest" 19 "github.com/MetalBlockchain/metalgo/snow/engine/common" 20 "github.com/MetalBlockchain/metalgo/snow/engine/snowman/block" 21 "github.com/MetalBlockchain/metalgo/snow/engine/snowman/bootstrap/interval" 22 "github.com/MetalBlockchain/metalgo/utils/logging" 23 "github.com/MetalBlockchain/metalgo/utils/set" 24 ) 25 26 var _ block.Parser = testParser(nil) 27 28 func TestGetMissingBlockIDs(t *testing.T) { 29 blocks := snowmantest.BuildChain(7) 30 parser := makeParser(blocks) 31 32 tests := []struct { 33 name string 34 blocks []snowman.Block 35 lastAcceptedHeight uint64 36 expected set.Set[ids.ID] 37 }{ 38 { 39 name: "initially empty", 40 blocks: nil, 41 lastAcceptedHeight: 0, 42 expected: nil, 43 }, 44 { 45 name: "wants one block", 46 blocks: []snowman.Block{blocks[4]}, 47 lastAcceptedHeight: 0, 48 expected: set.Of(blocks[3].ID()), 49 }, 50 { 51 name: "wants multiple blocks", 52 blocks: []snowman.Block{blocks[2], blocks[4]}, 53 lastAcceptedHeight: 0, 54 expected: set.Of(blocks[1].ID(), blocks[3].ID()), 55 }, 56 { 57 name: "doesn't want last accepted block", 58 blocks: []snowman.Block{blocks[1]}, 59 lastAcceptedHeight: 0, 60 expected: nil, 61 }, 62 { 63 name: "doesn't want known block", 64 blocks: []snowman.Block{blocks[2], blocks[3]}, 65 lastAcceptedHeight: 0, 66 expected: set.Of(blocks[1].ID()), 67 }, 68 { 69 name: "doesn't want already accepted block", 70 blocks: []snowman.Block{blocks[1]}, 71 lastAcceptedHeight: 4, 72 expected: nil, 73 }, 74 { 75 name: "doesn't underflow", 76 blocks: []snowman.Block{blocks[0]}, 77 lastAcceptedHeight: 0, 78 expected: nil, 79 }, 80 } 81 for _, test := range tests { 82 t.Run(test.name, func(t *testing.T) { 83 require := require.New(t) 84 85 db := memdb.New() 86 tree, err := interval.NewTree(db) 87 require.NoError(err) 88 for _, blk := range test.blocks { 89 _, err := interval.Add(db, tree, 0, blk.Height(), blk.Bytes()) 90 require.NoError(err) 91 } 92 93 missingBlockIDs, err := getMissingBlockIDs( 94 context.Background(), 95 db, 96 parser, 97 tree, 98 test.lastAcceptedHeight, 99 ) 100 require.NoError(err) 101 require.Equal(test.expected, missingBlockIDs) 102 }) 103 } 104 } 105 106 func TestProcess(t *testing.T) { 107 blocks := snowmantest.BuildChain(7) 108 109 tests := []struct { 110 name string 111 initialBlocks []snowman.Block 112 lastAcceptedHeight uint64 113 missingBlockIDs set.Set[ids.ID] 114 blk snowman.Block 115 ancestors map[ids.ID]snowman.Block 116 expectedParentID ids.ID 117 expectedShouldFetchParentID bool 118 expectedMissingBlockIDs set.Set[ids.ID] 119 expectedTrackedHeights []uint64 120 }{ 121 { 122 name: "add single block", 123 initialBlocks: nil, 124 lastAcceptedHeight: 0, 125 missingBlockIDs: set.Of(blocks[5].ID()), 126 blk: blocks[5], 127 ancestors: nil, 128 expectedParentID: blocks[4].ID(), 129 expectedShouldFetchParentID: true, 130 expectedMissingBlockIDs: set.Set[ids.ID]{}, 131 expectedTrackedHeights: []uint64{5}, 132 }, 133 { 134 name: "add multiple blocks", 135 initialBlocks: nil, 136 lastAcceptedHeight: 0, 137 missingBlockIDs: set.Of(blocks[5].ID()), 138 blk: blocks[5], 139 ancestors: map[ids.ID]snowman.Block{ 140 blocks[4].ID(): blocks[4], 141 }, 142 expectedParentID: blocks[3].ID(), 143 expectedShouldFetchParentID: true, 144 expectedMissingBlockIDs: set.Set[ids.ID]{}, 145 expectedTrackedHeights: []uint64{4, 5}, 146 }, 147 { 148 name: "ignore non-consecutive blocks", 149 initialBlocks: nil, 150 lastAcceptedHeight: 0, 151 missingBlockIDs: set.Of(blocks[3].ID(), blocks[5].ID()), 152 blk: blocks[5], 153 ancestors: map[ids.ID]snowman.Block{ 154 blocks[3].ID(): blocks[3], 155 }, 156 expectedParentID: blocks[4].ID(), 157 expectedShouldFetchParentID: true, 158 expectedMissingBlockIDs: set.Of(blocks[3].ID()), 159 expectedTrackedHeights: []uint64{5}, 160 }, 161 { 162 name: "do not request the last accepted block", 163 initialBlocks: nil, 164 lastAcceptedHeight: 2, 165 missingBlockIDs: set.Of(blocks[3].ID()), 166 blk: blocks[3], 167 ancestors: nil, 168 expectedParentID: ids.Empty, 169 expectedShouldFetchParentID: false, 170 expectedMissingBlockIDs: set.Set[ids.ID]{}, 171 expectedTrackedHeights: []uint64{3}, 172 }, 173 { 174 name: "do not request already known block", 175 initialBlocks: []snowman.Block{blocks[2]}, 176 lastAcceptedHeight: 0, 177 missingBlockIDs: set.Of(blocks[1].ID(), blocks[3].ID()), 178 blk: blocks[3], 179 ancestors: nil, 180 expectedParentID: ids.Empty, 181 expectedShouldFetchParentID: false, 182 expectedMissingBlockIDs: set.Of(blocks[1].ID()), 183 expectedTrackedHeights: []uint64{2, 3}, 184 }, 185 } 186 for _, test := range tests { 187 t.Run(test.name, func(t *testing.T) { 188 require := require.New(t) 189 190 db := memdb.New() 191 tree, err := interval.NewTree(db) 192 require.NoError(err) 193 for _, blk := range test.initialBlocks { 194 _, err := interval.Add(db, tree, 0, blk.Height(), blk.Bytes()) 195 require.NoError(err) 196 } 197 198 parentID, shouldFetchParentID, err := process( 199 db, 200 tree, 201 test.missingBlockIDs, 202 test.lastAcceptedHeight, 203 test.blk, 204 test.ancestors, 205 ) 206 require.NoError(err) 207 require.Equal(test.expectedShouldFetchParentID, shouldFetchParentID) 208 require.Equal(test.expectedParentID, parentID) 209 require.Equal(test.expectedMissingBlockIDs, test.missingBlockIDs) 210 211 require.Equal(uint64(len(test.expectedTrackedHeights)), tree.Len()) 212 for _, height := range test.expectedTrackedHeights { 213 require.True(tree.Contains(height)) 214 } 215 }) 216 } 217 } 218 219 func TestExecute(t *testing.T) { 220 const numBlocks = 7 221 222 unhalted := &common.Halter{} 223 halted := &common.Halter{} 224 halted.Halt(context.Background()) 225 226 tests := []struct { 227 name string 228 haltable common.Haltable 229 lastAcceptedHeight uint64 230 expectedProcessingHeights []uint64 231 expectedAcceptedHeights []uint64 232 }{ 233 { 234 name: "execute everything", 235 haltable: unhalted, 236 lastAcceptedHeight: 0, 237 expectedProcessingHeights: nil, 238 expectedAcceptedHeights: []uint64{0, 1, 2, 3, 4, 5, 6}, 239 }, 240 { 241 name: "do not execute blocks accepted by height", 242 haltable: unhalted, 243 lastAcceptedHeight: 3, 244 expectedProcessingHeights: []uint64{1, 2, 3}, 245 expectedAcceptedHeights: []uint64{0, 4, 5, 6}, 246 }, 247 { 248 name: "do not execute blocks when halted", 249 haltable: halted, 250 lastAcceptedHeight: 0, 251 expectedProcessingHeights: []uint64{1, 2, 3, 4, 5, 6}, 252 expectedAcceptedHeights: []uint64{0}, 253 }, 254 } 255 for _, test := range tests { 256 t.Run(test.name, func(t *testing.T) { 257 require := require.New(t) 258 259 db := memdb.New() 260 tree, err := interval.NewTree(db) 261 require.NoError(err) 262 263 blocks := snowmantest.BuildChain(numBlocks) 264 parser := makeParser(blocks) 265 for _, blk := range blocks { 266 _, err := interval.Add(db, tree, 0, blk.Height(), blk.Bytes()) 267 require.NoError(err) 268 } 269 270 require.NoError(execute( 271 context.Background(), 272 test.haltable, 273 logging.NoLog{}.Info, 274 db, 275 parser, 276 tree, 277 test.lastAcceptedHeight, 278 )) 279 for _, height := range test.expectedProcessingHeights { 280 require.Equal(choices.Processing, blocks[height].Status()) 281 } 282 for _, height := range test.expectedAcceptedHeights { 283 require.Equal(choices.Accepted, blocks[height].Status()) 284 } 285 286 if test.haltable.Halted() { 287 return 288 } 289 290 size, err := database.Count(db) 291 require.NoError(err) 292 require.Zero(size) 293 }) 294 } 295 } 296 297 type testParser func(context.Context, []byte) (snowman.Block, error) 298 299 func (f testParser) ParseBlock(ctx context.Context, bytes []byte) (snowman.Block, error) { 300 return f(ctx, bytes) 301 } 302 303 func makeParser(blocks []*snowmantest.Block) block.Parser { 304 return testParser(func(_ context.Context, b []byte) (snowman.Block, error) { 305 for _, block := range blocks { 306 if bytes.Equal(b, block.Bytes()) { 307 return block, nil 308 } 309 } 310 return nil, database.ErrNotFound 311 }) 312 }