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  }