github.com/koko1123/flow-go-1@v0.29.6/state/cluster/badger/mutator_test.go (about)

     1  package badger
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	"math/rand"
     8  	"os"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/dgraph-io/badger/v3"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  	"github.com/stretchr/testify/suite"
    16  
    17  	model "github.com/koko1123/flow-go-1/model/cluster"
    18  	"github.com/koko1123/flow-go-1/model/flow"
    19  	"github.com/koko1123/flow-go-1/module/metrics"
    20  	"github.com/koko1123/flow-go-1/module/trace"
    21  	"github.com/koko1123/flow-go-1/state"
    22  	"github.com/koko1123/flow-go-1/state/cluster"
    23  	"github.com/koko1123/flow-go-1/state/protocol"
    24  	pbadger "github.com/koko1123/flow-go-1/state/protocol/badger"
    25  	"github.com/koko1123/flow-go-1/state/protocol/events"
    26  	"github.com/koko1123/flow-go-1/state/protocol/inmem"
    27  	protocolutil "github.com/koko1123/flow-go-1/state/protocol/util"
    28  	storage "github.com/koko1123/flow-go-1/storage/badger"
    29  	"github.com/koko1123/flow-go-1/storage/badger/operation"
    30  	"github.com/koko1123/flow-go-1/storage/badger/procedure"
    31  	"github.com/koko1123/flow-go-1/storage/util"
    32  	"github.com/koko1123/flow-go-1/utils/unittest"
    33  )
    34  
    35  type MutatorSuite struct {
    36  	suite.Suite
    37  	db    *badger.DB
    38  	dbdir string
    39  
    40  	genesis *model.Block
    41  	chainID flow.ChainID
    42  
    43  	// protocol state for reference blocks for transactions
    44  	protoState   protocol.MutableState
    45  	protoGenesis *flow.Header
    46  
    47  	state cluster.MutableState
    48  }
    49  
    50  // runs before each test runs
    51  func (suite *MutatorSuite) SetupTest() {
    52  	var err error
    53  
    54  	// seed the RNG
    55  	rand.Seed(time.Now().UnixNano())
    56  
    57  	suite.genesis = model.Genesis()
    58  	suite.chainID = suite.genesis.Header.ChainID
    59  
    60  	suite.dbdir = unittest.TempDir(suite.T())
    61  	suite.db = unittest.BadgerDB(suite.T(), suite.dbdir)
    62  
    63  	metrics := metrics.NewNoopCollector()
    64  	tracer := trace.NewNoopTracer()
    65  	headers, _, seals, index, conPayloads, blocks, setups, commits, statuses, results := util.StorageLayer(suite.T(), suite.db)
    66  	colPayloads := storage.NewClusterPayloads(metrics, suite.db)
    67  
    68  	clusterStateRoot, err := NewStateRoot(suite.genesis)
    69  	suite.NoError(err)
    70  	clusterState, err := Bootstrap(suite.db, clusterStateRoot)
    71  	suite.Assert().Nil(err)
    72  	suite.state, err = NewMutableState(clusterState, tracer, headers, colPayloads)
    73  	suite.Assert().Nil(err)
    74  	consumer := events.NewNoop()
    75  
    76  	// just bootstrap with a genesis block, we'll use this as reference
    77  	participants := unittest.IdentityListFixture(5, unittest.WithAllRoles())
    78  	genesis, result, seal := unittest.BootstrapFixture(participants)
    79  	qc := unittest.QuorumCertificateFixture(unittest.QCWithBlockID(genesis.ID()))
    80  	// ensure we don't enter a new epoch for tests that build many blocks
    81  	result.ServiceEvents[0].Event.(*flow.EpochSetup).FinalView = genesis.Header.View + 100000
    82  	seal.ResultID = result.ID()
    83  
    84  	rootSnapshot, err := inmem.SnapshotFromBootstrapState(genesis, result, seal, qc)
    85  	require.NoError(suite.T(), err)
    86  
    87  	suite.protoGenesis = genesis.Header
    88  
    89  	state, err := pbadger.Bootstrap(metrics, suite.db, headers, seals, results, blocks, setups, commits, statuses, rootSnapshot)
    90  	require.NoError(suite.T(), err)
    91  
    92  	suite.protoState, err = pbadger.NewFollowerState(state, index, conPayloads, tracer, consumer, protocolutil.MockBlockTimer())
    93  	require.NoError(suite.T(), err)
    94  }
    95  
    96  // runs after each test finishes
    97  func (suite *MutatorSuite) TearDownTest() {
    98  	err := suite.db.Close()
    99  	suite.Assert().Nil(err)
   100  	err = os.RemoveAll(suite.dbdir)
   101  	suite.Assert().Nil(err)
   102  }
   103  
   104  // Payload returns a valid cluster block payload containing the given transactions.
   105  func (suite *MutatorSuite) Payload(transactions ...*flow.TransactionBody) model.Payload {
   106  	final, err := suite.protoState.Final().Head()
   107  	suite.Require().Nil(err)
   108  
   109  	// find the oldest reference block among the transactions
   110  	minRefID := final.ID() // use final by default
   111  	minRefHeight := uint64(math.MaxUint64)
   112  	for _, tx := range transactions {
   113  		refBlock, err := suite.protoState.AtBlockID(tx.ReferenceBlockID).Head()
   114  		if err != nil {
   115  			continue
   116  		}
   117  		if refBlock.Height < minRefHeight {
   118  			minRefHeight = refBlock.Height
   119  			minRefID = refBlock.ID()
   120  		}
   121  	}
   122  	return model.PayloadFromTransactions(minRefID, transactions...)
   123  }
   124  
   125  // BlockWithParent returns a valid block with the given parent.
   126  func (suite *MutatorSuite) BlockWithParent(parent *model.Block) model.Block {
   127  	block := unittest.ClusterBlockWithParent(parent)
   128  	payload := suite.Payload()
   129  	block.SetPayload(payload)
   130  	return block
   131  }
   132  
   133  // Block returns a valid cluster block with genesis as parent.
   134  func (suite *MutatorSuite) Block() model.Block {
   135  	return suite.BlockWithParent(suite.genesis)
   136  }
   137  
   138  func (suite *MutatorSuite) FinalizeBlock(block model.Block) {
   139  	err := suite.db.Update(func(tx *badger.Txn) error {
   140  		var refBlock flow.Header
   141  		err := operation.RetrieveHeader(block.Payload.ReferenceBlockID, &refBlock)(tx)
   142  		if err != nil {
   143  			return err
   144  		}
   145  		err = procedure.FinalizeClusterBlock(block.ID())(tx)
   146  		if err != nil {
   147  			return err
   148  		}
   149  		err = operation.IndexClusterBlockByReferenceHeight(refBlock.Height, block.ID())(tx)
   150  		return err
   151  	})
   152  	suite.Assert().NoError(err)
   153  }
   154  
   155  func (suite *MutatorSuite) Tx(opts ...func(*flow.TransactionBody)) flow.TransactionBody {
   156  	final, err := suite.protoState.Final().Head()
   157  	suite.Require().Nil(err)
   158  
   159  	tx := unittest.TransactionBodyFixture(opts...)
   160  	tx.ReferenceBlockID = final.ID()
   161  	return tx
   162  }
   163  
   164  func TestMutator(t *testing.T) {
   165  	suite.Run(t, new(MutatorSuite))
   166  }
   167  
   168  func (suite *MutatorSuite) TestBootstrap_InvalidNumber() {
   169  	suite.genesis.Header.Height = 1
   170  
   171  	_, err := NewStateRoot(suite.genesis)
   172  	suite.Assert().Error(err)
   173  }
   174  
   175  func (suite *MutatorSuite) TestBootstrap_InvalidParentHash() {
   176  	suite.genesis.Header.ParentID = unittest.IdentifierFixture()
   177  
   178  	_, err := NewStateRoot(suite.genesis)
   179  	suite.Assert().Error(err)
   180  }
   181  
   182  func (suite *MutatorSuite) TestBootstrap_InvalidPayloadHash() {
   183  	suite.genesis.Header.PayloadHash = unittest.IdentifierFixture()
   184  
   185  	_, err := NewStateRoot(suite.genesis)
   186  	suite.Assert().Error(err)
   187  }
   188  
   189  func (suite *MutatorSuite) TestBootstrap_InvalidPayload() {
   190  	// this is invalid because genesis collection should be empty
   191  	suite.genesis.Payload = unittest.ClusterPayloadFixture(2)
   192  
   193  	_, err := NewStateRoot(suite.genesis)
   194  	suite.Assert().Error(err)
   195  }
   196  
   197  func (suite *MutatorSuite) TestBootstrap_Successful() {
   198  	err := suite.db.View(func(tx *badger.Txn) error {
   199  
   200  		// should insert collection
   201  		var collection flow.LightCollection
   202  		err := operation.RetrieveCollection(suite.genesis.Payload.Collection.ID(), &collection)(tx)
   203  		suite.Assert().Nil(err)
   204  		suite.Assert().Equal(suite.genesis.Payload.Collection.Light(), collection)
   205  
   206  		// should index collection
   207  		collection = flow.LightCollection{} // reset the collection
   208  		err = operation.LookupCollectionPayload(suite.genesis.ID(), &collection.Transactions)(tx)
   209  		suite.Assert().Nil(err)
   210  		suite.Assert().Equal(suite.genesis.Payload.Collection.Light(), collection)
   211  
   212  		// should insert header
   213  		var header flow.Header
   214  		err = operation.RetrieveHeader(suite.genesis.ID(), &header)(tx)
   215  		suite.Assert().Nil(err)
   216  		suite.Assert().Equal(suite.genesis.Header.ID(), header.ID())
   217  
   218  		// should insert block height -> ID lookup
   219  		var blockID flow.Identifier
   220  		err = operation.LookupClusterBlockHeight(suite.genesis.Header.ChainID, suite.genesis.Header.Height, &blockID)(tx)
   221  		suite.Assert().Nil(err)
   222  		suite.Assert().Equal(suite.genesis.ID(), blockID)
   223  
   224  		// should insert boundary
   225  		var boundary uint64
   226  		err = operation.RetrieveClusterFinalizedHeight(suite.genesis.Header.ChainID, &boundary)(tx)
   227  		suite.Assert().Nil(err)
   228  		suite.Assert().Equal(suite.genesis.Header.Height, boundary)
   229  
   230  		return nil
   231  	})
   232  	suite.Assert().Nil(err)
   233  }
   234  
   235  func (suite *MutatorSuite) TestExtend_WithoutBootstrap() {
   236  	block := unittest.ClusterBlockWithParent(suite.genesis)
   237  	err := suite.state.Extend(&block)
   238  	suite.Assert().Error(err)
   239  }
   240  
   241  func (suite *MutatorSuite) TestExtend_InvalidChainID() {
   242  	block := suite.Block()
   243  	// change the chain ID
   244  	block.Header.ChainID = flow.ChainID(fmt.Sprintf("%s-invalid", block.Header.ChainID))
   245  
   246  	err := suite.state.Extend(&block)
   247  	suite.Assert().Error(err)
   248  	suite.Assert().True(state.IsInvalidExtensionError(err))
   249  }
   250  
   251  func (suite *MutatorSuite) TestExtend_InvalidBlockNumber() {
   252  	block := suite.Block()
   253  	// change the block height
   254  	block.Header.Height = block.Header.Height - 1
   255  
   256  	err := suite.state.Extend(&block)
   257  	suite.Assert().Error(err)
   258  	suite.Assert().True(state.IsInvalidExtensionError(err))
   259  }
   260  
   261  func (suite *MutatorSuite) TestExtend_DuplicateTxInPayload() {
   262  	block := suite.Block()
   263  	// add the same transaction to a payload twice
   264  	tx := suite.Tx()
   265  	payload := suite.Payload(&tx, &tx)
   266  	block.SetPayload(payload)
   267  
   268  	// should fail to extend block with invalid payload
   269  	err := suite.state.Extend(&block)
   270  	suite.Assert().Error(err)
   271  	suite.Assert().True(state.IsInvalidExtensionError(err))
   272  }
   273  
   274  func (suite *MutatorSuite) TestExtend_OnParentOfFinalized() {
   275  	// build one block on top of genesis
   276  	block1 := suite.Block()
   277  	err := suite.state.Extend(&block1)
   278  	suite.Assert().Nil(err)
   279  
   280  	// finalize the block
   281  	suite.FinalizeBlock(block1)
   282  
   283  	// insert another block on top of genesis
   284  	// since we have already finalized block 1, this is invalid
   285  	block2 := suite.Block()
   286  
   287  	// try to extend with the invalid block
   288  	err = suite.state.Extend(&block2)
   289  	suite.Assert().Error(err)
   290  	suite.Assert().True(state.IsOutdatedExtensionError(err))
   291  }
   292  
   293  func (suite *MutatorSuite) TestExtend_Success() {
   294  	block := suite.Block()
   295  	err := suite.state.Extend(&block)
   296  	suite.Assert().Nil(err)
   297  
   298  	// should be able to retrieve the block
   299  	var extended model.Block
   300  	err = suite.db.View(procedure.RetrieveClusterBlock(block.ID(), &extended))
   301  	suite.Assert().Nil(err)
   302  	suite.Assert().Equal(*block.Payload, *extended.Payload)
   303  
   304  	// the block should be indexed by its parent
   305  	var childIDs flow.IdentifierList
   306  	err = suite.db.View(procedure.LookupBlockChildren(suite.genesis.ID(), &childIDs))
   307  	suite.Assert().Nil(err)
   308  	suite.Require().Len(childIDs, 1)
   309  	suite.Assert().Equal(block.ID(), childIDs[0])
   310  }
   311  
   312  func (suite *MutatorSuite) TestExtend_WithEmptyCollection() {
   313  	block := suite.Block()
   314  	// set an empty collection as the payload
   315  	block.SetPayload(suite.Payload())
   316  	err := suite.state.Extend(&block)
   317  	suite.Assert().Nil(err)
   318  }
   319  
   320  // an unknown reference block is unverifiable
   321  func (suite *MutatorSuite) TestExtend_WithNonExistentReferenceBlock() {
   322  	block := suite.Block()
   323  	tx := suite.Tx()
   324  	payload := suite.Payload(&tx)
   325  	// set a random reference block ID
   326  	payload.ReferenceBlockID = unittest.IdentifierFixture()
   327  	block.SetPayload(payload)
   328  	err := suite.state.Extend(&block)
   329  	suite.Assert().Error(err)
   330  	suite.Assert().True(state.IsUnverifiableExtensionError(err))
   331  }
   332  
   333  // a collection with an expired reference block is a VALID extension of chain state
   334  func (suite *MutatorSuite) TestExtend_WithExpiredReferenceBlock() {
   335  	// build enough blocks so that using genesis as a reference block causes
   336  	// the collection to be expired
   337  	parent := suite.protoGenesis
   338  	for i := 0; i < flow.DefaultTransactionExpiry+1; i++ {
   339  		next := unittest.BlockWithParentFixture(parent)
   340  		next.Payload.Guarantees = nil
   341  		next.SetPayload(*next.Payload)
   342  		err := suite.protoState.Extend(context.Background(), next)
   343  		suite.Require().Nil(err)
   344  		err = suite.protoState.Finalize(context.Background(), next.ID())
   345  		suite.Require().Nil(err)
   346  		parent = next.Header
   347  	}
   348  
   349  	block := suite.Block()
   350  	// set genesis as reference block
   351  	block.SetPayload(model.EmptyPayload(suite.protoGenesis.ID()))
   352  	err := suite.state.Extend(&block)
   353  	suite.Assert().Nil(err)
   354  }
   355  
   356  func (suite *MutatorSuite) TestExtend_WithReferenceBlockFromClusterChain() {
   357  	// TODO skipping as this isn't implemented yet
   358  	unittest.SkipUnless(suite.T(), unittest.TEST_TODO, "skipping as this isn't implemented yet")
   359  
   360  	block := suite.Block()
   361  	// set genesis from cluster chain as reference block
   362  	block.SetPayload(model.EmptyPayload(suite.genesis.ID()))
   363  	err := suite.state.Extend(&block)
   364  	suite.Assert().Error(err)
   365  }
   366  
   367  func (suite *MutatorSuite) TestExtend_UnfinalizedBlockWithDupeTx() {
   368  	tx1 := suite.Tx()
   369  
   370  	// create a block extending genesis containing tx1
   371  	block1 := suite.Block()
   372  	payload1 := suite.Payload(&tx1)
   373  	block1.SetPayload(payload1)
   374  
   375  	// should be able to extend block 1
   376  	err := suite.state.Extend(&block1)
   377  	suite.Assert().Nil(err)
   378  
   379  	// create a block building on block1 ALSO containing tx1
   380  	block2 := suite.BlockWithParent(&block1)
   381  	payload2 := suite.Payload(&tx1)
   382  	block2.SetPayload(payload2)
   383  
   384  	// should be unable to extend block 2, as it contains a dupe transaction
   385  	err = suite.state.Extend(&block2)
   386  	suite.Assert().Error(err)
   387  	suite.Assert().True(state.IsInvalidExtensionError(err))
   388  }
   389  
   390  func (suite *MutatorSuite) TestExtend_FinalizedBlockWithDupeTx() {
   391  	tx1 := suite.Tx()
   392  
   393  	// create a block extending genesis containing tx1
   394  	block1 := suite.Block()
   395  	payload1 := suite.Payload(&tx1)
   396  	block1.SetPayload(payload1)
   397  
   398  	// should be able to extend block 1
   399  	err := suite.state.Extend(&block1)
   400  	suite.Assert().Nil(err)
   401  
   402  	// should be able to finalize block 1
   403  	suite.FinalizeBlock(block1)
   404  	suite.Assert().Nil(err)
   405  
   406  	// create a block building on block1 ALSO containing tx1
   407  	block2 := suite.BlockWithParent(&block1)
   408  	payload2 := suite.Payload(&tx1)
   409  	block2.SetPayload(payload2)
   410  
   411  	// should be unable to extend block 2, as it contains a dupe transaction
   412  	err = suite.state.Extend(&block2)
   413  	suite.Assert().Error(err)
   414  	suite.Assert().True(state.IsInvalidExtensionError(err))
   415  }
   416  
   417  func (suite *MutatorSuite) TestExtend_ConflictingForkWithDupeTx() {
   418  	tx1 := suite.Tx()
   419  
   420  	// create a block extending genesis containing tx1
   421  	block1 := suite.Block()
   422  	payload1 := suite.Payload(&tx1)
   423  	block1.SetPayload(payload1)
   424  
   425  	// should be able to extend block 1
   426  	err := suite.state.Extend(&block1)
   427  	suite.Assert().Nil(err)
   428  
   429  	// create a block ALSO extending genesis ALSO containing tx1
   430  	block2 := suite.Block()
   431  	payload2 := suite.Payload(&tx1)
   432  	block2.SetPayload(payload2)
   433  
   434  	// should be able to extend block2
   435  	// although it conflicts with block1, it is on a different fork
   436  	err = suite.state.Extend(&block2)
   437  	suite.Assert().Nil(err)
   438  }
   439  
   440  func (suite *MutatorSuite) TestExtend_LargeHistory() {
   441  	t := suite.T()
   442  
   443  	// get a valid reference block ID
   444  	final, err := suite.protoState.Final().Head()
   445  	require.NoError(t, err)
   446  	refID := final.ID()
   447  
   448  	// keep track of the head of the chain
   449  	head := *suite.genesis
   450  
   451  	// keep track of transactions in orphaned forks (eligible for inclusion in future block)
   452  	var invalidatedTransactions []*flow.TransactionBody
   453  	// keep track of the oldest transactions (further back in ancestry than the expiry window)
   454  	var oldTransactions []*flow.TransactionBody
   455  
   456  	// create a large history of blocks with invalidated forks every 3 blocks on
   457  	// average - build until the height exceeds transaction expiry
   458  	for i := 0; ; i++ {
   459  
   460  		// create a transaction
   461  		tx := unittest.TransactionBodyFixture(func(tx *flow.TransactionBody) {
   462  			tx.ReferenceBlockID = refID
   463  			tx.ProposalKey.SequenceNumber = uint64(i)
   464  		})
   465  
   466  		// 1/3 of the time create a conflicting fork that will be invalidated
   467  		// don't do this the first and last few times to ensure we don't
   468  		// try to fork genesis and the last block is the valid fork.
   469  		conflicting := rand.Intn(3) == 0 && i > 5 && i < 995
   470  
   471  		// by default, build on the head - if we are building a
   472  		// conflicting fork, build on the parent of the head
   473  		parent := head
   474  		if conflicting {
   475  			err = suite.db.View(procedure.RetrieveClusterBlock(parent.Header.ParentID, &parent))
   476  			assert.NoError(t, err)
   477  			// add the transaction to the invalidated list
   478  			invalidatedTransactions = append(invalidatedTransactions, &tx)
   479  		} else if head.Header.Height < 50 {
   480  			oldTransactions = append(oldTransactions, &tx)
   481  		}
   482  
   483  		// create a block containing the transaction
   484  		block := unittest.ClusterBlockWithParent(&head)
   485  		payload := suite.Payload(&tx)
   486  		block.SetPayload(payload)
   487  		err = suite.state.Extend(&block)
   488  		assert.NoError(t, err)
   489  
   490  		// reset the valid head if we aren't building a conflicting fork
   491  		if !conflicting {
   492  			head = block
   493  			suite.FinalizeBlock(block)
   494  			assert.NoError(t, err)
   495  		}
   496  
   497  		// stop building blocks once we've built a history which exceeds the transaction
   498  		// expiry length - this tests that deduplication works properly against old blocks
   499  		// which nevertheless have a potentially conflicting reference block
   500  		if head.Header.Height > flow.DefaultTransactionExpiry+100 {
   501  			break
   502  		}
   503  	}
   504  
   505  	t.Log("conflicting: ", len(invalidatedTransactions))
   506  
   507  	t.Run("should be able to extend with transactions in orphaned forks", func(t *testing.T) {
   508  		block := unittest.ClusterBlockWithParent(&head)
   509  		payload := suite.Payload(invalidatedTransactions...)
   510  		block.SetPayload(payload)
   511  		err = suite.state.Extend(&block)
   512  		assert.NoError(t, err)
   513  	})
   514  
   515  	t.Run("should be unable to extend with conflicting transactions within reference height range of extending block", func(t *testing.T) {
   516  		block := unittest.ClusterBlockWithParent(&head)
   517  		payload := suite.Payload(oldTransactions...)
   518  		block.SetPayload(payload)
   519  		err = suite.state.Extend(&block)
   520  		assert.Error(t, err)
   521  		suite.Assert().True(state.IsInvalidExtensionError(err))
   522  	})
   523  }