github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/protocol/badger/state_test.go (about) 1 package badger_test 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "testing" 8 9 "github.com/dgraph-io/badger/v2" 10 "github.com/stretchr/testify/assert" 11 testmock "github.com/stretchr/testify/mock" 12 "github.com/stretchr/testify/require" 13 14 "github.com/onflow/flow-go/model/flow" 15 "github.com/onflow/flow-go/module/metrics" 16 "github.com/onflow/flow-go/module/mock" 17 "github.com/onflow/flow-go/state/protocol" 18 bprotocol "github.com/onflow/flow-go/state/protocol/badger" 19 "github.com/onflow/flow-go/state/protocol/inmem" 20 "github.com/onflow/flow-go/state/protocol/util" 21 protoutil "github.com/onflow/flow-go/state/protocol/util" 22 storagebadger "github.com/onflow/flow-go/storage/badger" 23 storutil "github.com/onflow/flow-go/storage/util" 24 "github.com/onflow/flow-go/utils/unittest" 25 ) 26 27 // TestBootstrapAndOpen verifies after bootstrapping with a root snapshot 28 // we should be able to open it and got the same state. 29 func TestBootstrapAndOpen(t *testing.T) { 30 31 // create a state root and bootstrap the protocol state with it 32 participants := unittest.CompleteIdentitySet() 33 rootSnapshot := unittest.RootSnapshotFixture(participants, func(block *flow.Block) { 34 block.Header.ParentID = unittest.IdentifierFixture() 35 }) 36 37 protoutil.RunWithBootstrapState(t, rootSnapshot, func(db *badger.DB, _ *bprotocol.State) { 38 39 // expect the final view metric to be set to current epoch's final view 40 epoch := rootSnapshot.Epochs().Current() 41 finalView, err := epoch.FinalView() 42 require.NoError(t, err) 43 counter, err := epoch.Counter() 44 require.NoError(t, err) 45 phase, err := rootSnapshot.Phase() 46 require.NoError(t, err) 47 48 complianceMetrics := new(mock.ComplianceMetrics) 49 complianceMetrics.On("CurrentEpochCounter", counter).Once() 50 complianceMetrics.On("CurrentEpochPhase", phase).Once() 51 complianceMetrics.On("CurrentEpochFinalView", finalView).Once() 52 complianceMetrics.On("FinalizedHeight", testmock.Anything).Once() 53 complianceMetrics.On("SealedHeight", testmock.Anything).Once() 54 55 dkgPhase1FinalView, dkgPhase2FinalView, dkgPhase3FinalView, err := protocol.DKGPhaseViews(epoch) 56 require.NoError(t, err) 57 complianceMetrics.On("CurrentDKGPhase1FinalView", dkgPhase1FinalView).Once() 58 complianceMetrics.On("CurrentDKGPhase2FinalView", dkgPhase2FinalView).Once() 59 complianceMetrics.On("CurrentDKGPhase3FinalView", dkgPhase3FinalView).Once() 60 61 noopMetrics := new(metrics.NoopCollector) 62 all := storagebadger.InitAll(noopMetrics, db) 63 // protocol state has been bootstrapped, now open a protocol state with the database 64 state, err := bprotocol.OpenState( 65 complianceMetrics, 66 db, 67 all.Headers, 68 all.Seals, 69 all.Results, 70 all.Blocks, 71 all.QuorumCertificates, 72 all.Setups, 73 all.EpochCommits, 74 all.EpochProtocolState, 75 all.ProtocolKVStore, 76 all.VersionBeacons, 77 ) 78 require.NoError(t, err) 79 80 complianceMetrics.AssertExpectations(t) 81 82 unittest.AssertSnapshotsEqual(t, rootSnapshot, state.Final()) 83 84 vb, err := state.Final().VersionBeacon() 85 require.NoError(t, err) 86 require.Nil(t, vb) 87 }) 88 } 89 90 // TestBootstrapAndOpen_EpochCommitted verifies after bootstrapping with a 91 // root snapshot from EpochCommitted phase we should be able to open it and 92 // got the same state. 93 func TestBootstrapAndOpen_EpochCommitted(t *testing.T) { 94 unittest.SkipUnless(t, unittest.TEST_TODO, "such behavior is not supported yet since SealingSegment should"+ 95 "contain blocks with the same protocol state ID. For this test it means that blocks needs to be selected"+ 96 "from the same epoch phase and cannot cross epoch boundaries") 97 // create a state root and bootstrap the protocol state with it 98 participants := unittest.CompleteIdentitySet() 99 rootSnapshot := unittest.RootSnapshotFixture(participants, func(block *flow.Block) { 100 block.Header.ParentID = unittest.IdentifierFixture() 101 }) 102 rootBlock, err := rootSnapshot.Head() 103 require.NoError(t, err) 104 105 // build an epoch on the root state and return a snapshot from the committed phase 106 committedPhaseSnapshot := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState, mutableState protocol.MutableProtocolState) protocol.Snapshot { 107 unittest.NewEpochBuilder(t, mutableState, state).BuildEpoch().CompleteEpoch() 108 109 // find the point where we transition to the epoch committed phase 110 for height := rootBlock.Height + 1; ; height++ { 111 phase, err := state.AtHeight(height).Phase() 112 require.NoError(t, err) 113 if phase == flow.EpochPhaseCommitted { 114 return state.AtHeight(height) 115 } 116 } 117 }) 118 119 protoutil.RunWithBootstrapState(t, committedPhaseSnapshot, func(db *badger.DB, _ *bprotocol.State) { 120 121 complianceMetrics := new(mock.ComplianceMetrics) 122 123 // expect counter to be set to current epochs counter 124 counter, err := committedPhaseSnapshot.Epochs().Current().Counter() 125 require.NoError(t, err) 126 complianceMetrics.On("CurrentEpochCounter", counter).Once() 127 128 // expect epoch phase to be set to current phase 129 phase, err := committedPhaseSnapshot.Phase() 130 require.NoError(t, err) 131 complianceMetrics.On("CurrentEpochPhase", phase).Once() 132 133 currentEpochFinalView, err := committedPhaseSnapshot.Epochs().Current().FinalView() 134 require.NoError(t, err) 135 complianceMetrics.On("CurrentEpochFinalView", currentEpochFinalView).Once() 136 137 dkgPhase1FinalView, dkgPhase2FinalView, dkgPhase3FinalView, err := protocol.DKGPhaseViews(committedPhaseSnapshot.Epochs().Current()) 138 require.NoError(t, err) 139 complianceMetrics.On("CurrentDKGPhase1FinalView", dkgPhase1FinalView).Once() 140 complianceMetrics.On("CurrentDKGPhase2FinalView", dkgPhase2FinalView).Once() 141 complianceMetrics.On("CurrentDKGPhase3FinalView", dkgPhase3FinalView).Once() 142 complianceMetrics.On("FinalizedHeight", testmock.Anything).Once() 143 complianceMetrics.On("SealedHeight", testmock.Anything).Once() 144 145 noopMetrics := new(metrics.NoopCollector) 146 all := storagebadger.InitAll(noopMetrics, db) 147 state, err := bprotocol.OpenState( 148 complianceMetrics, 149 db, 150 all.Headers, 151 all.Seals, 152 all.Results, 153 all.Blocks, 154 all.QuorumCertificates, 155 all.Setups, 156 all.EpochCommits, 157 all.EpochProtocolState, 158 all.ProtocolKVStore, 159 all.VersionBeacons, 160 ) 161 require.NoError(t, err) 162 163 // assert update final view was called 164 complianceMetrics.AssertExpectations(t) 165 166 unittest.AssertSnapshotsEqual(t, committedPhaseSnapshot, state.Final()) 167 }) 168 } 169 170 // TestBootstrap_EpochHeightBoundaries tests that epoch height indexes are indexed 171 // when they are available in the input snapshot. 172 // 173 // DIAGRAM LEGEND: 174 // 175 // < = low endpoint of a sealing segment 176 // > = high endpoint of a sealing segment 177 // x = root sealing segment 178 // | = epoch boundary 179 func TestBootstrap_EpochHeightBoundaries(t *testing.T) { 180 t.Parallel() 181 // start with a regular post-spork root snapshot 182 rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) 183 epoch1FirstHeight := rootSnapshot.Encodable().Head.Height 184 185 // For the spork root snapshot, only the first height of the root epoch should be indexed. 186 // [x] 187 t.Run("spork root snapshot", func(t *testing.T) { 188 util.RunWithFollowerProtocolState(t, rootSnapshot, func(db *badger.DB, state *bprotocol.FollowerState) { 189 // first height of started current epoch should be known 190 firstHeight, err := state.Final().Epochs().Current().FirstHeight() 191 require.NoError(t, err) 192 assert.Equal(t, epoch1FirstHeight, firstHeight) 193 // final height of not completed current epoch should be unknown 194 _, err = state.Final().Epochs().Current().FinalHeight() 195 assert.ErrorIs(t, err, protocol.ErrUnknownEpochBoundary) 196 }) 197 }) 198 199 // In this test we construct a snapshot where the sealing segment is entirely 200 // within a particular epoch (does not cross any boundary). In this case, 201 // no boundaries should be queriable in the API. 202 // [---<--->--] 203 t.Run("snapshot excludes start boundary", func(t *testing.T) { 204 var epochHeights *unittest.EpochHeights 205 after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState, mutableState protocol.MutableProtocolState) protocol.Snapshot { 206 builder := unittest.NewEpochBuilder(t, mutableState, state) 207 builder.BuildEpoch(). 208 AddBlocksWithSeals(flow.DefaultTransactionExpiry, 1). // ensure sealing segment excludes start boundary 209 CompleteEpoch() // building epoch 1 (prepare epoch 2) 210 var ok bool 211 epochHeights, ok = builder.EpochHeights(1) 212 require.True(t, ok) 213 // return a snapshot with reference block in the Committed phase of Epoch 1 214 return state.AtHeight(epochHeights.CommittedFinal) 215 }) 216 217 bootstrap(t, after, func(state *bprotocol.State, err error) { 218 require.NoError(t, err) 219 // first height of started current epoch should be unknown 220 _, err = state.Final().Epochs().Current().FirstHeight() 221 assert.ErrorIs(t, err, protocol.ErrUnknownEpochBoundary) 222 // final height of not completed current epoch should be unknown 223 _, err = state.Final().Epochs().Current().FinalHeight() 224 assert.ErrorIs(t, err, protocol.ErrUnknownEpochBoundary) 225 // first and final height of not started next epoch should be unknown 226 _, err = state.Final().Epochs().Next().FirstHeight() 227 assert.ErrorIs(t, err, protocol.ErrUnknownEpochBoundary) 228 _, err = state.Final().Epochs().Next().FinalHeight() 229 assert.ErrorIs(t, err, protocol.ErrUnknownEpochBoundary) 230 // first and final height of nonexistent previous epoch should be unknown 231 _, err = state.Final().Epochs().Previous().FirstHeight() 232 assert.ErrorIs(t, err, protocol.ErrNoPreviousEpoch) 233 _, err = state.Final().Epochs().Previous().FinalHeight() 234 assert.ErrorIs(t, err, protocol.ErrNoPreviousEpoch) 235 }) 236 }) 237 238 // In this test we construct a root snapshot such that the Previous epoch w.r.t 239 // the snapshot reference block has only the end boundary included in the 240 // sealing segment. Therefore, only FinalBlock should be queriable in the API. 241 // [---<---|--->---] 242 t.Run("root snapshot includes previous epoch end boundary only", func(t *testing.T) { 243 var epoch2Heights *unittest.EpochHeights 244 after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState, mutableState protocol.MutableProtocolState) protocol.Snapshot { 245 builder := unittest.NewEpochBuilder(t, mutableState, state) 246 builder. 247 BuildEpoch(). 248 AddBlocksWithSeals(flow.DefaultTransactionExpiry, 1). // ensure sealing segment excludes start boundary 249 CompleteEpoch(). // building epoch 1 (prepare epoch 2) 250 BuildEpoch() // building epoch 2 (prepare epoch 3) 251 var ok bool 252 epoch2Heights, ok = builder.EpochHeights(2) 253 require.True(t, ok) 254 255 // return snapshot from Committed phase of epoch 2 256 return state.AtHeight(epoch2Heights.Committed) 257 }) 258 259 bootstrap(t, after, func(state *bprotocol.State, err error) { 260 require.NoError(t, err) 261 // first height of started current epoch should be known 262 firstHeight, err := state.Final().Epochs().Current().FirstHeight() 263 assert.Equal(t, epoch2Heights.FirstHeight(), firstHeight) 264 require.NoError(t, err) 265 // final height of not completed current epoch should be unknown 266 _, err = state.Final().Epochs().Current().FinalHeight() 267 assert.ErrorIs(t, err, protocol.ErrUnknownEpochBoundary) 268 // first height of previous epoch should be unknown 269 _, err = state.Final().Epochs().Previous().FirstHeight() 270 assert.ErrorIs(t, err, protocol.ErrUnknownEpochBoundary) 271 // final height of previous epoch should be known 272 finalHeight, err := state.Final().Epochs().Previous().FinalHeight() 273 require.NoError(t, err) 274 assert.Equal(t, finalHeight, epoch2Heights.FirstHeight()-1) 275 }) 276 }) 277 278 // In this test we construct a root snapshot such that the Previous epoch w.r.t 279 // the snapshot reference block has both start and end boundaries included in the 280 // sealing segment. Therefore, both boundaries should be queryable in the API. 281 // [---<---|---|--->---] 282 t.Run("root snapshot includes previous epoch start and end boundary", func(t *testing.T) { 283 var epoch3Heights *unittest.EpochHeights 284 var epoch2Heights *unittest.EpochHeights 285 after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState, mutableState protocol.MutableProtocolState) protocol.Snapshot { 286 builder := unittest.NewEpochBuilder(t, mutableState, state) 287 builder. 288 BuildEpoch().CompleteEpoch(). // building epoch 1 (prepare epoch 2) 289 BuildEpoch().CompleteEpoch(). // building epoch 2 (prepare epoch 3) 290 BuildEpoch() // building epoch 3 (prepare epoch 4) 291 var ok bool 292 epoch3Heights, ok = builder.EpochHeights(3) 293 require.True(t, ok) 294 epoch2Heights, ok = builder.EpochHeights(2) 295 require.True(t, ok) 296 297 // return snapshot from Committed phase of epoch 3 298 return state.AtHeight(epoch3Heights.Committed) 299 }) 300 301 bootstrap(t, after, func(state *bprotocol.State, err error) { 302 require.NoError(t, err) 303 // first height of started current epoch should be known 304 firstHeight, err := state.Final().Epochs().Current().FirstHeight() 305 assert.Equal(t, epoch3Heights.FirstHeight(), firstHeight) 306 require.NoError(t, err) 307 // final height of not completed current epoch should be unknown 308 _, err = state.Final().Epochs().Current().FinalHeight() 309 assert.ErrorIs(t, err, protocol.ErrUnknownEpochBoundary) 310 // first height of previous epoch should be known 311 firstHeight, err = state.Final().Epochs().Previous().FirstHeight() 312 require.NoError(t, err) 313 assert.Equal(t, epoch2Heights.FirstHeight(), firstHeight) 314 // final height of completed previous epoch should be known 315 finalHeight, err := state.Final().Epochs().Previous().FinalHeight() 316 require.NoError(t, err) 317 assert.Equal(t, finalHeight, epoch2Heights.FinalHeight()) 318 }) 319 }) 320 } 321 322 // TestBootstrapNonRoot tests bootstrapping the protocol state from arbitrary states. 323 // 324 // NOTE: for all these cases, we build a final child block (CHILD). This is 325 // needed otherwise the parent block would not have a valid QC, since the QC 326 // is stored in the child. 327 func TestBootstrapNonRoot(t *testing.T) { 328 t.Parallel() 329 // start with a regular post-spork root snapshot 330 participants := unittest.CompleteIdentitySet() 331 rootSnapshot := unittest.RootSnapshotFixture(participants) 332 rootProtocolStateID := getRootProtocolStateID(t, rootSnapshot) 333 rootBlock, err := rootSnapshot.Head() 334 require.NoError(t, err) 335 336 // should be able to bootstrap from snapshot after sealing a non-root block 337 // ROOT <- B1 <- B2(R1) <- B3(S1) <- CHILD 338 t.Run("with sealed block", func(t *testing.T) { 339 after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState, mutableState protocol.MutableProtocolState) protocol.Snapshot { 340 block1 := unittest.BlockWithParentFixture(rootBlock) 341 block1.SetPayload(unittest.PayloadFixture(unittest.WithProtocolStateID(rootProtocolStateID))) 342 buildFinalizedBlock(t, state, block1) 343 344 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 345 block2 := unittest.BlockWithParentFixture(block1.Header) 346 block2.SetPayload(unittest.PayloadFixture( 347 unittest.WithReceipts(receipt1), 348 unittest.WithProtocolStateID(rootProtocolStateID))) 349 buildFinalizedBlock(t, state, block2) 350 351 seals := []*flow.Seal{seal1} 352 block3 := unittest.BlockWithParentFixture(block2.Header) 353 block3.SetPayload(flow.Payload{ 354 Seals: seals, 355 ProtocolStateID: calculateExpectedStateId(t, mutableState)(block3.Header, seals), 356 }) 357 buildFinalizedBlock(t, state, block3) 358 359 child := unittest.BlockWithParentProtocolState(block3) 360 buildBlock(t, state, child) 361 362 return state.AtBlockID(block3.ID()) 363 }) 364 365 bootstrap(t, after, func(state *bprotocol.State, err error) { 366 require.NoError(t, err) 367 unittest.AssertSnapshotsEqual(t, after, state.Final()) 368 segment, err := state.Final().SealingSegment() 369 require.NoError(t, err) 370 for _, block := range segment.Blocks { 371 snapshot := state.AtBlockID(block.ID()) 372 // should be able to read all QCs 373 _, err := snapshot.QuorumCertificate() 374 require.NoError(t, err) 375 _, err = snapshot.RandomSource() 376 require.NoError(t, err) 377 } 378 }) 379 }) 380 381 t.Run("with setup next epoch", func(t *testing.T) { 382 after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState, mutableState protocol.MutableProtocolState) protocol.Snapshot { 383 unittest.NewEpochBuilder(t, mutableState, state).BuildEpoch() 384 385 // find the point where we transition to the epoch setup phase 386 for height := rootBlock.Height + 1; ; height++ { 387 phase, err := state.AtHeight(height).Phase() 388 require.NoError(t, err) 389 if phase == flow.EpochPhaseSetup { 390 return state.AtHeight(height) 391 } 392 } 393 }) 394 395 bootstrap(t, after, func(state *bprotocol.State, err error) { 396 require.NoError(t, err) 397 unittest.AssertSnapshotsEqual(t, after, state.Final()) 398 399 segment, err := state.Final().SealingSegment() 400 require.NoError(t, err) 401 assert.GreaterOrEqual(t, len(segment.ProtocolStateEntries), 2, "should have >2 distinct protocol state entries") 402 for _, block := range segment.Blocks { 403 snapshot := state.AtBlockID(block.ID()) 404 // should be able to read all protocol state entries 405 protocolStateEntry, err := snapshot.ProtocolState() 406 require.NoError(t, err) 407 assert.Equal(t, block.Payload.ProtocolStateID, protocolStateEntry.ID()) 408 } 409 }) 410 }) 411 412 t.Run("with committed next epoch", func(t *testing.T) { 413 after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState, mutableState protocol.MutableProtocolState) protocol.Snapshot { 414 unittest.NewEpochBuilder(t, mutableState, state).BuildEpoch().CompleteEpoch() 415 416 // find the point where we transition to the epoch committed phase 417 for height := rootBlock.Height + 1; ; height++ { 418 phase, err := state.AtHeight(height).Phase() 419 require.NoError(t, err) 420 if phase == flow.EpochPhaseCommitted { 421 return state.AtHeight(height) 422 } 423 } 424 }) 425 426 bootstrap(t, after, func(state *bprotocol.State, err error) { 427 require.NoError(t, err) 428 unittest.AssertSnapshotsEqual(t, after, state.Final()) 429 430 segment, err := state.Final().SealingSegment() 431 require.NoError(t, err) 432 assert.GreaterOrEqual(t, len(segment.ProtocolStateEntries), 2, "should have >2 distinct protocol state entries") 433 for _, block := range segment.Blocks { 434 snapshot := state.AtBlockID(block.ID()) 435 // should be able to read all protocol state entries 436 protocolStateEntry, err := snapshot.ProtocolState() 437 require.NoError(t, err) 438 assert.Equal(t, block.Payload.ProtocolStateID, protocolStateEntry.ID()) 439 } 440 }) 441 }) 442 443 t.Run("with previous and next epoch", func(t *testing.T) { 444 after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState, mutableState protocol.MutableProtocolState) protocol.Snapshot { 445 unittest.NewEpochBuilder(t, mutableState, state). 446 BuildEpoch().CompleteEpoch(). // build epoch 2 447 BuildEpoch() // build epoch 3 448 449 // find a snapshot from epoch setup phase in epoch 2 450 epoch1Counter, err := rootSnapshot.Epochs().Current().Counter() 451 require.NoError(t, err) 452 for height := rootBlock.Height + 1; ; height++ { 453 snap := state.AtHeight(height) 454 counter, err := snap.Epochs().Current().Counter() 455 require.NoError(t, err) 456 phase, err := snap.Phase() 457 require.NoError(t, err) 458 if phase == flow.EpochPhaseSetup && counter == epoch1Counter+1 { 459 return snap 460 } 461 } 462 }) 463 464 bootstrap(t, after, func(state *bprotocol.State, err error) { 465 require.NoError(t, err) 466 unittest.AssertSnapshotsEqual(t, after, state.Final()) 467 468 segment, err := state.Final().SealingSegment() 469 require.NoError(t, err) 470 assert.GreaterOrEqual(t, len(segment.ProtocolStateEntries), 2, "should have >2 distinct protocol state entries") 471 for _, block := range segment.Blocks { 472 snapshot := state.AtBlockID(block.ID()) 473 // should be able to read all protocol state entries 474 protocolStateEntry, err := snapshot.ProtocolState() 475 require.NoError(t, err) 476 assert.Equal(t, block.Payload.ProtocolStateID, protocolStateEntry.ID()) 477 } 478 }) 479 }) 480 } 481 482 func TestBootstrap_InvalidIdentities(t *testing.T) { 483 t.Run("duplicate node ID", func(t *testing.T) { 484 participants := unittest.CompleteIdentitySet() 485 dupeIDIdentity := unittest.IdentityFixture(unittest.WithNodeID(participants[0].NodeID)) 486 participants = append(participants, dupeIDIdentity) 487 488 root := unittest.RootSnapshotFixture(participants) 489 bootstrap(t, root, func(state *bprotocol.State, err error) { 490 assert.Error(t, err) 491 }) 492 }) 493 494 t.Run("zero weight", func(t *testing.T) { 495 zeroWeightIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleVerification), unittest.WithInitialWeight(0)) 496 participants := unittest.CompleteIdentitySet(zeroWeightIdentity) 497 root := unittest.RootSnapshotFixture(participants) 498 bootstrap(t, root, func(state *bprotocol.State, err error) { 499 assert.Error(t, err) 500 }) 501 }) 502 503 t.Run("missing role", func(t *testing.T) { 504 requiredRoles := []flow.Role{ 505 flow.RoleConsensus, 506 flow.RoleExecution, 507 flow.RoleVerification, 508 } 509 510 for _, role := range requiredRoles { 511 t.Run(fmt.Sprintf("no %s nodes", role), func(t *testing.T) { 512 participants := unittest.IdentityListFixture(5, unittest.WithAllRolesExcept(role)) 513 root := unittest.RootSnapshotFixture(participants) 514 bootstrap(t, root, func(state *bprotocol.State, err error) { 515 assert.Error(t, err) 516 }) 517 }) 518 } 519 }) 520 521 t.Run("duplicate address", func(t *testing.T) { 522 participants := unittest.CompleteIdentitySet() 523 dupeAddressIdentity := unittest.IdentityFixture(unittest.WithAddress(participants[0].Address)) 524 participants = append(participants, dupeAddressIdentity) 525 526 root := unittest.RootSnapshotFixture(participants) 527 bootstrap(t, root, func(state *bprotocol.State, err error) { 528 assert.Error(t, err) 529 }) 530 }) 531 532 t.Run("non-canonical ordering", func(t *testing.T) { 533 participants := unittest.IdentityListFixture(20, unittest.WithAllRoles()) 534 // randomly shuffle the identities so they are not canonically ordered 535 unorderedParticipants, err := participants.ToSkeleton().Shuffle() 536 require.NoError(t, err) 537 538 root := unittest.RootSnapshotFixture(participants) 539 encodable := root.Encodable() 540 541 // modify EpochSetup participants, making them unordered 542 latestProtocolStateEntry := encodable.SealingSegment.LatestProtocolStateEntry() 543 currentEpochSetup := latestProtocolStateEntry.EpochEntry.CurrentEpochSetup 544 currentEpochSetup.Participants = unorderedParticipants 545 currentEpochSetup.Participants = unorderedParticipants 546 latestProtocolStateEntry.EpochEntry.CurrentEpoch.SetupID = currentEpochSetup.ID() 547 548 root = inmem.SnapshotFromEncodable(encodable) 549 bootstrap(t, root, func(state *bprotocol.State, err error) { 550 assert.Error(t, err) 551 }) 552 }) 553 } 554 555 func TestBootstrap_DisconnectedSealingSegment(t *testing.T) { 556 rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) 557 // convert to encodable to easily modify snapshot 558 encodable := rootSnapshot.Encodable() 559 // add an un-connected tail block to the sealing segment 560 tail := unittest.BlockFixture() 561 encodable.SealingSegment.Blocks = append([]*flow.Block{&tail}, encodable.SealingSegment.Blocks...) 562 rootSnapshot = inmem.SnapshotFromEncodable(encodable) 563 564 bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { 565 assert.Error(t, err) 566 }) 567 } 568 569 func TestBootstrap_SealingSegmentMissingSeal(t *testing.T) { 570 rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) 571 // convert to encodable to easily modify snapshot 572 encodable := rootSnapshot.Encodable() 573 // we are missing the required first seal 574 encodable.SealingSegment.FirstSeal = nil 575 rootSnapshot = inmem.SnapshotFromEncodable(encodable) 576 577 bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { 578 assert.Error(t, err) 579 }) 580 } 581 582 func TestBootstrap_SealingSegmentMissingResult(t *testing.T) { 583 rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) 584 // convert to encodable to easily modify snapshot 585 encodable := rootSnapshot.Encodable() 586 // we are missing the result referenced by the root seal 587 encodable.SealingSegment.ExecutionResults = nil 588 rootSnapshot = inmem.SnapshotFromEncodable(encodable) 589 590 bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { 591 assert.Error(t, err) 592 }) 593 } 594 595 func TestBootstrap_InvalidQuorumCertificate(t *testing.T) { 596 rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) 597 // convert to encodable to easily modify snapshot 598 encodable := rootSnapshot.Encodable() 599 encodable.QuorumCertificate.BlockID = unittest.IdentifierFixture() 600 rootSnapshot = inmem.SnapshotFromEncodable(encodable) 601 602 bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { 603 assert.Error(t, err) 604 }) 605 } 606 607 func TestBootstrap_SealMismatch(t *testing.T) { 608 t.Run("seal doesn't match tail block", func(t *testing.T) { 609 rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) 610 // convert to encodable to easily modify snapshot 611 encodable := rootSnapshot.Encodable() 612 encodable.LatestSeal.BlockID = unittest.IdentifierFixture() 613 614 bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { 615 assert.Error(t, err) 616 }) 617 }) 618 619 t.Run("result doesn't match tail block", func(t *testing.T) { 620 rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) 621 // convert to encodable to easily modify snapshot 622 encodable := rootSnapshot.Encodable() 623 encodable.LatestResult.BlockID = unittest.IdentifierFixture() 624 625 bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { 626 assert.Error(t, err) 627 }) 628 }) 629 630 t.Run("seal doesn't match result", func(t *testing.T) { 631 rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) 632 // convert to encodable to easily modify snapshot 633 encodable := rootSnapshot.Encodable() 634 encodable.LatestSeal.ResultID = unittest.IdentifierFixture() 635 636 bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { 637 assert.Error(t, err) 638 }) 639 }) 640 } 641 642 // bootstraps protocol state with the given snapshot and invokes the callback 643 // with the result of the constructor 644 func bootstrap(t *testing.T, rootSnapshot protocol.Snapshot, f func(*bprotocol.State, error)) { 645 metrics := metrics.NewNoopCollector() 646 dir := unittest.TempDir(t) 647 defer os.RemoveAll(dir) 648 db := unittest.BadgerDB(t, dir) 649 defer db.Close() 650 all := storutil.StorageLayer(t, db) 651 state, err := bprotocol.Bootstrap( 652 metrics, 653 db, 654 all.Headers, 655 all.Seals, 656 all.Results, 657 all.Blocks, 658 all.QuorumCertificates, 659 all.Setups, 660 all.EpochCommits, 661 all.EpochProtocolState, 662 all.ProtocolKVStore, 663 all.VersionBeacons, 664 rootSnapshot, 665 ) 666 f(state, err) 667 } 668 669 // snapshotAfter bootstraps the protocol state from the root snapshot, applies 670 // the state-changing function f, clears the on-disk state, and returns a 671 // memory-backed snapshot corresponding to that returned by f. 672 // 673 // This is used for generating valid snapshots to use when testing bootstrapping 674 // from non-root states. 675 func snapshotAfter(t *testing.T, rootSnapshot protocol.Snapshot, f func(*bprotocol.FollowerState, protocol.MutableProtocolState) protocol.Snapshot) protocol.Snapshot { 676 var after protocol.Snapshot 677 protoutil.RunWithFullProtocolStateAndMutator(t, rootSnapshot, func(_ *badger.DB, state *bprotocol.ParticipantState, mutableState protocol.MutableProtocolState) { 678 snap := f(state.FollowerState, mutableState) 679 var err error 680 after, err = inmem.FromSnapshot(snap) 681 require.NoError(t, err) 682 }) 683 return after 684 } 685 686 // buildBlock extends the protocol state by the given block 687 func buildBlock(t *testing.T, state protocol.FollowerState, block *flow.Block) { 688 require.NoError(t, state.ExtendCertified(context.Background(), block, unittest.CertifyBlock(block.Header))) 689 } 690 691 // buildFinalizedBlock extends the protocol state by the given block and marks the block as finalized 692 func buildFinalizedBlock(t *testing.T, state protocol.FollowerState, block *flow.Block) { 693 require.NoError(t, state.ExtendCertified(context.Background(), block, unittest.CertifyBlock(block.Header))) 694 require.NoError(t, state.Finalize(context.Background(), block.ID())) 695 } 696 697 // assertSealingSegmentBlocksQueryable bootstraps the state with the given 698 // snapshot, then verifies that all sealing segment blocks are queryable. 699 func assertSealingSegmentBlocksQueryableAfterBootstrap(t *testing.T, snapshot protocol.Snapshot) { 700 bootstrap(t, snapshot, func(state *bprotocol.State, err error) { 701 require.NoError(t, err) 702 703 segment, err := state.Final().SealingSegment() 704 require.NoError(t, err) 705 706 rootBlock := state.Params().FinalizedRoot() 707 708 // root block should be the highest block from the sealing segment 709 assert.Equal(t, segment.Highest().Header, rootBlock) 710 711 // for each block in the sealing segment we should be able to query: 712 // * Head 713 // * SealedResult 714 // * Commit 715 for _, block := range segment.Blocks { 716 blockID := block.ID() 717 snap := state.AtBlockID(blockID) 718 header, err := snap.Head() 719 assert.NoError(t, err) 720 assert.Equal(t, blockID, header.ID()) 721 _, seal, err := snap.SealedResult() 722 assert.NoError(t, err) 723 assert.Equal(t, segment.LatestSeals[blockID], seal.ID()) 724 commit, err := snap.Commit() 725 assert.NoError(t, err) 726 assert.Equal(t, seal.FinalState, commit) 727 } 728 // for all blocks but the head, we should be unable to query SealingSegment: 729 for _, block := range segment.Blocks[:len(segment.Blocks)-1] { 730 snap := state.AtBlockID(block.ID()) 731 _, err := snap.SealingSegment() 732 assert.ErrorIs(t, err, protocol.ErrSealingSegmentBelowRootBlock) 733 } 734 }) 735 } 736 737 // BenchmarkFinal benchmarks retrieving the latest finalized block from storage. 738 func BenchmarkFinal(b *testing.B) { 739 util.RunWithBootstrapState(b, unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()), func(db *badger.DB, state *bprotocol.State) { 740 b.ResetTimer() 741 for i := 0; i < b.N; i++ { 742 header, err := state.Final().Head() 743 assert.NoError(b, err) 744 assert.NotNil(b, header) 745 } 746 }) 747 } 748 749 // BenchmarkFinal benchmarks retrieving the block by height from storage. 750 func BenchmarkByHeight(b *testing.B) { 751 util.RunWithBootstrapState(b, unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()), func(db *badger.DB, state *bprotocol.State) { 752 b.ResetTimer() 753 for i := 0; i < b.N; i++ { 754 header, err := state.AtHeight(0).Head() 755 assert.NoError(b, err) 756 assert.NotNil(b, header) 757 } 758 }) 759 }