github.com/onflow/flow-go@v0.33.17/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().Epochs.Current.Counter
    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.Statuses,
    70  		all.VersionBeacons,
    71  		root,
    72  	)
    73  	suite.Require().NoError(err)
    74  
    75  	clusterStateRoot, err := NewStateRoot(suite.genesis, unittest.QuorumCertificateFixture(), suite.epochCounter)
    76  	suite.Require().NoError(err)
    77  	clusterState, err := Bootstrap(suite.db, clusterStateRoot)
    78  	suite.Require().NoError(err)
    79  	suite.state, err = NewMutableState(clusterState, tracer, all.Headers, colPayloads)
    80  	suite.Require().NoError(err)
    81  }
    82  
    83  // runs after each test finishes
    84  func (suite *SnapshotSuite) TearDownTest() {
    85  	err := suite.db.Close()
    86  	suite.Assert().Nil(err)
    87  	err = os.RemoveAll(suite.dbdir)
    88  	suite.Assert().Nil(err)
    89  }
    90  
    91  // Payload returns a valid cluster block payload containing the given transactions.
    92  func (suite *SnapshotSuite) Payload(transactions ...*flow.TransactionBody) model.Payload {
    93  	final, err := suite.protoState.Final().Head()
    94  	suite.Require().Nil(err)
    95  
    96  	// find the oldest reference block among the transactions
    97  	minRefID := final.ID() // use final by default
    98  	minRefHeight := uint64(math.MaxUint64)
    99  	for _, tx := range transactions {
   100  		refBlock, err := suite.protoState.AtBlockID(tx.ReferenceBlockID).Head()
   101  		if err != nil {
   102  			continue
   103  		}
   104  		if refBlock.Height < minRefHeight {
   105  			minRefHeight = refBlock.Height
   106  			minRefID = refBlock.ID()
   107  		}
   108  	}
   109  	return model.PayloadFromTransactions(minRefID, transactions...)
   110  }
   111  
   112  // BlockWithParent returns a valid block with the given parent.
   113  func (suite *SnapshotSuite) BlockWithParent(parent *model.Block) model.Block {
   114  	block := unittest.ClusterBlockWithParent(parent)
   115  	payload := suite.Payload()
   116  	block.SetPayload(payload)
   117  	return block
   118  }
   119  
   120  // Block returns a valid cluster block with genesis as parent.
   121  func (suite *SnapshotSuite) Block() model.Block {
   122  	return suite.BlockWithParent(suite.genesis)
   123  }
   124  
   125  func (suite *SnapshotSuite) InsertBlock(block model.Block) {
   126  	err := suite.db.Update(procedure.InsertClusterBlock(&block))
   127  	suite.Assert().Nil(err)
   128  }
   129  
   130  // InsertSubtree recursively inserts chain state as a subtree of the parent
   131  // block. The subtree has the given depth and `fanout` children at each node.
   132  // All child indices are updated.
   133  func (suite *SnapshotSuite) InsertSubtree(parent model.Block, depth, fanout int) {
   134  	if depth == 0 {
   135  		return
   136  	}
   137  
   138  	for i := 0; i < fanout; i++ {
   139  		block := suite.BlockWithParent(&parent)
   140  		suite.InsertBlock(block)
   141  		suite.InsertSubtree(block, depth-1, fanout)
   142  	}
   143  }
   144  
   145  func TestSnapshot(t *testing.T) {
   146  	suite.Run(t, new(SnapshotSuite))
   147  }
   148  
   149  func (suite *SnapshotSuite) TestNonexistentBlock() {
   150  	t := suite.T()
   151  
   152  	nonexistentBlockID := unittest.IdentifierFixture()
   153  	snapshot := suite.state.AtBlockID(nonexistentBlockID)
   154  
   155  	_, err := snapshot.Collection()
   156  	assert.Error(t, err)
   157  
   158  	_, err = snapshot.Head()
   159  	assert.Error(t, err)
   160  }
   161  
   162  func (suite *SnapshotSuite) TestAtBlockID() {
   163  	t := suite.T()
   164  
   165  	snapshot := suite.state.AtBlockID(suite.genesis.ID())
   166  
   167  	// ensure collection is correct
   168  	coll, err := snapshot.Collection()
   169  	assert.Nil(t, err)
   170  	assert.Equal(t, &suite.genesis.Payload.Collection, coll)
   171  
   172  	// ensure head is correct
   173  	head, err := snapshot.Head()
   174  	assert.Nil(t, err)
   175  	assert.Equal(t, suite.genesis.ID(), head.ID())
   176  }
   177  
   178  func (suite *SnapshotSuite) TestEmptyCollection() {
   179  	t := suite.T()
   180  
   181  	// create a block with an empty collection
   182  	block := suite.BlockWithParent(suite.genesis)
   183  	block.SetPayload(model.EmptyPayload(flow.ZeroID))
   184  	suite.InsertBlock(block)
   185  
   186  	snapshot := suite.state.AtBlockID(block.ID())
   187  
   188  	// ensure collection is correct
   189  	coll, err := snapshot.Collection()
   190  	assert.Nil(t, err)
   191  	assert.Equal(t, &block.Payload.Collection, coll)
   192  }
   193  
   194  func (suite *SnapshotSuite) TestFinalizedBlock() {
   195  	t := suite.T()
   196  
   197  	// create a new finalized block on genesis (height=1)
   198  	finalizedBlock1 := suite.Block()
   199  	err := suite.state.Extend(&finalizedBlock1)
   200  	assert.Nil(t, err)
   201  
   202  	// create an un-finalized block on genesis (height=1)
   203  	unFinalizedBlock1 := suite.Block()
   204  	err = suite.state.Extend(&unFinalizedBlock1)
   205  	assert.Nil(t, err)
   206  
   207  	// create a second un-finalized on top of the finalized block (height=2)
   208  	unFinalizedBlock2 := suite.BlockWithParent(&finalizedBlock1)
   209  	err = suite.state.Extend(&unFinalizedBlock2)
   210  	assert.Nil(t, err)
   211  
   212  	// finalize the block
   213  	err = suite.db.Update(procedure.FinalizeClusterBlock(finalizedBlock1.ID()))
   214  	assert.Nil(t, err)
   215  
   216  	// get the final snapshot, should map to finalizedBlock1
   217  	snapshot := suite.state.Final()
   218  
   219  	// ensure collection is correct
   220  	coll, err := snapshot.Collection()
   221  	assert.Nil(t, err)
   222  	assert.Equal(t, &finalizedBlock1.Payload.Collection, coll)
   223  
   224  	// ensure head is correct
   225  	head, err := snapshot.Head()
   226  	assert.Nil(t, err)
   227  	assert.Equal(t, finalizedBlock1.ID(), head.ID())
   228  }
   229  
   230  // test that no pending blocks are returned when there are none
   231  func (suite *SnapshotSuite) TestPending_NoPendingBlocks() {
   232  
   233  	// first, check that a freshly bootstrapped state has no pending blocks
   234  	suite.Run("freshly bootstrapped state", func() {
   235  		pending, err := suite.state.Final().Pending()
   236  		suite.Require().Nil(err)
   237  		suite.Assert().Len(pending, 0)
   238  	})
   239  
   240  }
   241  
   242  // test that the appropriate pending blocks are included
   243  func (suite *SnapshotSuite) TestPending_WithPendingBlocks() {
   244  
   245  	// check with some finalized blocks
   246  	parent := suite.genesis
   247  	pendings := make([]flow.Identifier, 0, 10)
   248  	for i := 0; i < 10; i++ {
   249  		next := suite.BlockWithParent(parent)
   250  		suite.InsertBlock(next)
   251  		pendings = append(pendings, next.ID())
   252  	}
   253  
   254  	pending, err := suite.state.Final().Pending()
   255  	suite.Require().Nil(err)
   256  	suite.Require().Equal(pendings, pending)
   257  }
   258  
   259  // ensure that pending blocks are included, even they aren't direct children
   260  // of the finalized head
   261  func (suite *SnapshotSuite) TestPending_Grandchildren() {
   262  
   263  	// create 3 levels of children
   264  	suite.InsertSubtree(*suite.genesis, 3, 3)
   265  
   266  	pending, err := suite.state.Final().Pending()
   267  	suite.Require().Nil(err)
   268  
   269  	// we should have 3 + 3^2 + 3^3 = 39 total children
   270  	suite.Assert().Len(pending, 39)
   271  
   272  	// the result must be ordered so that we see parents before their children
   273  	parents := make(map[flow.Identifier]struct{})
   274  	// initialize with the latest finalized block, which is the parent of the
   275  	// first level of children
   276  	parents[suite.genesis.ID()] = struct{}{}
   277  
   278  	for _, blockID := range pending {
   279  		var header flow.Header
   280  		err := suite.db.View(operation.RetrieveHeader(blockID, &header))
   281  		suite.Require().Nil(err)
   282  
   283  		// we must have already seen the parent
   284  		_, seen := parents[header.ParentID]
   285  		suite.Assert().True(seen, "pending list contained child (%x) before parent (%x)", header.ID(), header.ParentID)
   286  
   287  		// mark this block as seen
   288  		parents[header.ID()] = struct{}{}
   289  	}
   290  }
   291  
   292  func (suite *SnapshotSuite) TestParams_ChainID() {
   293  
   294  	chainID, err := suite.state.Params().ChainID()
   295  	suite.Require().Nil(err)
   296  	suite.Assert().Equal(suite.genesis.Header.ChainID, chainID)
   297  }