github.com/onflow/flow-go@v0.33.17/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("CommittedEpochFinalView", finalView).Once()
    50  		complianceMetrics.On("CurrentEpochCounter", counter).Once()
    51  		complianceMetrics.On("CurrentEpochPhase", phase).Once()
    52  		complianceMetrics.On("CurrentEpochFinalView", finalView).Once()
    53  		complianceMetrics.On("FinalizedHeight", testmock.Anything).Once()
    54  		complianceMetrics.On("SealedHeight", testmock.Anything).Once()
    55  
    56  		dkgPhase1FinalView, dkgPhase2FinalView, dkgPhase3FinalView, err := protocol.DKGPhaseViews(epoch)
    57  		require.NoError(t, err)
    58  		complianceMetrics.On("CurrentDKGPhase1FinalView", dkgPhase1FinalView).Once()
    59  		complianceMetrics.On("CurrentDKGPhase2FinalView", dkgPhase2FinalView).Once()
    60  		complianceMetrics.On("CurrentDKGPhase3FinalView", dkgPhase3FinalView).Once()
    61  
    62  		noopMetrics := new(metrics.NoopCollector)
    63  		all := storagebadger.InitAll(noopMetrics, db)
    64  		// protocol state has been bootstrapped, now open a protocol state with the database
    65  		state, err := bprotocol.OpenState(
    66  			complianceMetrics,
    67  			db,
    68  			all.Headers,
    69  			all.Seals,
    70  			all.Results,
    71  			all.Blocks,
    72  			all.QuorumCertificates,
    73  			all.Setups,
    74  			all.EpochCommits,
    75  			all.Statuses,
    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  
    95  	// create a state root and bootstrap the protocol state with it
    96  	participants := unittest.CompleteIdentitySet()
    97  	rootSnapshot := unittest.RootSnapshotFixture(participants, func(block *flow.Block) {
    98  		block.Header.ParentID = unittest.IdentifierFixture()
    99  	})
   100  	rootBlock, err := rootSnapshot.Head()
   101  	require.NoError(t, err)
   102  
   103  	// build an epoch on the root state and return a snapshot from the committed phase
   104  	committedPhaseSnapshot := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState) protocol.Snapshot {
   105  		unittest.NewEpochBuilder(t, state).BuildEpoch().CompleteEpoch()
   106  
   107  		// find the point where we transition to the epoch committed phase
   108  		for height := rootBlock.Height + 1; ; height++ {
   109  			phase, err := state.AtHeight(height).Phase()
   110  			require.NoError(t, err)
   111  			if phase == flow.EpochPhaseCommitted {
   112  				return state.AtHeight(height)
   113  			}
   114  		}
   115  	})
   116  
   117  	protoutil.RunWithBootstrapState(t, committedPhaseSnapshot, func(db *badger.DB, _ *bprotocol.State) {
   118  
   119  		complianceMetrics := new(mock.ComplianceMetrics)
   120  
   121  		// expect the final view metric to be set to next epoch's final view
   122  		finalView, err := committedPhaseSnapshot.Epochs().Next().FinalView()
   123  		require.NoError(t, err)
   124  		complianceMetrics.On("CommittedEpochFinalView", finalView).Once()
   125  
   126  		// expect counter to be set to current epochs counter
   127  		counter, err := committedPhaseSnapshot.Epochs().Current().Counter()
   128  		require.NoError(t, err)
   129  		complianceMetrics.On("CurrentEpochCounter", counter).Once()
   130  
   131  		// expect epoch phase to be set to current phase
   132  		phase, err := committedPhaseSnapshot.Phase()
   133  		require.NoError(t, err)
   134  		complianceMetrics.On("CurrentEpochPhase", phase).Once()
   135  
   136  		currentEpochFinalView, err := committedPhaseSnapshot.Epochs().Current().FinalView()
   137  		require.NoError(t, err)
   138  		complianceMetrics.On("CurrentEpochFinalView", currentEpochFinalView).Once()
   139  
   140  		dkgPhase1FinalView, dkgPhase2FinalView, dkgPhase3FinalView, err := protocol.DKGPhaseViews(committedPhaseSnapshot.Epochs().Current())
   141  		require.NoError(t, err)
   142  		complianceMetrics.On("CurrentDKGPhase1FinalView", dkgPhase1FinalView).Once()
   143  		complianceMetrics.On("CurrentDKGPhase2FinalView", dkgPhase2FinalView).Once()
   144  		complianceMetrics.On("CurrentDKGPhase3FinalView", dkgPhase3FinalView).Once()
   145  		complianceMetrics.On("FinalizedHeight", testmock.Anything).Once()
   146  		complianceMetrics.On("SealedHeight", testmock.Anything).Once()
   147  
   148  		noopMetrics := new(metrics.NoopCollector)
   149  		all := storagebadger.InitAll(noopMetrics, db)
   150  		state, err := bprotocol.OpenState(
   151  			complianceMetrics,
   152  			db,
   153  			all.Headers,
   154  			all.Seals,
   155  			all.Results,
   156  			all.Blocks,
   157  			all.QuorumCertificates,
   158  			all.Setups,
   159  			all.EpochCommits,
   160  			all.Statuses,
   161  			all.VersionBeacons,
   162  		)
   163  		require.NoError(t, err)
   164  
   165  		// assert update final view was called
   166  		complianceMetrics.AssertExpectations(t)
   167  
   168  		unittest.AssertSnapshotsEqual(t, committedPhaseSnapshot, state.Final())
   169  	})
   170  }
   171  
   172  // TestBootstrap_EpochHeightBoundaries tests that epoch height indexes are indexed
   173  // when they are available in the input snapshot.
   174  func TestBootstrap_EpochHeightBoundaries(t *testing.T) {
   175  	t.Parallel()
   176  	// start with a regular post-spork root snapshot
   177  	rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   178  	epoch1FirstHeight := rootSnapshot.Encodable().Head.Height
   179  
   180  	t.Run("root snapshot", func(t *testing.T) {
   181  		util.RunWithFollowerProtocolState(t, rootSnapshot, func(db *badger.DB, state *bprotocol.FollowerState) {
   182  			// first height of started current epoch should be known
   183  			firstHeight, err := state.Final().Epochs().Current().FirstHeight()
   184  			require.NoError(t, err)
   185  			assert.Equal(t, epoch1FirstHeight, firstHeight)
   186  			// final height of not completed current epoch should be unknown
   187  			_, err = state.Final().Epochs().Current().FinalHeight()
   188  			assert.ErrorIs(t, err, protocol.ErrEpochTransitionNotFinalized)
   189  		})
   190  	})
   191  
   192  	t.Run("with next epoch", func(t *testing.T) {
   193  		after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState) protocol.Snapshot {
   194  			builder := unittest.NewEpochBuilder(t, state)
   195  			builder.BuildEpoch().CompleteEpoch()
   196  			heights, ok := builder.EpochHeights(1)
   197  			require.True(t, ok)
   198  			return state.AtHeight(heights.Committed)
   199  		})
   200  
   201  		bootstrap(t, after, func(state *bprotocol.State, err error) {
   202  			require.NoError(t, err)
   203  			// first height of started current epoch should be known
   204  			firstHeight, err := state.Final().Epochs().Current().FirstHeight()
   205  			assert.Equal(t, epoch1FirstHeight, firstHeight)
   206  			require.NoError(t, err)
   207  			// final height of not completed current epoch should be unknown
   208  			_, err = state.Final().Epochs().Current().FinalHeight()
   209  			assert.ErrorIs(t, err, protocol.ErrEpochTransitionNotFinalized)
   210  			// first and final height of not started next epoch should be unknown
   211  			_, err = state.Final().Epochs().Next().FirstHeight()
   212  			assert.ErrorIs(t, err, protocol.ErrEpochTransitionNotFinalized)
   213  			_, err = state.Final().Epochs().Next().FinalHeight()
   214  			assert.ErrorIs(t, err, protocol.ErrEpochTransitionNotFinalized)
   215  		})
   216  	})
   217  	t.Run("with previous epoch", func(t *testing.T) {
   218  		var epoch1FinalHeight uint64
   219  		var epoch2FirstHeight uint64
   220  		after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState) protocol.Snapshot {
   221  			builder := unittest.NewEpochBuilder(t, state)
   222  			builder.
   223  				BuildEpoch().CompleteEpoch(). // build epoch 2
   224  				BuildEpoch()                  // build epoch 3
   225  			heights, ok := builder.EpochHeights(2)
   226  			epoch2FirstHeight = heights.FirstHeight()
   227  			epoch1FinalHeight = epoch2FirstHeight - 1
   228  			require.True(t, ok)
   229  			// return snapshot from within epoch 2 (middle epoch)
   230  			return state.AtHeight(heights.Setup)
   231  		})
   232  
   233  		bootstrap(t, after, func(state *bprotocol.State, err error) {
   234  			require.NoError(t, err)
   235  			// first height of started current epoch should be known
   236  			firstHeight, err := state.Final().Epochs().Current().FirstHeight()
   237  			assert.Equal(t, epoch2FirstHeight, firstHeight)
   238  			require.NoError(t, err)
   239  			// final height of not completed current epoch should be unknown
   240  			_, err = state.Final().Epochs().Current().FinalHeight()
   241  			assert.ErrorIs(t, err, protocol.ErrEpochTransitionNotFinalized)
   242  			// first and final height of completed previous epoch should be known
   243  			firstHeight, err = state.Final().Epochs().Previous().FirstHeight()
   244  			require.NoError(t, err)
   245  			assert.Equal(t, firstHeight, epoch1FirstHeight)
   246  			finalHeight, err := state.Final().Epochs().Previous().FinalHeight()
   247  			require.NoError(t, err)
   248  			assert.Equal(t, finalHeight, epoch1FinalHeight)
   249  		})
   250  	})
   251  }
   252  
   253  // TestBootstrapNonRoot tests bootstrapping the protocol state from arbitrary states.
   254  //
   255  // NOTE: for all these cases, we build a final child block (CHILD). This is
   256  // needed otherwise the parent block would not have a valid QC, since the QC
   257  // is stored in the child.
   258  func TestBootstrapNonRoot(t *testing.T) {
   259  	t.Parallel()
   260  	// start with a regular post-spork root snapshot
   261  	participants := unittest.CompleteIdentitySet()
   262  	rootSnapshot := unittest.RootSnapshotFixture(participants)
   263  	rootBlock, err := rootSnapshot.Head()
   264  	require.NoError(t, err)
   265  
   266  	// should be able to bootstrap from snapshot after sealing a non-root block
   267  	// ROOT <- B1 <- B2(R1) <- B3(S1) <- CHILD
   268  	t.Run("with sealed block", func(t *testing.T) {
   269  		after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState) protocol.Snapshot {
   270  			block1 := unittest.BlockWithParentFixture(rootBlock)
   271  			buildFinalizedBlock(t, state, block1)
   272  
   273  			receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1)
   274  			block2 := unittest.BlockWithParentFixture(block1.Header)
   275  			block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1)))
   276  			buildFinalizedBlock(t, state, block2)
   277  
   278  			block3 := unittest.BlockWithParentFixture(block2.Header)
   279  			block3.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal1)))
   280  			buildFinalizedBlock(t, state, block3)
   281  
   282  			child := unittest.BlockWithParentFixture(block3.Header)
   283  			buildBlock(t, state, child)
   284  
   285  			return state.AtBlockID(block3.ID())
   286  		})
   287  
   288  		bootstrap(t, after, func(state *bprotocol.State, err error) {
   289  			require.NoError(t, err)
   290  			unittest.AssertSnapshotsEqual(t, after, state.Final())
   291  			// should be able to read all QCs
   292  			segment, err := state.Final().SealingSegment()
   293  			require.NoError(t, err)
   294  			for _, block := range segment.Blocks {
   295  				snapshot := state.AtBlockID(block.ID())
   296  				_, err := snapshot.QuorumCertificate()
   297  				require.NoError(t, err)
   298  				_, err = snapshot.RandomSource()
   299  				require.NoError(t, err)
   300  			}
   301  		})
   302  	})
   303  
   304  	t.Run("with setup next epoch", func(t *testing.T) {
   305  		after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState) protocol.Snapshot {
   306  			unittest.NewEpochBuilder(t, state).BuildEpoch()
   307  
   308  			// find the point where we transition to the epoch setup phase
   309  			for height := rootBlock.Height + 1; ; height++ {
   310  				phase, err := state.AtHeight(height).Phase()
   311  				require.NoError(t, err)
   312  				if phase == flow.EpochPhaseSetup {
   313  					return state.AtHeight(height)
   314  				}
   315  			}
   316  		})
   317  
   318  		bootstrap(t, after, func(state *bprotocol.State, err error) {
   319  			require.NoError(t, err)
   320  			unittest.AssertSnapshotsEqual(t, after, state.Final())
   321  		})
   322  	})
   323  
   324  	t.Run("with committed next epoch", func(t *testing.T) {
   325  		after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState) protocol.Snapshot {
   326  			unittest.NewEpochBuilder(t, state).BuildEpoch().CompleteEpoch()
   327  
   328  			// find the point where we transition to the epoch committed phase
   329  			for height := rootBlock.Height + 1; ; height++ {
   330  				phase, err := state.AtHeight(height).Phase()
   331  				require.NoError(t, err)
   332  				if phase == flow.EpochPhaseCommitted {
   333  					return state.AtHeight(height)
   334  				}
   335  			}
   336  		})
   337  
   338  		bootstrap(t, after, func(state *bprotocol.State, err error) {
   339  			require.NoError(t, err)
   340  			unittest.AssertSnapshotsEqual(t, after, state.Final())
   341  		})
   342  	})
   343  
   344  	t.Run("with previous and next epoch", func(t *testing.T) {
   345  		after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState) protocol.Snapshot {
   346  			unittest.NewEpochBuilder(t, state).
   347  				BuildEpoch().CompleteEpoch(). // build epoch 2
   348  				BuildEpoch()                  // build epoch 3
   349  
   350  			// find a snapshot from epoch setup phase in epoch 2
   351  			epoch1Counter, err := rootSnapshot.Epochs().Current().Counter()
   352  			require.NoError(t, err)
   353  			for height := rootBlock.Height + 1; ; height++ {
   354  				snap := state.AtHeight(height)
   355  				counter, err := snap.Epochs().Current().Counter()
   356  				require.NoError(t, err)
   357  				phase, err := snap.Phase()
   358  				require.NoError(t, err)
   359  				if phase == flow.EpochPhaseSetup && counter == epoch1Counter+1 {
   360  					return snap
   361  				}
   362  			}
   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  		})
   369  	})
   370  }
   371  
   372  func TestBootstrap_InvalidIdentities(t *testing.T) {
   373  	t.Run("duplicate node ID", func(t *testing.T) {
   374  		participants := unittest.CompleteIdentitySet()
   375  		dupeIDIdentity := unittest.IdentityFixture(unittest.WithNodeID(participants[0].NodeID))
   376  		participants = append(participants, dupeIDIdentity)
   377  
   378  		root := unittest.RootSnapshotFixture(participants)
   379  		bootstrap(t, root, func(state *bprotocol.State, err error) {
   380  			assert.Error(t, err)
   381  		})
   382  	})
   383  
   384  	t.Run("zero weight", func(t *testing.T) {
   385  		zeroWeightIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleVerification), unittest.WithWeight(0))
   386  		participants := unittest.CompleteIdentitySet(zeroWeightIdentity)
   387  		root := unittest.RootSnapshotFixture(participants)
   388  		bootstrap(t, root, func(state *bprotocol.State, err error) {
   389  			assert.Error(t, err)
   390  		})
   391  	})
   392  
   393  	t.Run("missing role", func(t *testing.T) {
   394  		requiredRoles := []flow.Role{
   395  			flow.RoleConsensus,
   396  			flow.RoleExecution,
   397  			flow.RoleVerification,
   398  		}
   399  
   400  		for _, role := range requiredRoles {
   401  			t.Run(fmt.Sprintf("no %s nodes", role), func(t *testing.T) {
   402  				participants := unittest.IdentityListFixture(5, unittest.WithAllRolesExcept(role))
   403  				root := unittest.RootSnapshotFixture(participants)
   404  				bootstrap(t, root, func(state *bprotocol.State, err error) {
   405  					assert.Error(t, err)
   406  				})
   407  			})
   408  		}
   409  	})
   410  
   411  	t.Run("duplicate address", func(t *testing.T) {
   412  		participants := unittest.CompleteIdentitySet()
   413  		dupeAddressIdentity := unittest.IdentityFixture(unittest.WithAddress(participants[0].Address))
   414  		participants = append(participants, dupeAddressIdentity)
   415  
   416  		root := unittest.RootSnapshotFixture(participants)
   417  		bootstrap(t, root, func(state *bprotocol.State, err error) {
   418  			assert.Error(t, err)
   419  		})
   420  	})
   421  
   422  	t.Run("non-canonical ordering", func(t *testing.T) {
   423  		participants := unittest.IdentityListFixture(20, unittest.WithAllRoles())
   424  
   425  		root := unittest.RootSnapshotFixture(participants)
   426  		// randomly shuffle the identities so they are not canonically ordered
   427  		encodable := root.Encodable()
   428  		var err error
   429  		encodable.Identities, err = participants.Shuffle()
   430  		require.NoError(t, err)
   431  		root = inmem.SnapshotFromEncodable(encodable)
   432  		bootstrap(t, root, func(state *bprotocol.State, err error) {
   433  			assert.Error(t, err)
   434  		})
   435  	})
   436  }
   437  
   438  func TestBootstrap_DisconnectedSealingSegment(t *testing.T) {
   439  	rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   440  	// convert to encodable to easily modify snapshot
   441  	encodable := rootSnapshot.Encodable()
   442  	// add an un-connected tail block to the sealing segment
   443  	tail := unittest.BlockFixture()
   444  	encodable.SealingSegment.Blocks = append([]*flow.Block{&tail}, encodable.SealingSegment.Blocks...)
   445  	rootSnapshot = inmem.SnapshotFromEncodable(encodable)
   446  
   447  	bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   448  		assert.Error(t, err)
   449  	})
   450  }
   451  
   452  func TestBootstrap_SealingSegmentMissingSeal(t *testing.T) {
   453  	rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   454  	// convert to encodable to easily modify snapshot
   455  	encodable := rootSnapshot.Encodable()
   456  	// we are missing the required first seal
   457  	encodable.SealingSegment.FirstSeal = nil
   458  	rootSnapshot = inmem.SnapshotFromEncodable(encodable)
   459  
   460  	bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   461  		assert.Error(t, err)
   462  	})
   463  }
   464  
   465  func TestBootstrap_SealingSegmentMissingResult(t *testing.T) {
   466  	rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   467  	// convert to encodable to easily modify snapshot
   468  	encodable := rootSnapshot.Encodable()
   469  	// we are missing the result referenced by the root seal
   470  	encodable.SealingSegment.ExecutionResults = nil
   471  	rootSnapshot = inmem.SnapshotFromEncodable(encodable)
   472  
   473  	bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   474  		assert.Error(t, err)
   475  	})
   476  }
   477  
   478  func TestBootstrap_InvalidQuorumCertificate(t *testing.T) {
   479  	rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   480  	// convert to encodable to easily modify snapshot
   481  	encodable := rootSnapshot.Encodable()
   482  	encodable.QuorumCertificate.BlockID = unittest.IdentifierFixture()
   483  	rootSnapshot = inmem.SnapshotFromEncodable(encodable)
   484  
   485  	bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   486  		assert.Error(t, err)
   487  	})
   488  }
   489  
   490  func TestBootstrap_SealMismatch(t *testing.T) {
   491  	t.Run("seal doesn't match tail block", func(t *testing.T) {
   492  		rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   493  		// convert to encodable to easily modify snapshot
   494  		encodable := rootSnapshot.Encodable()
   495  		encodable.LatestSeal.BlockID = unittest.IdentifierFixture()
   496  
   497  		bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   498  			assert.Error(t, err)
   499  		})
   500  	})
   501  
   502  	t.Run("result doesn't match tail block", func(t *testing.T) {
   503  		rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   504  		// convert to encodable to easily modify snapshot
   505  		encodable := rootSnapshot.Encodable()
   506  		encodable.LatestResult.BlockID = unittest.IdentifierFixture()
   507  
   508  		bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   509  			assert.Error(t, err)
   510  		})
   511  	})
   512  
   513  	t.Run("seal doesn't match result", func(t *testing.T) {
   514  		rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   515  		// convert to encodable to easily modify snapshot
   516  		encodable := rootSnapshot.Encodable()
   517  		encodable.LatestSeal.ResultID = unittest.IdentifierFixture()
   518  
   519  		bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   520  			assert.Error(t, err)
   521  		})
   522  	})
   523  }
   524  
   525  // bootstraps protocol state with the given snapshot and invokes the callback
   526  // with the result of the constructor
   527  func bootstrap(t *testing.T, rootSnapshot protocol.Snapshot, f func(*bprotocol.State, error)) {
   528  	metrics := metrics.NewNoopCollector()
   529  	dir := unittest.TempDir(t)
   530  	defer os.RemoveAll(dir)
   531  	db := unittest.BadgerDB(t, dir)
   532  	defer db.Close()
   533  	all := storutil.StorageLayer(t, db)
   534  	state, err := bprotocol.Bootstrap(
   535  		metrics,
   536  		db,
   537  		all.Headers,
   538  		all.Seals,
   539  		all.Results,
   540  		all.Blocks,
   541  		all.QuorumCertificates,
   542  		all.Setups,
   543  		all.EpochCommits,
   544  		all.Statuses,
   545  		all.VersionBeacons,
   546  		rootSnapshot,
   547  	)
   548  	f(state, err)
   549  }
   550  
   551  // snapshotAfter bootstraps the protocol state from the root snapshot, applies
   552  // the state-changing function f, clears the on-disk state, and returns a
   553  // memory-backed snapshot corresponding to that returned by f.
   554  //
   555  // This is used for generating valid snapshots to use when testing bootstrapping
   556  // from non-root states.
   557  func snapshotAfter(t *testing.T, rootSnapshot protocol.Snapshot, f func(*bprotocol.FollowerState) protocol.Snapshot) protocol.Snapshot {
   558  	var after protocol.Snapshot
   559  	protoutil.RunWithFollowerProtocolState(t, rootSnapshot, func(_ *badger.DB, state *bprotocol.FollowerState) {
   560  		snap := f(state)
   561  		var err error
   562  		after, err = inmem.FromSnapshot(snap)
   563  		require.NoError(t, err)
   564  	})
   565  	return after
   566  }
   567  
   568  // buildBlock extends the protocol state by the given block
   569  func buildBlock(t *testing.T, state protocol.FollowerState, block *flow.Block) {
   570  	require.NoError(t, state.ExtendCertified(context.Background(), block, unittest.CertifyBlock(block.Header)))
   571  }
   572  
   573  // buildFinalizedBlock extends the protocol state by the given block and marks the block as finalized
   574  func buildFinalizedBlock(t *testing.T, state protocol.FollowerState, block *flow.Block) {
   575  	require.NoError(t, state.ExtendCertified(context.Background(), block, unittest.CertifyBlock(block.Header)))
   576  	require.NoError(t, state.Finalize(context.Background(), block.ID()))
   577  }
   578  
   579  // assertSealingSegmentBlocksQueryable bootstraps the state with the given
   580  // snapshot, then verifies that all sealing segment blocks are queryable.
   581  func assertSealingSegmentBlocksQueryableAfterBootstrap(t *testing.T, snapshot protocol.Snapshot) {
   582  	bootstrap(t, snapshot, func(state *bprotocol.State, err error) {
   583  		require.NoError(t, err)
   584  
   585  		segment, err := state.Final().SealingSegment()
   586  		require.NoError(t, err)
   587  
   588  		rootBlock, err := state.Params().FinalizedRoot()
   589  		require.NoError(t, err)
   590  
   591  		// root block should be the highest block from the sealing segment
   592  		assert.Equal(t, segment.Highest().Header, rootBlock)
   593  
   594  		// for each block in the sealing segment we should be able to query:
   595  		// * Head
   596  		// * SealedResult
   597  		// * Commit
   598  		for _, block := range segment.Blocks {
   599  			blockID := block.ID()
   600  			snap := state.AtBlockID(blockID)
   601  			header, err := snap.Head()
   602  			assert.NoError(t, err)
   603  			assert.Equal(t, blockID, header.ID())
   604  			_, seal, err := snap.SealedResult()
   605  			assert.NoError(t, err)
   606  			assert.Equal(t, segment.LatestSeals[blockID], seal.ID())
   607  			commit, err := snap.Commit()
   608  			assert.NoError(t, err)
   609  			assert.Equal(t, seal.FinalState, commit)
   610  		}
   611  		// for all blocks but the head, we should be unable to query SealingSegment:
   612  		for _, block := range segment.Blocks[:len(segment.Blocks)-1] {
   613  			snap := state.AtBlockID(block.ID())
   614  			_, err := snap.SealingSegment()
   615  			assert.ErrorIs(t, err, protocol.ErrSealingSegmentBelowRootBlock)
   616  		}
   617  	})
   618  }
   619  
   620  // BenchmarkFinal benchmarks retrieving the latest finalized block from storage.
   621  func BenchmarkFinal(b *testing.B) {
   622  	util.RunWithBootstrapState(b, unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()), func(db *badger.DB, state *bprotocol.State) {
   623  		b.ResetTimer()
   624  		for i := 0; i < b.N; i++ {
   625  			header, err := state.Final().Head()
   626  			assert.NoError(b, err)
   627  			assert.NotNil(b, header)
   628  		}
   629  	})
   630  }
   631  
   632  // BenchmarkFinal benchmarks retrieving the block by height from storage.
   633  func BenchmarkByHeight(b *testing.B) {
   634  	util.RunWithBootstrapState(b, unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()), func(db *badger.DB, state *bprotocol.State) {
   635  		b.ResetTimer()
   636  		for i := 0; i < b.N; i++ {
   637  			header, err := state.AtHeight(0).Head()
   638  			assert.NoError(b, err)
   639  			assert.NotNil(b, header)
   640  		}
   641  	})
   642  }