github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/cluster/badger/snapshot_test.go (about)

     1  package badger
     2  
     3  import (
     4  	"math"
     5  	"os"
     6  	"testing"
     7  
     8  	"github.com/dgraph-io/badger/v2"
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/suite"
    11  
    12  	model "github.com/onflow/flow-go/model/cluster"
    13  	"github.com/onflow/flow-go/model/flow"
    14  	"github.com/onflow/flow-go/module/metrics"
    15  	"github.com/onflow/flow-go/module/trace"
    16  	"github.com/onflow/flow-go/state/cluster"
    17  	"github.com/onflow/flow-go/state/protocol"
    18  	pbadger "github.com/onflow/flow-go/state/protocol/badger"
    19  	storage "github.com/onflow/flow-go/storage/badger"
    20  	"github.com/onflow/flow-go/storage/badger/operation"
    21  	"github.com/onflow/flow-go/storage/badger/procedure"
    22  	"github.com/onflow/flow-go/storage/util"
    23  	"github.com/onflow/flow-go/utils/unittest"
    24  )
    25  
    26  type SnapshotSuite struct {
    27  	suite.Suite
    28  	db    *badger.DB
    29  	dbdir string
    30  
    31  	genesis      *model.Block
    32  	chainID      flow.ChainID
    33  	epochCounter uint64
    34  
    35  	protoState protocol.State
    36  
    37  	state cluster.MutableState
    38  }
    39  
    40  // runs before each test runs
    41  func (suite *SnapshotSuite) SetupTest() {
    42  	var err error
    43  
    44  	suite.genesis = model.Genesis()
    45  	suite.chainID = suite.genesis.Header.ChainID
    46  
    47  	suite.dbdir = unittest.TempDir(suite.T())
    48  	suite.db = unittest.BadgerDB(suite.T(), suite.dbdir)
    49  
    50  	metrics := metrics.NewNoopCollector()
    51  	tracer := trace.NewNoopTracer()
    52  
    53  	all := util.StorageLayer(suite.T(), suite.db)
    54  	colPayloads := storage.NewClusterPayloads(metrics, suite.db)
    55  
    56  	root := unittest.RootSnapshotFixture(unittest.IdentityListFixture(5, unittest.WithAllRoles()))
    57  	suite.epochCounter = root.Encodable().SealingSegment.LatestProtocolStateEntry().EpochEntry.EpochCounter()
    58  
    59  	suite.protoState, err = pbadger.Bootstrap(
    60  		metrics,
    61  		suite.db,
    62  		all.Headers,
    63  		all.Seals,
    64  		all.Results,
    65  		all.Blocks,
    66  		all.QuorumCertificates,
    67  		all.Setups,
    68  		all.EpochCommits,
    69  		all.EpochProtocolState,
    70  		all.ProtocolKVStore,
    71  		all.VersionBeacons,
    72  		root,
    73  	)
    74  	suite.Require().NoError(err)
    75  
    76  	clusterStateRoot, err := NewStateRoot(suite.genesis, unittest.QuorumCertificateFixture(), suite.epochCounter)
    77  	suite.Require().NoError(err)
    78  	clusterState, err := Bootstrap(suite.db, clusterStateRoot)
    79  	suite.Require().NoError(err)
    80  	suite.state, err = NewMutableState(clusterState, tracer, all.Headers, colPayloads)
    81  	suite.Require().NoError(err)
    82  }
    83  
    84  // runs after each test finishes
    85  func (suite *SnapshotSuite) TearDownTest() {
    86  	err := suite.db.Close()
    87  	suite.Assert().Nil(err)
    88  	err = os.RemoveAll(suite.dbdir)
    89  	suite.Assert().Nil(err)
    90  }
    91  
    92  // Payload returns a valid cluster block payload containing the given transactions.
    93  func (suite *SnapshotSuite) Payload(transactions ...*flow.TransactionBody) model.Payload {
    94  	final, err := suite.protoState.Final().Head()
    95  	suite.Require().Nil(err)
    96  
    97  	// find the oldest reference block among the transactions
    98  	minRefID := final.ID() // use final by default
    99  	minRefHeight := uint64(math.MaxUint64)
   100  	for _, tx := range transactions {
   101  		refBlock, err := suite.protoState.AtBlockID(tx.ReferenceBlockID).Head()
   102  		if err != nil {
   103  			continue
   104  		}
   105  		if refBlock.Height < minRefHeight {
   106  			minRefHeight = refBlock.Height
   107  			minRefID = refBlock.ID()
   108  		}
   109  	}
   110  	return model.PayloadFromTransactions(minRefID, transactions...)
   111  }
   112  
   113  // BlockWithParent returns a valid block with the given parent.
   114  func (suite *SnapshotSuite) BlockWithParent(parent *model.Block) model.Block {
   115  	block := unittest.ClusterBlockWithParent(parent)
   116  	payload := suite.Payload()
   117  	block.SetPayload(payload)
   118  	return block
   119  }
   120  
   121  // Block returns a valid cluster block with genesis as parent.
   122  func (suite *SnapshotSuite) Block() model.Block {
   123  	return suite.BlockWithParent(suite.genesis)
   124  }
   125  
   126  func (suite *SnapshotSuite) InsertBlock(block model.Block) {
   127  	err := suite.db.Update(procedure.InsertClusterBlock(&block))
   128  	suite.Assert().Nil(err)
   129  }
   130  
   131  // InsertSubtree recursively inserts chain state as a subtree of the parent
   132  // block. The subtree has the given depth and `fanout` children at each node.
   133  // All child indices are updated.
   134  func (suite *SnapshotSuite) InsertSubtree(parent model.Block, depth, fanout int) {
   135  	if depth == 0 {
   136  		return
   137  	}
   138  
   139  	for i := 0; i < fanout; i++ {
   140  		block := suite.BlockWithParent(&parent)
   141  		suite.InsertBlock(block)
   142  		suite.InsertSubtree(block, depth-1, fanout)
   143  	}
   144  }
   145  
   146  func TestSnapshot(t *testing.T) {
   147  	suite.Run(t, new(SnapshotSuite))
   148  }
   149  
   150  func (suite *SnapshotSuite) TestNonexistentBlock() {
   151  	t := suite.T()
   152  
   153  	nonexistentBlockID := unittest.IdentifierFixture()
   154  	snapshot := suite.state.AtBlockID(nonexistentBlockID)
   155  
   156  	_, err := snapshot.Collection()
   157  	assert.Error(t, err)
   158  
   159  	_, err = snapshot.Head()
   160  	assert.Error(t, err)
   161  }
   162  
   163  func (suite *SnapshotSuite) TestAtBlockID() {
   164  	t := suite.T()
   165  
   166  	snapshot := suite.state.AtBlockID(suite.genesis.ID())
   167  
   168  	// ensure collection is correct
   169  	coll, err := snapshot.Collection()
   170  	assert.Nil(t, err)
   171  	assert.Equal(t, &suite.genesis.Payload.Collection, coll)
   172  
   173  	// ensure head is correct
   174  	head, err := snapshot.Head()
   175  	assert.Nil(t, err)
   176  	assert.Equal(t, suite.genesis.ID(), head.ID())
   177  }
   178  
   179  func (suite *SnapshotSuite) TestEmptyCollection() {
   180  	t := suite.T()
   181  
   182  	// create a block with an empty collection
   183  	block := suite.BlockWithParent(suite.genesis)
   184  	block.SetPayload(model.EmptyPayload(flow.ZeroID))
   185  	suite.InsertBlock(block)
   186  
   187  	snapshot := suite.state.AtBlockID(block.ID())
   188  
   189  	// ensure collection is correct
   190  	coll, err := snapshot.Collection()
   191  	assert.Nil(t, err)
   192  	assert.Equal(t, &block.Payload.Collection, coll)
   193  }
   194  
   195  func (suite *SnapshotSuite) TestFinalizedBlock() {
   196  	t := suite.T()
   197  
   198  	// create a new finalized block on genesis (height=1)
   199  	finalizedBlock1 := suite.Block()
   200  	err := suite.state.Extend(&finalizedBlock1)
   201  	assert.Nil(t, err)
   202  
   203  	// create an un-finalized block on genesis (height=1)
   204  	unFinalizedBlock1 := suite.Block()
   205  	err = suite.state.Extend(&unFinalizedBlock1)
   206  	assert.Nil(t, err)
   207  
   208  	// create a second un-finalized on top of the finalized block (height=2)
   209  	unFinalizedBlock2 := suite.BlockWithParent(&finalizedBlock1)
   210  	err = suite.state.Extend(&unFinalizedBlock2)
   211  	assert.Nil(t, err)
   212  
   213  	// finalize the block
   214  	err = suite.db.Update(procedure.FinalizeClusterBlock(finalizedBlock1.ID()))
   215  	assert.Nil(t, err)
   216  
   217  	// get the final snapshot, should map to finalizedBlock1
   218  	snapshot := suite.state.Final()
   219  
   220  	// ensure collection is correct
   221  	coll, err := snapshot.Collection()
   222  	assert.Nil(t, err)
   223  	assert.Equal(t, &finalizedBlock1.Payload.Collection, coll)
   224  
   225  	// ensure head is correct
   226  	head, err := snapshot.Head()
   227  	assert.Nil(t, err)
   228  	assert.Equal(t, finalizedBlock1.ID(), head.ID())
   229  }
   230  
   231  // test that no pending blocks are returned when there are none
   232  func (suite *SnapshotSuite) TestPending_NoPendingBlocks() {
   233  
   234  	// first, check that a freshly bootstrapped state has no pending blocks
   235  	suite.Run("freshly bootstrapped state", func() {
   236  		pending, err := suite.state.Final().Pending()
   237  		suite.Require().Nil(err)
   238  		suite.Assert().Len(pending, 0)
   239  	})
   240  
   241  }
   242  
   243  // test that the appropriate pending blocks are included
   244  func (suite *SnapshotSuite) TestPending_WithPendingBlocks() {
   245  
   246  	// check with some finalized blocks
   247  	parent := suite.genesis
   248  	pendings := make([]flow.Identifier, 0, 10)
   249  	for i := 0; i < 10; i++ {
   250  		next := suite.BlockWithParent(parent)
   251  		suite.InsertBlock(next)
   252  		pendings = append(pendings, next.ID())
   253  	}
   254  
   255  	pending, err := suite.state.Final().Pending()
   256  	suite.Require().Nil(err)
   257  	suite.Require().Equal(pendings, pending)
   258  }
   259  
   260  // ensure that pending blocks are included, even they aren't direct children
   261  // of the finalized head
   262  func (suite *SnapshotSuite) TestPending_Grandchildren() {
   263  
   264  	// create 3 levels of children
   265  	suite.InsertSubtree(*suite.genesis, 3, 3)
   266  
   267  	pending, err := suite.state.Final().Pending()
   268  	suite.Require().Nil(err)
   269  
   270  	// we should have 3 + 3^2 + 3^3 = 39 total children
   271  	suite.Assert().Len(pending, 39)
   272  
   273  	// the result must be ordered so that we see parents before their children
   274  	parents := make(map[flow.Identifier]struct{})
   275  	// initialize with the latest finalized block, which is the parent of the
   276  	// first level of children
   277  	parents[suite.genesis.ID()] = struct{}{}
   278  
   279  	for _, blockID := range pending {
   280  		var header flow.Header
   281  		err := suite.db.View(operation.RetrieveHeader(blockID, &header))
   282  		suite.Require().Nil(err)
   283  
   284  		// we must have already seen the parent
   285  		_, seen := parents[header.ParentID]
   286  		suite.Assert().True(seen, "pending list contained child (%x) before parent (%x)", header.ID(), header.ParentID)
   287  
   288  		// mark this block as seen
   289  		parents[header.ID()] = struct{}{}
   290  	}
   291  }
   292  
   293  func (suite *SnapshotSuite) TestParams_ChainID() {
   294  	chainID := suite.state.Params().ChainID()
   295  	suite.Assert().Equal(suite.genesis.Header.ChainID, chainID)
   296  }