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 }