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 }