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 }