github.com/koko1123/flow-go-1@v0.29.6/utils/unittest/epoch_builder.go (about) 1 package unittest 2 3 import ( 4 "context" 5 "math/rand" 6 "testing" 7 8 "github.com/stretchr/testify/require" 9 10 "github.com/koko1123/flow-go-1/model/flow" 11 "github.com/koko1123/flow-go-1/model/flow/filter" 12 "github.com/koko1123/flow-go-1/state/protocol" 13 ) 14 15 // EpochHeights is a structure caching the results of building an epoch with 16 // EpochBuilder. It contains the first block height for each phase of the epoch. 17 type EpochHeights struct { 18 Counter uint64 // which epoch this is 19 Staking uint64 // first height of staking phase 20 Setup uint64 // first height of setup phase 21 Committed uint64 // first height of committed phase 22 CommittedFinal uint64 // final height of the committed phase 23 } 24 25 // Range returns the range of all heights that are in this epoch. 26 func (epoch EpochHeights) Range() []uint64 { 27 var heights []uint64 28 for height := epoch.Staking; height <= epoch.CommittedFinal; height++ { 29 heights = append(heights, height) 30 } 31 return heights 32 } 33 34 // StakingRange returns the range of all heights in the staking phase. 35 func (epoch EpochHeights) StakingRange() []uint64 { 36 var heights []uint64 37 for height := epoch.Staking; height < epoch.Setup; height++ { 38 heights = append(heights, height) 39 } 40 return heights 41 } 42 43 // SetupRange returns the range of all heights in the setup phase. 44 func (epoch EpochHeights) SetupRange() []uint64 { 45 var heights []uint64 46 for height := epoch.Setup; height < epoch.Committed; height++ { 47 heights = append(heights, height) 48 } 49 return heights 50 } 51 52 // CommittedRange returns the range of all heights in the committed phase. 53 func (epoch EpochHeights) CommittedRange() []uint64 { 54 var heights []uint64 55 for height := epoch.Committed; height < epoch.CommittedFinal; height++ { 56 heights = append(heights, height) 57 } 58 return heights 59 } 60 61 // EpochBuilder is a testing utility for building epochs into chain state. 62 type EpochBuilder struct { 63 t *testing.T 64 states []protocol.MutableState 65 blocksByID map[flow.Identifier]*flow.Block 66 blocks []*flow.Block 67 built map[uint64]*EpochHeights 68 setupOpts []func(*flow.EpochSetup) // options to apply to the EpochSetup event 69 commitOpts []func(*flow.EpochCommit) // options to apply to the EpochCommit event 70 } 71 72 // NewEpochBuilder returns a new EpochBuilder which will build epochs using the 73 // given states. At least one state must be provided. If more than one are 74 // provided they must have the same initial state. 75 func NewEpochBuilder(t *testing.T, states ...protocol.MutableState) *EpochBuilder { 76 require.True(t, len(states) >= 1, "must provide at least one state") 77 78 builder := &EpochBuilder{ 79 t: t, 80 states: states, 81 blocksByID: make(map[flow.Identifier]*flow.Block), 82 blocks: make([]*flow.Block, 0), 83 built: make(map[uint64]*EpochHeights), 84 } 85 return builder 86 } 87 88 // UsingSetupOpts sets options for the epoch setup event. For options 89 // targeting the same field, those added here will take precedence 90 // over defaults. 91 func (builder *EpochBuilder) UsingSetupOpts(opts ...func(*flow.EpochSetup)) *EpochBuilder { 92 builder.setupOpts = opts 93 return builder 94 } 95 96 // UsingCommitOpts sets options for the epoch setup event. For options 97 // targeting the same field, those added here will take precedence 98 // over defaults. 99 func (builder *EpochBuilder) UsingCommitOpts(opts ...func(*flow.EpochCommit)) *EpochBuilder { 100 builder.commitOpts = opts 101 return builder 102 } 103 104 // EpochHeights returns heights of each phase within about a built epoch. 105 func (builder *EpochBuilder) EpochHeights(counter uint64) (*EpochHeights, bool) { 106 epoch, ok := builder.built[counter] 107 return epoch, ok 108 } 109 110 // BuildEpoch builds and finalizes a sequence of blocks comprising a minimal full 111 // epoch (epoch N). We assume the latest finalized block is within staking phase 112 // in epoch N. 113 // 114 // | EPOCH N | 115 // | | 116 // P A B C D E F G 117 // 118 // +------------+ +------------+ +-----------+ +-----------+ +----------+ +----------+ +----------+----------+ 119 // | ER(P-1) |->| ER(P) |->| ER(A) |->| ER(B) |->| ER(C) |->| ER(D) |->| ER(E) | ER(F) | 120 // | S(ER(P-2)) | | S(ER(P-1)) | | S(ER(P)) | | S(ER(A)) | | S(ER(B)) | | S(ER(C)) | | S(ER(D)) | S(ER(E)) | 121 // +------------+ +------------+ +-----------+ +-----------+ +----------+ +----------+ +----------+----------+ 122 // | | 123 // Setup Commit 124 // 125 // ER(X) := ExecutionReceipt for block X 126 // S(ER(X)) := Seal for the ExecutionResult contained in ER(X) (seals block X) 127 // 128 // A is the latest finalized block. Every block contains a receipt for the 129 // previous block and a seal for the receipt contained in the previous block. 130 // The only exception is when A is the root block, in which case block B does 131 // not contain a receipt for block A, and block C does not contain a seal for 132 // block A. This is because the root block is sealed from genesis and we 133 // can't insert duplicate seals. 134 // 135 // D contains a seal for block B containing the EpochSetup service event. 136 // E contains a QC for D, which causes the EpochSetup to become activated. 137 // 138 // F contains a seal for block D containing the EpochCommit service event. 139 // G contains a QC for F, which causes the EpochCommit to become activated. 140 // 141 // To build a sequence of epochs, we call BuildEpoch, then CompleteEpoch, and so on. 142 // 143 // Upon building an epoch N (preparing epoch N+1), we store some information 144 // about the heights of blocks in the BUILT epoch (epoch N). These can be 145 // queried with EpochHeights. 146 func (builder *EpochBuilder) BuildEpoch() *EpochBuilder { 147 148 state := builder.states[0] 149 150 // prepare default values for the service events based on the current state 151 identities, err := state.Final().Identities(filter.Any) 152 require.Nil(builder.t, err) 153 epoch := state.Final().Epochs().Current() 154 counter, err := epoch.Counter() 155 require.Nil(builder.t, err) 156 finalView, err := epoch.FinalView() 157 require.Nil(builder.t, err) 158 159 // retrieve block A 160 A, err := state.Final().Head() 161 require.Nil(builder.t, err) 162 163 // check that block A satisfies initial condition 164 phase, err := state.Final().Phase() 165 require.Nil(builder.t, err) 166 require.Equal(builder.t, flow.EpochPhaseStaking, phase) 167 168 // Define receipts and seals for block B payload. They will be nil if A is 169 // the root block 170 var receiptA *flow.ExecutionReceipt 171 var prevReceipts []*flow.ExecutionReceiptMeta 172 var prevResults []*flow.ExecutionResult 173 var sealsForPrev []*flow.Seal 174 175 aBlock, ok := builder.blocksByID[A.ID()] 176 if ok { 177 // A is not the root block. B will contain a receipt for A, and a seal 178 // for the receipt contained in A. 179 receiptA = ReceiptForBlockFixture(aBlock) 180 prevReceipts = []*flow.ExecutionReceiptMeta{ 181 receiptA.Meta(), 182 } 183 prevResults = []*flow.ExecutionResult{ 184 &receiptA.ExecutionResult, 185 } 186 resultByID := aBlock.Payload.Results.Lookup() 187 sealsForPrev = []*flow.Seal{ 188 Seal.Fixture(Seal.WithResult(resultByID[aBlock.Payload.Receipts[0].ResultID])), 189 } 190 } 191 192 // defaults for the EpochSetup event 193 setupDefaults := []func(*flow.EpochSetup){ 194 WithParticipants(identities), 195 SetupWithCounter(counter + 1), 196 WithFirstView(finalView + 1), 197 WithFinalView(finalView + 1_000_000), 198 } 199 setup := EpochSetupFixture(append(setupDefaults, builder.setupOpts...)...) 200 201 // build block B, sealing up to and including block A 202 B := BlockWithParentFixture(A) 203 B.SetPayload(flow.Payload{ 204 Receipts: prevReceipts, 205 Results: prevResults, 206 Seals: sealsForPrev, 207 }) 208 209 builder.addBlock(B) 210 211 // create a receipt for block B, to be included in block C 212 // the receipt for B contains the EpochSetup event 213 receiptB := ReceiptForBlockFixture(B) 214 receiptB.ExecutionResult.ServiceEvents = []flow.ServiceEvent{setup.ServiceEvent()} 215 216 // insert block C with a receipt for block B, and a seal for the receipt in 217 // block B if there was one 218 C := BlockWithParentFixture(B.Header) 219 var sealsForA []*flow.Seal 220 if receiptA != nil { 221 sealsForA = []*flow.Seal{ 222 Seal.Fixture(Seal.WithResult(&receiptA.ExecutionResult)), 223 } 224 } 225 C.SetPayload(flow.Payload{ 226 Receipts: []*flow.ExecutionReceiptMeta{receiptB.Meta()}, 227 Results: []*flow.ExecutionResult{&receiptB.ExecutionResult}, 228 Seals: sealsForA, 229 }) 230 builder.addBlock(C) 231 // create a receipt for block C, to be included in block D 232 receiptC := ReceiptForBlockFixture(C) 233 234 // build block D 235 // D contains a seal for block B and a receipt for block C 236 D := BlockWithParentFixture(C.Header) 237 sealForB := Seal.Fixture( 238 Seal.WithResult(&receiptB.ExecutionResult), 239 ) 240 D.SetPayload(flow.Payload{ 241 Receipts: []*flow.ExecutionReceiptMeta{receiptC.Meta()}, 242 Results: []*flow.ExecutionResult{&receiptC.ExecutionResult}, 243 Seals: []*flow.Seal{sealForB}, 244 }) 245 builder.addBlock(D) 246 247 // defaults for the EpochCommit event 248 commitDefaults := []func(*flow.EpochCommit){ 249 CommitWithCounter(counter + 1), 250 WithDKGFromParticipants(setup.Participants), 251 WithClusterQCsFromAssignments(setup.Assignments), 252 } 253 commit := EpochCommitFixture(append(commitDefaults, builder.commitOpts...)...) 254 255 // create receipt for block D, to be included in block E 256 // the receipt for block D contains the EpochCommit event 257 receiptD := ReceiptForBlockFixture(D) 258 receiptD.ExecutionResult.ServiceEvents = []flow.ServiceEvent{commit.ServiceEvent()} 259 260 // build block E 261 // E contains a seal for C and a receipt for D 262 E := BlockWithParentFixture(D.Header) 263 sealForC := Seal.Fixture( 264 Seal.WithResult(&receiptC.ExecutionResult), 265 ) 266 E.SetPayload(flow.Payload{ 267 Receipts: []*flow.ExecutionReceiptMeta{receiptD.Meta()}, 268 Results: []*flow.ExecutionResult{&receiptD.ExecutionResult}, 269 Seals: []*flow.Seal{sealForC}, 270 }) 271 builder.addBlock(E) 272 // create receipt for block E 273 receiptE := ReceiptForBlockFixture(E) 274 275 // build block F 276 // F contains a seal for block D and the EpochCommit event, as well as a 277 // receipt for block E 278 F := BlockWithParentFixture(E.Header) 279 sealForD := Seal.Fixture( 280 Seal.WithResult(&receiptD.ExecutionResult), 281 ) 282 F.SetPayload(flow.Payload{ 283 Receipts: []*flow.ExecutionReceiptMeta{receiptE.Meta()}, 284 Results: []*flow.ExecutionResult{&receiptE.ExecutionResult}, 285 Seals: []*flow.Seal{sealForD}, 286 }) 287 builder.addBlock(F) 288 // create receipt for block F 289 receiptF := ReceiptForBlockFixture(F) 290 291 // build block G 292 // G contains a seal for block E and a receipt for block F 293 G := BlockWithParentFixture(F.Header) 294 sealForE := Seal.Fixture( 295 Seal.WithResult(&receiptE.ExecutionResult), 296 ) 297 G.SetPayload(flow.Payload{ 298 Receipts: []*flow.ExecutionReceiptMeta{receiptF.Meta()}, 299 Results: []*flow.ExecutionResult{&receiptF.ExecutionResult}, 300 Seals: []*flow.Seal{sealForE}, 301 }) 302 303 builder.addBlock(G) 304 305 // cache information about the built epoch 306 builder.built[counter] = &EpochHeights{ 307 Counter: counter, 308 Staking: A.Height, 309 Setup: E.Header.Height, 310 Committed: G.Header.Height, 311 CommittedFinal: G.Header.Height, 312 } 313 314 return builder 315 } 316 317 // CompleteEpoch caps off the current epoch by building the first block of the next 318 // epoch. We must be in the Committed phase to call CompleteEpoch. Once the epoch 319 // has been capped off, we can build the next epoch with BuildEpoch. 320 func (builder *EpochBuilder) CompleteEpoch() *EpochBuilder { 321 322 state := builder.states[0] 323 324 phase, err := state.Final().Phase() 325 require.Nil(builder.t, err) 326 require.Equal(builder.t, flow.EpochPhaseCommitted, phase) 327 finalView, err := state.Final().Epochs().Current().FinalView() 328 require.Nil(builder.t, err) 329 330 final, err := state.Final().Head() 331 require.Nil(builder.t, err) 332 333 finalBlock, ok := builder.blocksByID[final.ID()] 334 require.True(builder.t, ok) 335 336 // A is the first block of the next epoch (see diagram in BuildEpoch) 337 A := BlockWithParentFixture(final) 338 // first view is not necessarily exactly final view of previous epoch 339 A.Header.View = finalView + (rand.Uint64() % 4) + 1 340 finalReceipt := ReceiptForBlockFixture(finalBlock) 341 A.SetPayload(flow.Payload{ 342 Receipts: []*flow.ExecutionReceiptMeta{ 343 finalReceipt.Meta(), 344 }, 345 Results: []*flow.ExecutionResult{ 346 &finalReceipt.ExecutionResult, 347 }, 348 Seals: []*flow.Seal{ 349 Seal.Fixture( 350 Seal.WithResult(finalBlock.Payload.Results[0]), 351 ), 352 }, 353 }) 354 builder.addBlock(A) 355 356 return builder 357 } 358 359 // BuildBlocks builds empty blocks on top of the finalized state. It is used 360 // to build epochs that are not the minimum possible length, which is the 361 // default result from chaining BuildEpoch and CompleteEpoch. 362 func (builder *EpochBuilder) BuildBlocks(n uint) { 363 head, err := builder.states[0].Final().Head() 364 require.NoError(builder.t, err) 365 for i := uint(0); i < n; i++ { 366 next := BlockWithParentFixture(head) 367 builder.addBlock(next) 368 head = next.Header 369 } 370 } 371 372 // addBlock adds the given block to the state by: extending the state, 373 // finalizing the block, marking the block as valid, and caching the block. 374 func (builder *EpochBuilder) addBlock(block *flow.Block) { 375 blockID := block.ID() 376 for _, state := range builder.states { 377 err := state.Extend(context.Background(), block) 378 require.NoError(builder.t, err) 379 380 err = state.MarkValid(blockID) 381 require.NoError(builder.t, err) 382 383 err = state.Finalize(context.Background(), blockID) 384 require.NoError(builder.t, err) 385 } 386 387 builder.blocksByID[block.ID()] = block 388 builder.blocks = append(builder.blocks, block) 389 } 390 391 // AddBlocksWithSeals for the n number of blocks specified this func 392 // will add a seal for the second highest block in the state and a 393 // receipt for the highest block in state to the given block before adding it to the state. 394 // NOTE: This func should only be used after BuildEpoch to extend the commit phase 395 func (builder *EpochBuilder) AddBlocksWithSeals(n int, counter uint64) *EpochBuilder { 396 for i := 0; i < n; i++ { 397 // Given the last 2 blocks in state A <- B when we add block C it will contain the following. 398 // - seal for A 399 // - execution result for B 400 b := builder.blocks[len(builder.blocks)-1] 401 402 receiptB := ReceiptForBlockFixture(b) 403 404 block := BlockWithParentFixture(b.Header) 405 seal := Seal.Fixture( 406 Seal.WithResult(b.Payload.Results[0]), 407 ) 408 409 payload := PayloadFixture( 410 WithReceipts(receiptB), 411 WithSeals(seal), 412 ) 413 block.SetPayload(payload) 414 415 builder.addBlock(block) 416 417 // update cache information about the built epoch 418 // we have extended the commit phase 419 builder.built[counter].CommittedFinal = block.Header.Height 420 } 421 422 return builder 423 }