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  }