mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-05-20 13:56:45 +00:00

* fix mempool accessing, rewrite get_mempool_entries_by_addresses * fix counter, add verbose * fmt * addresses as string * Define error in case utxoEntry is missing. * fix error variable to string * stop tests from failing (see in code comment) * access both pools in the same state via parameters * get rid of todo message * fmt - very important! * perf: scriptpublickey in mempool, no txscript. * address reveiw * fmt fix * mixed up isorphan bool, pass tests now * do map preallocation in mempoolbyaddresses * no proallocation for orphanpool sending. Co-authored-by: Ori Newman <orinewman1@gmail.com>
376 lines
11 KiB
Go
376 lines
11 KiB
Go
package mempool
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
|
|
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
|
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
|
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
|
|
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type idToOrphanMap map[externalapi.DomainTransactionID]*model.OrphanTransaction
|
|
type previousOutpointToOrphanMap map[externalapi.DomainOutpoint]*model.OrphanTransaction
|
|
|
|
type orphansPool struct {
|
|
mempool *mempool
|
|
allOrphans idToOrphanMap
|
|
orphansByPreviousOutpoint previousOutpointToOrphanMap
|
|
lastExpireScan uint64
|
|
}
|
|
|
|
func newOrphansPool(mp *mempool) *orphansPool {
|
|
return &orphansPool{
|
|
mempool: mp,
|
|
allOrphans: idToOrphanMap{},
|
|
orphansByPreviousOutpoint: previousOutpointToOrphanMap{},
|
|
lastExpireScan: 0,
|
|
}
|
|
}
|
|
|
|
func (op *orphansPool) maybeAddOrphan(transaction *externalapi.DomainTransaction, isHighPriority bool) error {
|
|
if op.mempool.config.MaximumOrphanTransactionCount == 0 {
|
|
return nil
|
|
}
|
|
|
|
err := op.checkOrphanDuplicate(transaction)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = op.checkOrphanMass(transaction)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = op.checkOrphanDoubleSpend(transaction)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = op.addOrphan(transaction, isHighPriority)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = op.limitOrphanPoolSize()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) limitOrphanPoolSize() error {
|
|
for uint64(len(op.allOrphans)) > op.mempool.config.MaximumOrphanTransactionCount {
|
|
orphanToRemove := op.randomNonHighPriorityOrphan()
|
|
if orphanToRemove == nil { // this means all orphans are HighPriority
|
|
log.Warnf(
|
|
"Number of high-priority transactions in orphanPool (%d) is higher than maximum allowed (%d)",
|
|
len(op.allOrphans),
|
|
op.mempool.config.MaximumOrphanTransactionCount)
|
|
break
|
|
}
|
|
|
|
// Don't remove redeemers in the case of a random eviction since the evicted transaction is
|
|
// not invalid, therefore it's redeemers are as good as any orphan that just arrived.
|
|
err := op.removeOrphan(orphanToRemove.TransactionID(), false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) checkOrphanMass(transaction *externalapi.DomainTransaction) error {
|
|
if transaction.Mass > op.mempool.config.MaximumOrphanTransactionMass {
|
|
str := fmt.Sprintf("orphan transaction size of %d bytes is "+
|
|
"larger than max allowed size of %d bytes",
|
|
transaction.Mass, op.mempool.config.MaximumOrphanTransactionMass)
|
|
return transactionRuleError(RejectBadOrphan, str)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) checkOrphanDuplicate(transaction *externalapi.DomainTransaction) error {
|
|
if _, ok := op.allOrphans[*consensushashing.TransactionID(transaction)]; ok {
|
|
str := fmt.Sprintf("Orphan transacion %s is already in the orphan pool",
|
|
consensushashing.TransactionID(transaction))
|
|
return transactionRuleError(RejectDuplicate, str)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) checkOrphanDoubleSpend(transaction *externalapi.DomainTransaction) error {
|
|
for _, input := range transaction.Inputs {
|
|
if doubleSpendOrphan, ok := op.orphansByPreviousOutpoint[input.PreviousOutpoint]; ok {
|
|
str := fmt.Sprintf("Orphan transacion %s is double spending an input from already existing orphan %s",
|
|
consensushashing.TransactionID(transaction), doubleSpendOrphan.TransactionID())
|
|
return transactionRuleError(RejectDuplicate, str)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) addOrphan(transaction *externalapi.DomainTransaction, isHighPriority bool) error {
|
|
virtualDAAScore, err := op.mempool.consensusReference.Consensus().GetVirtualDAAScore()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
orphanTransaction := model.NewOrphanTransaction(transaction, isHighPriority, virtualDAAScore)
|
|
|
|
op.allOrphans[*orphanTransaction.TransactionID()] = orphanTransaction
|
|
for _, input := range transaction.Inputs {
|
|
op.orphansByPreviousOutpoint[input.PreviousOutpoint] = orphanTransaction
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) processOrphansAfterAcceptedTransaction(acceptedTransaction *externalapi.DomainTransaction) (
|
|
acceptedOrphans []*externalapi.DomainTransaction, err error) {
|
|
|
|
acceptedOrphans = []*externalapi.DomainTransaction{}
|
|
queue := []*externalapi.DomainTransaction{acceptedTransaction}
|
|
|
|
for len(queue) > 0 {
|
|
var current *externalapi.DomainTransaction
|
|
current, queue = queue[0], queue[1:]
|
|
|
|
currentTransactionID := consensushashing.TransactionID(current)
|
|
outpoint := externalapi.DomainOutpoint{TransactionID: *currentTransactionID}
|
|
for i, output := range current.Outputs {
|
|
outpoint.Index = uint32(i)
|
|
orphan, ok := op.orphansByPreviousOutpoint[outpoint]
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, input := range orphan.Transaction().Inputs {
|
|
if input.PreviousOutpoint.Equal(&outpoint) && input.UTXOEntry == nil {
|
|
input.UTXOEntry = utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false,
|
|
constants.UnacceptedDAAScore)
|
|
break
|
|
}
|
|
}
|
|
if countUnfilledInputs(orphan) == 0 {
|
|
err := op.unorphanTransaction(orphan)
|
|
if err != nil {
|
|
if errors.As(err, &RuleError{}) {
|
|
log.Infof("Failed to unorphan transaction %s due to rule error: %s",
|
|
currentTransactionID, err)
|
|
continue
|
|
}
|
|
return nil, err
|
|
}
|
|
acceptedOrphans = append(acceptedOrphans, orphan.Transaction().Clone()) //these pointers leave the mempool, hence the clone
|
|
}
|
|
}
|
|
}
|
|
|
|
return acceptedOrphans, nil
|
|
}
|
|
|
|
func countUnfilledInputs(orphan *model.OrphanTransaction) int {
|
|
unfilledInputs := 0
|
|
for _, input := range orphan.Transaction().Inputs {
|
|
if input.UTXOEntry == nil {
|
|
unfilledInputs++
|
|
}
|
|
}
|
|
return unfilledInputs
|
|
}
|
|
|
|
func (op *orphansPool) unorphanTransaction(transaction *model.OrphanTransaction) error {
|
|
err := op.removeOrphan(transaction.TransactionID(), false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = op.mempool.consensusReference.Consensus().ValidateTransactionAndPopulateWithConsensusData(transaction.Transaction())
|
|
if err != nil {
|
|
if errors.Is(err, ruleerrors.ErrImmatureSpend) {
|
|
return transactionRuleError(RejectImmatureSpend, "one of the transaction inputs spends an immature UTXO")
|
|
}
|
|
if errors.As(err, &ruleerrors.RuleError{}) {
|
|
return newRuleError(err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
err = op.mempool.validateTransactionInContext(transaction.Transaction())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
virtualDAAScore, err := op.mempool.consensusReference.Consensus().GetVirtualDAAScore()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mempoolTransaction := model.NewMempoolTransaction(
|
|
transaction.Transaction(),
|
|
op.mempool.transactionsPool.getParentTransactionsInPool(transaction.Transaction()),
|
|
false,
|
|
virtualDAAScore,
|
|
)
|
|
err = op.mempool.transactionsPool.addMempoolTransaction(mempoolTransaction)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) removeOrphan(orphanTransactionID *externalapi.DomainTransactionID, removeRedeemers bool) error {
|
|
orphanTransaction, ok := op.allOrphans[*orphanTransactionID]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
delete(op.allOrphans, *orphanTransactionID)
|
|
|
|
for i, input := range orphanTransaction.Transaction().Inputs {
|
|
if _, ok := op.orphansByPreviousOutpoint[input.PreviousOutpoint]; !ok {
|
|
return errors.Errorf("Input No. %d of %s (%s) doesn't exist in orphansByPreviousOutpoint",
|
|
i, orphanTransactionID, input.PreviousOutpoint)
|
|
}
|
|
delete(op.orphansByPreviousOutpoint, input.PreviousOutpoint)
|
|
}
|
|
|
|
if removeRedeemers {
|
|
err := op.removeRedeemersOf(orphanTransaction)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) removeRedeemersOf(transaction model.Transaction) error {
|
|
outpoint := externalapi.DomainOutpoint{TransactionID: *transaction.TransactionID()}
|
|
for i := range transaction.Transaction().Outputs {
|
|
outpoint.Index = uint32(i)
|
|
if orphan, ok := op.orphansByPreviousOutpoint[outpoint]; ok {
|
|
// Recursive call is bound by size of orphan pool (which is very small)
|
|
err := op.removeOrphan(orphan.TransactionID(), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) expireOrphanTransactions() error {
|
|
virtualDAAScore, err := op.mempool.consensusReference.Consensus().GetVirtualDAAScore()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if virtualDAAScore-op.lastExpireScan < op.mempool.config.OrphanExpireScanIntervalDAAScore {
|
|
return nil
|
|
}
|
|
|
|
for _, orphanTransaction := range op.allOrphans {
|
|
// Never expire high priority transactions
|
|
if orphanTransaction.IsHighPriority() {
|
|
continue
|
|
}
|
|
|
|
// Remove all transactions whose addedAtDAAScore is older then TransactionExpireIntervalDAAScore
|
|
if virtualDAAScore-orphanTransaction.AddedAtDAAScore() > op.mempool.config.OrphanExpireIntervalDAAScore {
|
|
err = op.removeOrphan(orphanTransaction.TransactionID(), false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
op.lastExpireScan = virtualDAAScore
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) updateOrphansAfterTransactionRemoved(
|
|
removedTransaction *model.MempoolTransaction, removeRedeemers bool) error {
|
|
|
|
if removeRedeemers {
|
|
return op.removeRedeemersOf(removedTransaction)
|
|
}
|
|
|
|
outpoint := externalapi.DomainOutpoint{TransactionID: *removedTransaction.TransactionID()}
|
|
for i := range removedTransaction.Transaction().Outputs {
|
|
outpoint.Index = uint32(i)
|
|
if orphan, ok := op.orphansByPreviousOutpoint[outpoint]; ok {
|
|
for _, input := range orphan.Transaction().Inputs {
|
|
if input.PreviousOutpoint.TransactionID.Equal(removedTransaction.TransactionID()) {
|
|
input.UTXOEntry = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) randomNonHighPriorityOrphan() *model.OrphanTransaction {
|
|
for _, orphan := range op.allOrphans {
|
|
if !orphan.IsHighPriority() {
|
|
return orphan
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) getOrphanTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) {
|
|
if orphanTransaction, ok := op.allOrphans[*transactionID]; ok {
|
|
return orphanTransaction.Transaction().Clone(), true //this pointer leaves the mempool, hence we clone.
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (op *orphansPool) getOrphanTransactionsByAddresses() (
|
|
sending model.ScriptPublicKeyStringToDomainTransaction,
|
|
receiving model.ScriptPublicKeyStringToDomainTransaction,
|
|
err error) {
|
|
sending = make(model.ScriptPublicKeyStringToDomainTransaction)
|
|
receiving = make(model.ScriptPublicKeyStringToDomainTransaction, op.orphanTransactionCount())
|
|
var transaction *externalapi.DomainTransaction
|
|
for _, mempoolTransaction := range op.allOrphans {
|
|
transaction = mempoolTransaction.Transaction().Clone() //these pointers leave the mempool, hence we clone.
|
|
for _, input := range transaction.Inputs {
|
|
if input.UTXOEntry == nil { //this is not a bug, but a valid state of orphan transactions with missing outpoints.
|
|
continue
|
|
}
|
|
|
|
sending[input.UTXOEntry.ScriptPublicKey().String()] = transaction
|
|
}
|
|
for _, output := range transaction.Outputs {
|
|
receiving[output.ScriptPublicKey.String()] = transaction
|
|
|
|
}
|
|
}
|
|
return sending, receiving, nil
|
|
}
|
|
|
|
func (op *orphansPool) getAllOrphanTransactions() []*externalapi.DomainTransaction {
|
|
allOrphanTransactions := make([]*externalapi.DomainTransaction, len(op.allOrphans))
|
|
i := 0
|
|
for _, mempoolTransaction := range op.allOrphans {
|
|
allOrphanTransactions[i] = mempoolTransaction.Transaction().Clone() //these pointers leave the mempool, hence we clone.
|
|
i++
|
|
}
|
|
return allOrphanTransactions
|
|
}
|
|
|
|
func (op *orphansPool) orphanTransactionCount() int {
|
|
return len(op.allOrphans)
|
|
}
|