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 }