Files
kaspad/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go
Svarog c13a4d90ed Mempool redesign (#1752)
* Added model and stubs for all main methods

* Add constructors to all main objects

* Implement BlockCandidateTransactions

* implement expireOldTransactions and expireOrphanTransactions

* Rename isHighPriority to neverExpires

* Add stub for checkDoubleSpends

* Revert "Rename isHighPriority to neverExpires"

This reverts commit b2da9a4a00.

* Imeplement transactionsOrderedByFeeRate

* Orphan maps should be idToOrphan

* Add error.go to mempool

* Invert the condition for banning when mempool rejects a transaction

* Move all model objects to model package

* Implement getParentsInPool

* Implemented mempoolUTXOSet.addTransaction

* Implement removeTransaction, remove sanity checks

* Implemented mempoolUTXOSet.checkDoubleSpends

* Implemented removeOrphan

* Implement removeOrphan

* Implement maybeAddOrphan and AddOrphan

* Implemented processOrphansAfterAcceptedTransaction

* Implement transactionsPool.addTransaction

* Implement RemoveTransaction

* If a transaction was removed from the mempool - update it's redeemers in orphan pool as well

* Use maximumOrphanTransactionCount

* Add allowOrphans to ValidateAndInsertTransaction stub

* Implement validateTransaction functions

* Implement fillInputs

* Implement ValidateAndInsertTransaction

* Implement HandleNewBlockTransactions

* Implement missing mempool interface methods

* Add comments to exported functions

* Call ValidateTransactionInIsolation where needed

* Implement RevalidateHighPriorityTransactions

* Rewire kaspad to use new mempool, and fix compilation errors

* Update rebroadcast logic to use new structure

* Handle non-standard transaction errors properly

* Add mutex to mempool

* bugfix: GetTransaction panics when ok is false

* properly calculate targetBlocksPerSecond in config.go

* Fix various lint errors and tests

* Fix expected text in test for duplicate transactions

* Skip the coinbase transaction in HandleNewBlockTransactions

* Unorphan the correct transactions

* Call ValidateTransactionAndPopulateWithConsensusData on unorphanTransaction

* Re-apply policy_test as check_transactions_standard_test

* removeTransaction: Remove redeemers in orphan pool as well

* Remove redundant check for uint64 < 0

* Export and rename isDust -> IsTransactionOutputDust to allow usage by rothschild

* Add allowOrphan to SubmitTransaction RPC request

* Remove all implementation from mempool.go

* tidy go mod

* Don't pass acceptedOrphans to handleNewBlockTransactions

* Use t.Errorf in stead of t.Fatalf

* Remove minimum relay fee from TestDust, as it's no longer configurable

* Add separate VirtualDAASCore method for faster retrieval where it's repeated multiple times

* Broadcast all transactions that were accepted

* Don't re-use GetVirtualDAAScore in GetVirtualInfo - this causes a deadlock

* Use real transaction count, and not Orphan

* Get mempool config from outside, incorporating values received from cli

* Use MinRelayFee and MaxOrphanTxs from global kaspad config

* Add explanation for the seemingly redundant check for transaction version in checkTransactionStandard

* Update some comment

* Convert creation of acceptedTransactions to a single line

* Move mempoolUTXOSet out of checkDoubleSpends

* Add test for attempt to insert double spend into mempool

* fillInputs: Skip check for coinbase - it's always false in mempool

* Clarify comment about removeRedeemers when removing random orphan

* Don't remove high-priority transactions in limitTransactionCount

* Use mempool.removeTransaction in limitTransactionCount

* Add mutex comment to handleNewBlockTransactions

* Return error from limitTransactionCount

* Pluralize the map types

* mempoolUTXOSet.removeTransaction: Don't restore utxo if it was not created in mempool

* Don't evacuate from orphanPool high-priority transactions

* Disallow double-spends in orphan pool

* Don't use exported (and locking) methods from inside mempool

* Check for double spends in mempool during revalidateTransaction

* Add checkOrphanDuplicate

* Add orphan to acceptedOrphans, not current

* Add TestHighPriorityTransactions

* Fix off-by-one error in limitTransactionCount

* Add TestRevalidateHighPriorityTransactions

* Remove checkDoubleSpends from revalidateTransaction

* Fix TestRevalidateHighPriorityTransactions

* Move check for MaximumOrphanCount to beggining of maybeAddOrphan

* Rename all map type to singulateToSingularMap

* limitOrphanPool only after the orphan was added

* TestDoubleSpendInMempool: use createChildTxWhenParentTxWasAddedByConsensus instead of createTransactionWithUTXOEntry

* Fix some comments

* Have separate min/max transaction versions for mempool

* Add comment on defaultMaximumOrphanTransactionCount to keep it small as long as we have recursion

* Fix comment

* Rename: createChildTxWhenParentTxWasAddedByConsensus -> createChildTxWhereParentTxWasAddedByConsensus

* Handle error from createChildTxWhereParentTxWasAddedByConsensus

* Rename createChildTxWhereParentTxWasAddedByConsensus -> createChildAndParentTxsAndAddParentToConsensus

* Convert all MaximumXXX constants to uint64

* Add comment

* remove mutex comments
2021-06-23 15:49:20 +03:00

178 lines
7.5 KiB
Go

package blocktemplatebuilder
import (
"math"
"sort"
"github.com/kaspanet/kaspad/util/difficulty"
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/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.BlockCandidateTransactions()
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)
}
err = btb.mempool.RemoveTransactions(invalidTxs, true)
if err != nil {
// mempool.RemoveTransactions might return errors in situations that are perfectly fine in this context.
// TODO: Once the mempool invariants are clear, this should be converted back `return nil, err`:
// https://github.com/kaspanet/kaspad/issues/1553
log.Criticalf("Error from mempool.RemoveTransactions: %+v", err)
}
// 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, difficulty.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))
}