From b335fa5a1d5b7032c905bcce0d317046f0f84fb8 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 26 Sep 2023 21:29:17 +0300 Subject: [PATCH] Anti-spam measurements against dust attack (#2223) * BlockCandidateTransactions patch * Fix condition * Fix fee * Fix bug * Reject from mempool * Fix hasCoinbaseInput * Fix position * Bump version to v0.12.14 --- domain/miningmanager/mempool/error.go | 1 + domain/miningmanager/mempool/mempool.go | 54 ++++++++++++++++++- .../validate_and_insert_transaction.go | 1 - .../mempool/validate_transaction.go | 15 ++++++ version/version.go | 2 +- 5 files changed, 70 insertions(+), 3 deletions(-) diff --git a/domain/miningmanager/mempool/error.go b/domain/miningmanager/mempool/error.go index d698217d7f..827fa9014f 100644 --- a/domain/miningmanager/mempool/error.go +++ b/domain/miningmanager/mempool/error.go @@ -51,6 +51,7 @@ const ( RejectDifficulty RejectCode = 0x44 RejectImmatureSpend RejectCode = 0x45 RejectBadOrphan RejectCode = 0x64 + RejectSpamTx RejectCode = 0x65 ) // Map of reject codes back strings for pretty printing. diff --git a/domain/miningmanager/mempool/mempool.go b/domain/miningmanager/mempool/mempool.go index f4d6ef7bf3..6362e483d1 100644 --- a/domain/miningmanager/mempool/mempool.go +++ b/domain/miningmanager/mempool/mempool.go @@ -1,6 +1,8 @@ package mempool import ( + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" "sync" "github.com/kaspanet/kaspad/domain/consensusreference" @@ -141,7 +143,57 @@ func (mp *mempool) BlockCandidateTransactions() []*externalapi.DomainTransaction mp.mtx.RLock() defer mp.mtx.RUnlock() - return mp.transactionsPool.allReadyTransactions() + readyTxs := mp.transactionsPool.allReadyTransactions() + var candidateTxs []*externalapi.DomainTransaction + var spamTx *externalapi.DomainTransaction + var spamTxNewestUTXODaaScore uint64 + for _, tx := range readyTxs { + if len(tx.Outputs) > len(tx.Inputs) { + hasCoinbaseInput := false + for _, input := range tx.Inputs { + if input.UTXOEntry.IsCoinbase() { + hasCoinbaseInput = true + break + } + } + + numExtraOuts := len(tx.Outputs) - len(tx.Inputs) + if !hasCoinbaseInput && numExtraOuts > 2 && tx.Fee < uint64(numExtraOuts)*constants.SompiPerKaspa { + log.Debugf("Filtered spam tx %s", consensushashing.TransactionID(tx)) + continue + } + + if hasCoinbaseInput || tx.Fee > uint64(numExtraOuts)*constants.SompiPerKaspa { + candidateTxs = append(candidateTxs, tx) + } else { + txNewestUTXODaaScore := tx.Inputs[0].UTXOEntry.BlockDAAScore() + for _, input := range tx.Inputs { + if input.UTXOEntry.BlockDAAScore() > txNewestUTXODaaScore { + txNewestUTXODaaScore = input.UTXOEntry.BlockDAAScore() + } + } + + if spamTx != nil { + if txNewestUTXODaaScore < spamTxNewestUTXODaaScore { + spamTx = tx + spamTxNewestUTXODaaScore = txNewestUTXODaaScore + } + } else { + spamTx = tx + spamTxNewestUTXODaaScore = txNewestUTXODaaScore + } + } + } else { + candidateTxs = append(candidateTxs, tx) + } + } + + if spamTx != nil { + log.Debugf("Adding spam tx candidate %s", consensushashing.TransactionID(spamTx)) + candidateTxs = append(candidateTxs, spamTx) + } + + return candidateTxs } func (mp *mempool) RevalidateHighPriorityTransactions() (validTransactions []*externalapi.DomainTransaction, err error) { diff --git a/domain/miningmanager/mempool/validate_and_insert_transaction.go b/domain/miningmanager/mempool/validate_and_insert_transaction.go index d06a19cea7..ff8c752d32 100644 --- a/domain/miningmanager/mempool/validate_and_insert_transaction.go +++ b/domain/miningmanager/mempool/validate_and_insert_transaction.go @@ -2,7 +2,6 @@ package mempool import ( "fmt" - "github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" diff --git a/domain/miningmanager/mempool/validate_transaction.go b/domain/miningmanager/mempool/validate_transaction.go index 42e77c9e24..86695f051f 100644 --- a/domain/miningmanager/mempool/validate_transaction.go +++ b/domain/miningmanager/mempool/validate_transaction.go @@ -2,6 +2,7 @@ package mempool import ( "fmt" + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" @@ -44,6 +45,20 @@ func (mp *mempool) validateTransactionInIsolation(transaction *externalapi.Domai } func (mp *mempool) validateTransactionInContext(transaction *externalapi.DomainTransaction) error { + hasCoinbaseInput := false + for _, input := range transaction.Inputs { + if input.UTXOEntry.IsCoinbase() { + hasCoinbaseInput = true + break + } + } + + numExtraOuts := len(transaction.Outputs) - len(transaction.Inputs) + if !hasCoinbaseInput && numExtraOuts > 2 && transaction.Fee < uint64(numExtraOuts)*constants.SompiPerKaspa { + log.Warnf("Rejected spam tx %s from mempool (%d outputs)", consensushashing.TransactionID(transaction), len(transaction.Outputs)) + return transactionRuleError(RejectSpamTx, fmt.Sprintf("Rejected spam tx %s from mempool", consensushashing.TransactionID(transaction))) + } + if !mp.config.AcceptNonStandard { err := mp.checkTransactionStandardInContext(transaction) if err != nil { diff --git a/version/version.go b/version/version.go index c04cd9643b..d47434c776 100644 --- a/version/version.go +++ b/version/version.go @@ -11,7 +11,7 @@ const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs const ( appMajor uint = 0 appMinor uint = 12 - appPatch uint = 13 + appPatch uint = 14 ) // appBuild is defined as a variable so it can be overridden during the build