diff --git a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go index 564542db8..76e61101f 100644 --- a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go +++ b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go @@ -147,16 +147,12 @@ func (btb *blockTemplateBuilder) BuildBlockTemplate( invalidTxsErr := ruleerrors.ErrInvalidTransactionsInNewBlock{} if errors.As(err, &invalidTxsErr) { log.Criticalf("consensusReference.Consensus().BuildBlock returned invalid txs in BuildBlockTemplate") - invalidTxs := make([]*consensusexternalapi.DomainTransaction, 0, len(invalidTxsErr.InvalidTransactions)) - for _, tx := range invalidTxsErr.InvalidTransactions { - invalidTxs = append(invalidTxs, tx.Transaction) - } - err = btb.mempool.RemoveTransactions(invalidTxs, true) + err = btb.mempool.RemoveInvalidTransactions(&invalidTxsErr) if err != nil { - // mempool.RemoveTransactions might return errors in situations that are perfectly fine in this context. + // mempool.RemoveInvalidTransactions 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) + log.Criticalf("Error from mempool.RemoveInvalidTransactions: %+v", err) } // We can call this recursively without worry because this should almost never happen return btb.BuildBlockTemplate(coinbaseData) diff --git a/domain/miningmanager/mempool/mempool.go b/domain/miningmanager/mempool/mempool.go index f4d6ef7bf..e8c3fab54 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/ruleerrors" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "sync" "github.com/kaspanet/kaspad/domain/consensusreference" @@ -151,11 +153,29 @@ func (mp *mempool) RevalidateHighPriorityTransactions() (validTransactions []*ex return mp.revalidateHighPriorityTransactions() } -func (mp *mempool) RemoveTransactions(transactions []*externalapi.DomainTransaction, removeRedeemers bool) error { +func (mp *mempool) RemoveInvalidTransactions(err *ruleerrors.ErrInvalidTransactionsInNewBlock) error { mp.mtx.Lock() defer mp.mtx.Unlock() - return mp.removeTransactions(transactions, removeRedeemers) + for _, tx := range err.InvalidTransactions { + ruleErr, success := tx.Error.(ruleerrors.RuleError) + if !success { + continue + } + + inner := ruleErr.Unwrap() + removeRedeemers := true + if _, ok := inner.(ruleerrors.ErrMissingTxOut); ok { + removeRedeemers = false + } + + err := mp.removeTransaction(consensushashing.TransactionID(tx.Transaction), removeRedeemers) + if err != nil { + return err + } + } + + return nil } func (mp *mempool) RemoveTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error { diff --git a/domain/miningmanager/mempool/remove_transaction.go b/domain/miningmanager/mempool/remove_transaction.go index da812d872..a21c4a162 100644 --- a/domain/miningmanager/mempool/remove_transaction.go +++ b/domain/miningmanager/mempool/remove_transaction.go @@ -2,20 +2,9 @@ package mempool import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" - "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/kaspanet/kaspad/domain/miningmanager/mempool/model" ) -func (mp *mempool) removeTransactions(transactions []*externalapi.DomainTransaction, removeRedeemers bool) error { - for _, transaction := range transactions { - err := mp.removeTransaction(consensushashing.TransactionID(transaction), removeRedeemers) - if err != nil { - return err - } - } - return nil -} - func (mp *mempool) removeTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error { if _, ok := mp.orphansPool.allOrphans[*transactionID]; ok { return mp.orphansPool.removeOrphan(transactionID, true) diff --git a/domain/miningmanager/mempool/revalidate_high_priority_transactions.go b/domain/miningmanager/mempool/revalidate_high_priority_transactions.go index 12f76403f..0525af12f 100644 --- a/domain/miningmanager/mempool/revalidate_high_priority_transactions.go +++ b/domain/miningmanager/mempool/revalidate_high_priority_transactions.go @@ -10,16 +10,83 @@ func (mp *mempool) revalidateHighPriorityTransactions() ([]*externalapi.DomainTr onEnd := logger.LogAndMeasureExecutionTime(log, "revalidateHighPriorityTransactions") defer onEnd() + // We revalidate transactions in topological order in case there are dependencies between them + + // Naturally transactions points to their dependencies, but since we want to start processing the dependencies + // first, we build the opposite DAG. We initially fill `queue` with transactions with no dependencies. + childrenByID := make(map[externalapi.DomainTransactionID]map[externalapi.DomainTransactionID]struct{}) + queue := make([]externalapi.DomainTransactionID, 0, len(mp.transactionsPool.highPriorityTransactions)) + for id, transaction := range mp.transactionsPool.highPriorityTransactions { + hasParents := false + for _, input := range transaction.Transaction().Inputs { + if _, ok := mp.transactionsPool.highPriorityTransactions[input.PreviousOutpoint.TransactionID]; !ok { + continue + } + + hasParents = true + if _, ok := childrenByID[input.PreviousOutpoint.TransactionID]; !ok { + childrenByID[input.PreviousOutpoint.TransactionID] = make(map[externalapi.DomainTransactionID]struct{}) + } + + childrenByID[input.PreviousOutpoint.TransactionID][id] = struct{}{} + } + + if !hasParents { + queue = append(queue, id) + } + } + + invalidTransactions := make(map[externalapi.DomainTransactionID]struct{}) + visited := make(map[externalapi.DomainTransactionID]struct{}) validTransactions := []*externalapi.DomainTransaction{} - for _, transaction := range mp.transactionsPool.highPriorityTransactions { + + // Now we iterate the DAG in topological order using BFS + for len(queue) > 0 { + var txID externalapi.DomainTransactionID + txID, queue = queue[0], queue[1:] + + if _, ok := visited[txID]; ok { + continue + } + visited[txID] = struct{}{} + + if _, ok := invalidTransactions[txID]; ok { + continue + } + + transaction := mp.transactionsPool.highPriorityTransactions[txID] isValid, err := mp.revalidateTransaction(transaction) if err != nil { return nil, err } + if !isValid { + // Invalidate the offspring of this transaction + invalidateQueue := []externalapi.DomainTransactionID{txID} + for len(invalidateQueue) > 0 { + var current externalapi.DomainTransactionID + current, invalidateQueue = invalidateQueue[0], invalidateQueue[1:] + + if _, ok := invalidTransactions[current]; ok { + continue + } + + invalidTransactions[current] = struct{}{} + if children, ok := childrenByID[current]; ok { + for child := range children { + invalidateQueue = append(invalidateQueue, child) + } + } + } continue } + if children, ok := childrenByID[txID]; ok { + for child := range children { + queue = append(queue, child) + } + } + validTransactions = append(validTransactions, transaction.Transaction().Clone()) } diff --git a/domain/miningmanager/model/interface_mempool.go b/domain/miningmanager/model/interface_mempool.go index fe9be9b5c..777d23cde 100644 --- a/domain/miningmanager/model/interface_mempool.go +++ b/domain/miningmanager/model/interface_mempool.go @@ -2,6 +2,7 @@ package model import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" ) // Mempool maintains a set of known transactions that @@ -11,7 +12,7 @@ type Mempool interface { BlockCandidateTransactions() []*externalapi.DomainTransaction ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) ( acceptedTransactions []*externalapi.DomainTransaction, err error) - RemoveTransactions(txs []*externalapi.DomainTransaction, removeRedeemers bool) error + RemoveInvalidTransactions(err *ruleerrors.ErrInvalidTransactionsInNewBlock) error GetTransaction( transactionID *externalapi.DomainTransactionID, includeTransactionPool bool, diff --git a/domain/utxoindex/utxoindex.go b/domain/utxoindex/utxoindex.go index a5c4353ad..670fb25b0 100644 --- a/domain/utxoindex/utxoindex.go +++ b/domain/utxoindex/utxoindex.go @@ -52,6 +52,8 @@ func (ui *UTXOIndex) Reset() error { ui.mutex.Lock() defer ui.mutex.Unlock() + log.Infof("Starting UTXO index reset") + err := ui.store.deleteAll() if err != nil { return err @@ -88,7 +90,13 @@ func (ui *UTXOIndex) Reset() error { } // This has to be done last to mark that the reset went smoothly and no reset has to be called next time. - return ui.store.updateAndCommitVirtualParentsWithoutTransaction(virtualInfo.ParentHashes) + err = ui.store.updateAndCommitVirtualParentsWithoutTransaction(virtualInfo.ParentHashes) + if err != nil { + return err + } + + log.Infof("Finished UTXO index reset") + return nil } func (ui *UTXOIndex) isSynced() (bool, error) {