github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/forks/block_builder_test.go (about)

     1  package forks
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/onflow/flow-go/consensus/hotstuff/helper"
     7  	"github.com/onflow/flow-go/consensus/hotstuff/model"
     8  	"github.com/onflow/flow-go/model/flow"
     9  )
    10  
    11  // BlockView specifies the data to create a block
    12  type BlockView struct {
    13  	// View is the view of the block to be created
    14  	View uint64
    15  	// BlockVersion is the version of the block for that view.
    16  	// Useful for creating conflicting blocks at the same view.
    17  	BlockVersion int
    18  	// QCView is the view of the QC embedded in this block (also: the view of the block's parent)
    19  	QCView uint64
    20  	// QCVersion is the version of the QC for that view.
    21  	QCVersion int
    22  }
    23  
    24  // QCIndex returns a unique identifier for the block's QC.
    25  func (bv *BlockView) QCIndex() string {
    26  	return fmt.Sprintf("%v-%v", bv.QCView, bv.QCVersion)
    27  }
    28  
    29  // BlockIndex returns a unique identifier for the block.
    30  func (bv *BlockView) BlockIndex() string {
    31  	return fmt.Sprintf("%v-%v", bv.View, bv.BlockVersion)
    32  }
    33  
    34  // BlockBuilder is a test utility for creating block structure fixtures.
    35  type BlockBuilder struct {
    36  	blockViews []*BlockView
    37  }
    38  
    39  func NewBlockBuilder() *BlockBuilder {
    40  	return &BlockBuilder{
    41  		blockViews: make([]*BlockView, 0),
    42  	}
    43  }
    44  
    45  // Add adds a block with the given qcView and blockView. Returns self-reference for chaining.
    46  func (bb *BlockBuilder) Add(qcView uint64, blockView uint64) *BlockBuilder {
    47  	bb.blockViews = append(bb.blockViews, &BlockView{
    48  		View:   blockView,
    49  		QCView: qcView,
    50  	})
    51  	return bb
    52  }
    53  
    54  // GenesisBlock returns the genesis block, which is always finalized.
    55  func (bb *BlockBuilder) GenesisBlock() *model.CertifiedBlock {
    56  	return makeGenesis()
    57  }
    58  
    59  // AddVersioned adds a block with the given qcView and blockView.
    60  // In addition, the version identifier of the QC embedded within the block
    61  // is specified by `qcVersion`. The version identifier for the block itself
    62  // (primarily for emulating different payloads) is specified by `blockVersion`.
    63  // [(◄3) 4] denotes a block of view 4, with a qc for view 3
    64  // [(◄3) 4'] denotes a block of view 4 that is different than [(◄3) 4], with a qc for view 3
    65  // [(◄3) 4'] can be created by AddVersioned(3, 4, 0, 1)
    66  // [(◄3') 4] can be created by AddVersioned(3, 4, 1, 0)
    67  // Returns self-reference for chaining.
    68  func (bb *BlockBuilder) AddVersioned(qcView uint64, blockView uint64, qcVersion int, blockVersion int) *BlockBuilder {
    69  	bb.blockViews = append(bb.blockViews, &BlockView{
    70  		View:         blockView,
    71  		QCView:       qcView,
    72  		BlockVersion: blockVersion,
    73  		QCVersion:    qcVersion,
    74  	})
    75  	return bb
    76  }
    77  
    78  // Proposals returns a list of all proposals added to the BlockBuilder.
    79  // Returns an error if the blocks do not form a connected tree rooted at genesis.
    80  func (bb *BlockBuilder) Proposals() ([]*model.Proposal, error) {
    81  	blocks := make([]*model.Proposal, 0, len(bb.blockViews))
    82  
    83  	genesisBlock := makeGenesis()
    84  	genesisBV := &BlockView{
    85  		View:   genesisBlock.Block.View,
    86  		QCView: genesisBlock.CertifyingQC.View,
    87  	}
    88  
    89  	qcs := make(map[string]*flow.QuorumCertificate)
    90  	qcs[genesisBV.QCIndex()] = genesisBlock.CertifyingQC
    91  
    92  	for _, bv := range bb.blockViews {
    93  		qc, ok := qcs[bv.QCIndex()]
    94  		if !ok {
    95  			return nil, fmt.Errorf("test fail: no qc found for qc index: %v", bv.QCIndex())
    96  		}
    97  		payloadHash := makePayloadHash(bv.View, qc, bv.BlockVersion)
    98  		var lastViewTC *flow.TimeoutCertificate
    99  		if qc.View+1 != bv.View {
   100  			lastViewTC = helper.MakeTC(helper.WithTCView(bv.View - 1))
   101  		}
   102  		proposal := &model.Proposal{
   103  			Block: &model.Block{
   104  				View:        bv.View,
   105  				QC:          qc,
   106  				PayloadHash: payloadHash,
   107  			},
   108  			LastViewTC: lastViewTC,
   109  			SigData:    nil,
   110  		}
   111  		proposal.Block.BlockID = makeBlockID(proposal.Block)
   112  
   113  		blocks = append(blocks, proposal)
   114  
   115  		// generate QC for the new proposal
   116  		qcs[bv.BlockIndex()] = &flow.QuorumCertificate{
   117  			View:          proposal.Block.View,
   118  			BlockID:       proposal.Block.BlockID,
   119  			SignerIndices: nil,
   120  			SigData:       nil,
   121  		}
   122  	}
   123  
   124  	return blocks, nil
   125  }
   126  
   127  // Blocks returns a list of all blocks added to the BlockBuilder.
   128  // Returns an error if the blocks do not form a connected tree rooted at genesis.
   129  func (bb *BlockBuilder) Blocks() ([]*model.Block, error) {
   130  	proposals, err := bb.Proposals()
   131  	if err != nil {
   132  		return nil, fmt.Errorf("BlockBuilder failed to generate proposals: %w", err)
   133  	}
   134  	return toBlocks(proposals), nil
   135  }
   136  
   137  func makePayloadHash(view uint64, qc *flow.QuorumCertificate, blockVersion int) flow.Identifier {
   138  	return flow.MakeID(struct {
   139  		View         uint64
   140  		QC           *flow.QuorumCertificate
   141  		BlockVersion uint64
   142  	}{
   143  		View:         view,
   144  		QC:           qc,
   145  		BlockVersion: uint64(blockVersion),
   146  	})
   147  }
   148  
   149  func makeBlockID(block *model.Block) flow.Identifier {
   150  	return flow.MakeID(struct {
   151  		View        uint64
   152  		QC          *flow.QuorumCertificate
   153  		PayloadHash flow.Identifier
   154  	}{
   155  		View:        block.View,
   156  		QC:          block.QC,
   157  		PayloadHash: block.PayloadHash,
   158  	})
   159  }
   160  
   161  // constructs the genesis block (identical for all calls)
   162  func makeGenesis() *model.CertifiedBlock {
   163  	genesis := &model.Block{
   164  		View: 1,
   165  	}
   166  	genesis.BlockID = makeBlockID(genesis)
   167  
   168  	genesisQC := &flow.QuorumCertificate{
   169  		View:    1,
   170  		BlockID: genesis.BlockID,
   171  	}
   172  	certifiedGenesisBlock, err := model.NewCertifiedBlock(genesis, genesisQC)
   173  	if err != nil {
   174  		panic(fmt.Sprintf("combining genesis block and genensis QC to certified block failed: %s", err.Error()))
   175  	}
   176  	return &certifiedGenesisBlock
   177  }
   178  
   179  // toBlocks converts the given proposals to slice of blocks
   180  func toBlocks(proposals []*model.Proposal) []*model.Block {
   181  	blocks := make([]*model.Block, 0, len(proposals))
   182  	for _, b := range proposals {
   183  		blocks = append(blocks, b.Block)
   184  	}
   185  	return blocks
   186  }