kaspad/domain/miningmanager/mempool/transactions_pool.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 b2da9a4a00c02fb380d2518cf54fa16257bd8423.

* 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

204 lines
6.3 KiB
Go

package mempool
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
)
type transactionsPool struct {
mempool *mempool
allTransactions model.IDToTransactionMap
highPriorityTransactions model.IDToTransactionMap
chainedTransactionsByPreviousOutpoint model.OutpointToTransactionMap
transactionsOrderedByFeeRate model.TransactionsOrderedByFeeRate
lastExpireScan uint64
}
func newTransactionsPool(mp *mempool) *transactionsPool {
return &transactionsPool{
mempool: mp,
allTransactions: model.IDToTransactionMap{},
highPriorityTransactions: model.IDToTransactionMap{},
chainedTransactionsByPreviousOutpoint: model.OutpointToTransactionMap{},
transactionsOrderedByFeeRate: model.TransactionsOrderedByFeeRate{},
lastExpireScan: 0,
}
}
func (tp *transactionsPool) addTransaction(transaction *externalapi.DomainTransaction,
parentTransactionsInPool model.OutpointToTransactionMap, isHighPriority bool) (*model.MempoolTransaction, error) {
virtualDAAScore, err := tp.mempool.consensus.GetVirtualDAAScore()
if err != nil {
return nil, err
}
mempoolTransaction := model.NewMempoolTransaction(
transaction, parentTransactionsInPool, isHighPriority, virtualDAAScore)
err = tp.addMempoolTransaction(mempoolTransaction)
if err != nil {
return nil, err
}
return mempoolTransaction, nil
}
func (tp *transactionsPool) addMempoolTransaction(transaction *model.MempoolTransaction) error {
tp.allTransactions[*transaction.TransactionID()] = transaction
for outpoint, parentTransactionInPool := range transaction.ParentTransactionsInPool() {
tp.chainedTransactionsByPreviousOutpoint[outpoint] = parentTransactionInPool
}
tp.mempool.mempoolUTXOSet.addTransaction(transaction)
err := tp.transactionsOrderedByFeeRate.Push(transaction)
if err != nil {
return err
}
if transaction.IsHighPriority() {
tp.highPriorityTransactions[*transaction.TransactionID()] = transaction
}
return nil
}
func (tp *transactionsPool) removeTransaction(transaction *model.MempoolTransaction) error {
delete(tp.allTransactions, *transaction.TransactionID())
err := tp.transactionsOrderedByFeeRate.Remove(transaction)
if err != nil {
return err
}
delete(tp.highPriorityTransactions, *transaction.TransactionID())
for outpoint := range transaction.ParentTransactionsInPool() {
delete(tp.chainedTransactionsByPreviousOutpoint, outpoint)
}
return nil
}
func (tp *transactionsPool) expireOldTransactions() error {
virtualDAAScore, err := tp.mempool.consensus.GetVirtualDAAScore()
if err != nil {
return err
}
if virtualDAAScore-tp.lastExpireScan < tp.mempool.config.TransactionExpireScanIntervalDAAScore {
return nil
}
for _, mempoolTransaction := range tp.allTransactions {
// Never expire high priority transactions
if mempoolTransaction.IsHighPriority() {
continue
}
// Remove all transactions whose addedAtDAAScore is older then TransactionExpireIntervalDAAScore
if virtualDAAScore-mempoolTransaction.AddedAtDAAScore() > tp.mempool.config.TransactionExpireIntervalDAAScore {
err = tp.mempool.removeTransaction(mempoolTransaction.TransactionID(), true)
if err != nil {
return err
}
}
}
tp.lastExpireScan = virtualDAAScore
return nil
}
func (tp *transactionsPool) allReadyTransactions() []*externalapi.DomainTransaction {
result := []*externalapi.DomainTransaction{}
for _, mempoolTransaction := range tp.allTransactions {
if len(mempoolTransaction.ParentTransactionsInPool()) == 0 {
result = append(result, mempoolTransaction.Transaction())
}
}
return result
}
func (tp *transactionsPool) getParentTransactionsInPool(
transaction *externalapi.DomainTransaction) model.OutpointToTransactionMap {
parentsTransactionsInPool := model.OutpointToTransactionMap{}
for _, input := range transaction.Inputs {
if transaction, ok := tp.allTransactions[input.PreviousOutpoint.TransactionID]; ok {
parentsTransactionsInPool[input.PreviousOutpoint] = transaction
}
}
return parentsTransactionsInPool
}
func (tp *transactionsPool) getRedeemers(transaction *model.MempoolTransaction) []*model.MempoolTransaction {
queue := []*model.MempoolTransaction{transaction}
redeemers := []*model.MempoolTransaction{}
for len(queue) > 0 {
var current *model.MempoolTransaction
current, queue = queue[0], queue[1:]
outpoint := externalapi.DomainOutpoint{TransactionID: *current.TransactionID()}
for i := range current.Transaction().Outputs {
outpoint.Index = uint32(i)
if redeemerTransaction, ok := tp.chainedTransactionsByPreviousOutpoint[outpoint]; ok {
queue = append(queue, redeemerTransaction)
redeemers = append(redeemers, redeemerTransaction)
}
}
}
return redeemers
}
func (tp *transactionsPool) limitTransactionCount() error {
currentIndex := 0
for uint64(len(tp.allTransactions)) > tp.mempool.config.MaximumTransactionCount {
var transactionToRemove *model.MempoolTransaction
for {
transactionToRemove = tp.transactionsOrderedByFeeRate.GetByIndex(currentIndex)
if !transactionToRemove.IsHighPriority() {
break
}
currentIndex++
if currentIndex >= len(tp.allTransactions) {
log.Warnf(
"Number of high-priority transactions in mempool (%d) is higher than maximum allowed (%d)",
len(tp.allTransactions), tp.mempool.config.MaximumTransactionCount)
return nil
}
}
err := tp.mempool.removeTransaction(transactionToRemove.TransactionID(), true)
if err != nil {
return err
}
}
return nil
}
func (tp *transactionsPool) getTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) {
if mempoolTransaction, ok := tp.allTransactions[*transactionID]; ok {
return mempoolTransaction.Transaction(), true
}
return nil, false
}
func (tp *transactionsPool) getAllTransactions() []*externalapi.DomainTransaction {
allTransactions := make([]*externalapi.DomainTransaction, 0, len(tp.allTransactions))
for _, mempoolTransaction := range tp.allTransactions {
allTransactions = append(allTransactions, mempoolTransaction.Transaction())
}
return allTransactions
}
func (tp *transactionsPool) transactionCount() int {
return len(tp.allTransactions)
}