From 903fa58957ad57da22e1829ba07f8e557ee9765e Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Sun, 30 May 2021 16:49:51 +0300 Subject: [PATCH] Revalidate transactions before rebroadcast --- app/protocol/flowcontext/blocks.go | 6 ++- app/protocol/flowcontext/transactions.go | 16 +++++-- .../miningmanager/mempool/expiration_heap.go | 1 + domain/miningmanager/mempool/mempool.go | 48 +++++++++++++++---- domain/miningmanager/miningmanager.go | 5 ++ .../miningmanager/model/interface_mempool.go | 1 + 6 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 domain/miningmanager/mempool/expiration_heap.go diff --git a/app/protocol/flowcontext/blocks.go b/app/protocol/flowcontext/blocks.go index c875501b2..3ab8e31fc 100644 --- a/app/protocol/flowcontext/blocks.go +++ b/app/protocol/flowcontext/blocks.go @@ -80,7 +80,11 @@ func (f *FlowContext) broadcastTransactionsAfterBlockAdded( var txIDsToRebroadcast []*externalapi.DomainTransactionID if f.shouldRebroadcastTransactions() { - txIDsToRebroadcast = f.txIDsToRebroadcast() + var err error + txIDsToRebroadcast, err = f.txIDsToRebroadcast() + if err != nil { + return err + } } txIDsToBroadcast := make([]*externalapi.DomainTransactionID, len(transactionsAcceptedToMempool)+len(txIDsToRebroadcast)) diff --git a/app/protocol/flowcontext/transactions.go b/app/protocol/flowcontext/transactions.go index 9f87431d7..6ce222728 100644 --- a/app/protocol/flowcontext/transactions.go +++ b/app/protocol/flowcontext/transactions.go @@ -44,17 +44,25 @@ func (f *FlowContext) shouldRebroadcastTransactions() bool { return time.Since(f.lastRebroadcastTime) > rebroadcastInterval } -func (f *FlowContext) txIDsToRebroadcast() []*externalapi.DomainTransactionID { +func (f *FlowContext) txIDsToRebroadcast() ([]*externalapi.DomainTransactionID, error) { f.transactionsToRebroadcastLock.Lock() defer f.transactionsToRebroadcastLock.Unlock() - txIDs := make([]*externalapi.DomainTransactionID, len(f.transactionsToRebroadcast)) + txIDs := make([]*externalapi.DomainTransactionID, 0, len(f.transactionsToRebroadcast)) i := 0 for _, tx := range f.transactionsToRebroadcast { - txIDs[i] = consensushashing.TransactionID(tx) + isValid, err := f.Domain().MiningManager().RevalidateTransaction(tx) + if err != nil { + return nil, err + } + if !isValid { + continue + } + + txIDs = append(txIDs, consensushashing.TransactionID(tx)) i++ } - return txIDs + return txIDs, nil } // SharedRequestedTransactions returns a *transactionrelay.SharedRequestedTransactions for sharing diff --git a/domain/miningmanager/mempool/expiration_heap.go b/domain/miningmanager/mempool/expiration_heap.go new file mode 100644 index 000000000..6a26ef9f2 --- /dev/null +++ b/domain/miningmanager/mempool/expiration_heap.go @@ -0,0 +1 @@ +package mempool diff --git a/domain/miningmanager/mempool/mempool.go b/domain/miningmanager/mempool/mempool.go index a73dbf321..7a5499c28 100644 --- a/domain/miningmanager/mempool/mempool.go +++ b/domain/miningmanager/mempool/mempool.go @@ -7,22 +7,19 @@ package mempool import ( "container/list" "fmt" - "github.com/kaspanet/kaspad/domain/dagconfig" "sort" "sync" "time" - "github.com/kaspanet/kaspad/infrastructure/logger" - - "github.com/kaspanet/kaspad/domain/consensus/utils/constants" - - "github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper" - consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" "github.com/kaspanet/kaspad/domain/consensus/utils/estimatedsize" + "github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper" + "github.com/kaspanet/kaspad/domain/dagconfig" miningmanagermodel "github.com/kaspanet/kaspad/domain/miningmanager/model" + "github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/util" "github.com/kaspanet/kaspad/util/mstime" "github.com/pkg/errors" @@ -164,6 +161,10 @@ type txDescriptor struct { // one that is accepted to pool, but cannot be mined in next block because it // depends on outputs of accepted, but still not mined transaction depCount int + + // expirationDAAScore is the virtual DAA score at which this transaction is expired + // if expirationDAAScore == 0 - the transaction never expires. + expirationDAAScore uint64 } // orphanTx is normal transaction that references an ancestor transaction @@ -411,6 +412,13 @@ type transactionAndOutpoint struct { outpoint *consensusexternalapi.DomainOutpoint } +func (mp *mempool) removeTransactionAndItsChainedTransactionsWithLock(transaction *consensusexternalapi.DomainTransaction) error { + mp.mtx.Lock() + defer mp.mtx.Unlock() + + return mp.removeTransactionAndItsChainedTransactions(transaction) +} + // removeTransactionAndItsChainedTransactions removes a transaction and all of its chained transaction from the mempool. // This function MUST be called with the mempool lock held (for writes). func (mp *mempool) removeTransactionAndItsChainedTransactions(transaction *consensusexternalapi.DomainTransaction) error { @@ -899,7 +907,9 @@ func (mp *mempool) processOrphans(acceptedTx *consensusexternalapi.DomainTransac // the passed one being accepted. // // This function is safe for concurrent access. -func (mp *mempool) ValidateAndInsertTransaction(tx *consensusexternalapi.DomainTransaction, allowOrphan bool) error { +func (mp *mempool) ValidateAndInsertTransaction( + tx *consensusexternalapi.DomainTransaction, expirationDAAScore uint64, allowOrphan bool) error { + log.Tracef("Processing transaction %s", consensushashing.TransactionID(tx)) // Protect concurrent access. @@ -1042,3 +1052,25 @@ func (mp *mempool) RemoveTransactions(txs []*consensusexternalapi.DomainTransact return mp.removeTransactionsFromPool(txs) } + +// RevalidateTransaction revalidates given transaction, and removes from mempool if it isn't valid +func (mp *mempool) RevalidateTransaction(tx *consensusexternalapi.DomainTransaction) (isValid bool, err error) { + tx = tx.Clone() + + _ = mp.mempoolUTXOSet.populateUTXOEntries(tx) + + err = mp.consensus.ValidateTransactionAndPopulateWithConsensusData(tx) + if err != nil { + if errors.As(err, &ruleerrors.RuleError{}) { + log.Debugf("Transaction %s was found invalid during revalidate and therefore is being removed from mempool", + consensushashing.TransactionID(tx)) + err := mp.removeTransactionAndItsChainedTransactionsWithLock(tx) + if err != nil { + return false, err + } + return false, nil + } + return false, err + } + return true, nil +} diff --git a/domain/miningmanager/miningmanager.go b/domain/miningmanager/miningmanager.go index bb393c100..a495d7847 100644 --- a/domain/miningmanager/miningmanager.go +++ b/domain/miningmanager/miningmanager.go @@ -14,6 +14,7 @@ type MiningManager interface { TransactionCount() int HandleNewBlockTransactions(txs []*consensusexternalapi.DomainTransaction) ([]*consensusexternalapi.DomainTransaction, error) ValidateAndInsertTransaction(transaction *consensusexternalapi.DomainTransaction, allowOrphan bool) error + RevalidateTransaction(tx *consensusexternalapi.DomainTransaction) (isValid bool, err error) } type miningManager struct { @@ -51,3 +52,7 @@ func (mm *miningManager) AllTransactions() []*consensusexternalapi.DomainTransac func (mm *miningManager) TransactionCount() int { return mm.mempool.TransactionCount() } + +func (mm *miningManager) RevalidateTransaction(tx *consensusexternalapi.DomainTransaction) (isValid bool, err error) { + return mm.mempool.RevalidateTransaction(tx) +} diff --git a/domain/miningmanager/model/interface_mempool.go b/domain/miningmanager/model/interface_mempool.go index e4a3a5f6a..b0608a205 100644 --- a/domain/miningmanager/model/interface_mempool.go +++ b/domain/miningmanager/model/interface_mempool.go @@ -14,4 +14,5 @@ type Mempool interface { GetTransaction(transactionID *consensusexternalapi.DomainTransactionID) (*consensusexternalapi.DomainTransaction, bool) AllTransactions() []*consensusexternalapi.DomainTransaction TransactionCount() int + RevalidateTransaction(tx *consensusexternalapi.DomainTransaction) (isValid bool, err error) }