package blocktemplatebuilder import ( "math" "sort" consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks" miningmanagerapi "github.com/kaspanet/kaspad/domain/miningmanager/model" "github.com/kaspanet/kaspad/util" "github.com/pkg/errors" ) type candidateTx struct { *consensusexternalapi.DomainTransaction txValue float64 gasLimit uint64 p float64 start float64 end float64 isMarkedForDeletion bool } // blockTemplateBuilder creates block templates for a miner to consume type blockTemplateBuilder struct { consensus consensusexternalapi.Consensus mempool miningmanagerapi.Mempool policy policy } // New creates a new blockTemplateBuilder func New(consensus consensusexternalapi.Consensus, mempool miningmanagerapi.Mempool, blockMaxMass uint64) miningmanagerapi.BlockTemplateBuilder { return &blockTemplateBuilder{ consensus: consensus, mempool: mempool, policy: policy{BlockMaxMass: blockMaxMass}, } } // GetBlockTemplate creates a block template for a miner to consume // GetBlockTemplate returns a new block template that is ready to be solved // using the transactions from the passed transaction source pool and a coinbase // that either pays to the passed address if it is not nil, or a coinbase that // is redeemable by anyone if the passed address is nil. The nil address // functionality is useful since there are cases such as the getblocktemplate // RPC where external mining software is responsible for creating their own // coinbase which will replace the one generated for the block template. Thus // the need to have configured address can be avoided. // // The transactions selected and included are prioritized according to several // factors. First, each transaction has a priority calculated based on its // value, age of inputs, and size. Transactions which consist of larger // amounts, older inputs, and small sizes have the highest priority. Second, a // fee per kilobyte is calculated for each transaction. Transactions with a // higher fee per kilobyte are preferred. Finally, the block generation related // policy settings are all taken into account. // // Transactions which only spend outputs from other transactions already in the // block DAG are immediately added to a priority queue which either // prioritizes based on the priority (then fee per kilobyte) or the fee per // kilobyte (then priority) depending on whether or not the BlockPrioritySize // policy setting allots space for high-priority transactions. Transactions // which spend outputs from other transactions in the source pool are added to a // dependency map so they can be added to the priority queue once the // transactions they depend on have been included. // // Once the high-priority area (if configured) has been filled with // transactions, or the priority falls below what is considered high-priority, // the priority queue is updated to prioritize by fees per kilobyte (then // priority). // // When the fees per kilobyte drop below the TxMinFreeFee policy setting, the // transaction will be skipped unless the BlockMinSize policy setting is // nonzero, in which case the block will be filled with the low-fee/free // transactions until the block size reaches that minimum size. // // Any transactions which would cause the block to exceed the BlockMaxMass // policy setting, exceed the maximum allowed signature operations per block, or // otherwise cause the block to be invalid are skipped. // // Given the above, a block generated by this function is of the following form: // // ----------------------------------- -- -- // | Coinbase Transaction | | | // |-----------------------------------| | | // | | | | ----- policy.BlockPrioritySize // | High-priority Transactions | | | // | | | | // |-----------------------------------| | -- // | | | // | | | // | | |--- policy.BlockMaxMass // | Transactions prioritized by fee | | // | until <= policy.TxMinFreeFee | | // | | | // | | | // | | | // |-----------------------------------| | // | Low-fee/Non high-priority (free) | | // | transactions (while block size | | // | <= policy.BlockMinSize) | | // ----------------------------------- -- func (btb *blockTemplateBuilder) GetBlockTemplate(coinbaseData *consensusexternalapi.DomainCoinbaseData) (*consensusexternalapi.DomainBlock, error) { mempoolTransactions := btb.mempool.Transactions() candidateTxs := make([]*candidateTx, 0, len(mempoolTransactions)) for _, tx := range mempoolTransactions { // Calculate the tx value gasLimit := uint64(0) if !subnetworks.IsBuiltInOrNative(tx.SubnetworkID) { panic("We currently don't support non native subnetworks") } candidateTxs = append(candidateTxs, &candidateTx{ DomainTransaction: tx, txValue: btb.calcTxValue(tx), gasLimit: gasLimit, }) } // Sort the candidate txs by subnetworkID. sort.Slice(candidateTxs, func(i, j int) bool { return subnetworks.Less(candidateTxs[i].SubnetworkID, candidateTxs[j].SubnetworkID) }) log.Debugf("Considering %d transactions for inclusion to new block", len(candidateTxs)) blockTxs := btb.selectTransactions(candidateTxs) blk, err := btb.consensus.BuildBlock(coinbaseData, blockTxs.selectedTxs) invalidTxsErr := ruleerrors.ErrInvalidTransactionsInNewBlock{} if errors.As(err, &invalidTxsErr) { log.Criticalf("consensus.BuildBlock returned invalid txs in GetBlockTemplate: %s", err) invalidTxs := make([]*consensusexternalapi.DomainTransaction, 0, len(invalidTxsErr.InvalidTransactions)) for _, tx := range invalidTxsErr.InvalidTransactions { invalidTxs = append(invalidTxs, tx.Transaction) } btb.mempool.RemoveTransactions(invalidTxs) // We can call this recursively without worry because this should almost never happen return btb.GetBlockTemplate(coinbaseData) } if err != nil { return nil, err } log.Debugf("Created new block template (%d transactions, %d in fees, %d mass, target difficulty %064x)", len(blk.Transactions), blockTxs.totalFees, blockTxs.totalMass, util.CompactToBig(blk.Header.Bits)) return blk, nil } // calcTxValue calculates a value to be used in transaction selection. // The higher the number the more likely it is that the transaction will be // included in the block. func (btb *blockTemplateBuilder) calcTxValue(tx *consensusexternalapi.DomainTransaction) float64 { massLimit := btb.policy.BlockMaxMass mass := tx.Mass fee := tx.Fee if subnetworks.IsBuiltInOrNative(tx.SubnetworkID) { return float64(fee) / (float64(mass) / float64(massLimit)) } // TODO: Replace with real gas once implemented gasLimit := uint64(math.MaxUint64) return float64(fee) / (float64(mass)/float64(massLimit) + float64(tx.Gas)/float64(gasLimit)) }