mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-09-15 14:00:13 +00:00
261 lines
7.7 KiB
Go
261 lines
7.7 KiB
Go
package mempool
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
|
|
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/estimatedsize"
|
|
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type idToOrphan map[externalapi.DomainTransactionID]*model.OrphanTransaction
|
|
type previousOutpointToOrphans map[externalapi.DomainOutpoint]idToOrphan
|
|
|
|
type orphansPool struct {
|
|
mempool *mempool
|
|
allOrphans idToOrphan
|
|
orphansByPreviousOutpoint previousOutpointToOrphans
|
|
lastExpireScan uint64
|
|
}
|
|
|
|
func newOrphansPool(mp *mempool) *orphansPool {
|
|
return &orphansPool{
|
|
mempool: mp,
|
|
allOrphans: idToOrphan{},
|
|
orphansByPreviousOutpoint: previousOutpointToOrphans{},
|
|
lastExpireScan: 0,
|
|
}
|
|
}
|
|
|
|
func (op *orphansPool) maybeAddOrphan(transaction *externalapi.DomainTransaction, isHighPriority bool) error {
|
|
serializedLength := estimatedsize.TransactionEstimatedSerializedSize(transaction)
|
|
if serializedLength > uint64(op.mempool.config.maximumOrphanTransactionSize) {
|
|
str := fmt.Sprintf("orphan transaction size of %d bytes is "+
|
|
"larger than max allowed size of %d bytes",
|
|
serializedLength, op.mempool.config.maximumOrphanTransactionSize)
|
|
return txRuleError(RejectIncompatibleOrphan, str)
|
|
}
|
|
if op.mempool.config.maximumOrphanTransactionCount <= 0 {
|
|
return nil
|
|
}
|
|
for len(op.allOrphans) >= op.mempool.config.maximumOrphanTransactionCount {
|
|
// Don't remove redeemers in the case of a random eviction since
|
|
// it is quite possible it might be needed again shortly.
|
|
err := op.removeOrphan(op.randomOrphan().TransactionID(), false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return op.addOrphan(transaction, isHighPriority)
|
|
}
|
|
|
|
func (op *orphansPool) addOrphan(transaction *externalapi.DomainTransaction, isHighPriority bool) error {
|
|
virtualDAAScore, err := op.mempool.virtualDAAScore()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
orphanTransaction := model.NewOrphanTransaction(transaction, isHighPriority, virtualDAAScore)
|
|
|
|
op.allOrphans[*orphanTransaction.TransactionID()] = orphanTransaction
|
|
for _, input := range transaction.Inputs {
|
|
if input.UTXOEntry == nil {
|
|
if _, ok := op.orphansByPreviousOutpoint[input.PreviousOutpoint]; !ok {
|
|
op.orphansByPreviousOutpoint[input.PreviousOutpoint] = idToOrphan{}
|
|
}
|
|
op.orphansByPreviousOutpoint[input.PreviousOutpoint][*orphanTransaction.TransactionID()] = orphanTransaction
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) processOrphansAfterAcceptedTransaction(acceptedTransaction *model.MempoolTransaction) (
|
|
acceptedOrphans []*model.MempoolTransaction, err error) {
|
|
|
|
acceptedOrphans = []*model.MempoolTransaction{}
|
|
queue := []*model.MempoolTransaction{acceptedTransaction}
|
|
|
|
for len(queue) > 0 {
|
|
var current *model.MempoolTransaction
|
|
current, queue = queue[0], queue[1:]
|
|
|
|
outpoint := externalapi.DomainOutpoint{TransactionID: *current.TransactionID()}
|
|
for i, output := range current.Transaction().Outputs {
|
|
outpoint.Index = uint32(i)
|
|
orphans, ok := op.orphansByPreviousOutpoint[outpoint]
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, orphan := range orphans {
|
|
for _, input := range orphan.Transaction().Inputs {
|
|
if input.PreviousOutpoint.Equal(&outpoint) {
|
|
input.UTXOEntry = utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false,
|
|
model.UnacceptedDAAScore)
|
|
break
|
|
}
|
|
}
|
|
if countUnfilledInputs(orphan) == 0 {
|
|
unorphanedTransaction, err := op.unorphanTransaction(current)
|
|
if err != nil {
|
|
if errors.As(err, &RuleError{}) {
|
|
log.Infof("Failed to unorphan transaction %s due to rule error: %s", err)
|
|
continue
|
|
}
|
|
return nil, err
|
|
}
|
|
acceptedOrphans = append(acceptedOrphans, unorphanedTransaction)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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(orphanTransaction *model.MempoolTransaction) (*model.MempoolTransaction, error) {
|
|
err := op.removeOrphan(orphanTransaction.TransactionID(), false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = op.mempool.validateTransactionInContext(orphanTransaction.Transaction())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
virtualDAAScore, err := op.mempool.virtualDAAScore()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mempoolTransaction := model.NewMempoolTransaction(
|
|
orphanTransaction.Transaction(),
|
|
op.mempool.transactionsPool.getParentTransactionsInPool(orphanTransaction.Transaction()),
|
|
false,
|
|
virtualDAAScore,
|
|
)
|
|
err = op.mempool.transactionsPool.addMempoolTransaction(mempoolTransaction)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return mempoolTransaction, 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 {
|
|
orphans, ok := op.orphansByPreviousOutpoint[input.PreviousOutpoint]
|
|
if !ok {
|
|
return errors.Errorf("Input No. %d of %s (%s) doesn't exist in orphansByPreviousOutpoint",
|
|
i, orphanTransactionID, input.PreviousOutpoint)
|
|
}
|
|
delete(orphans, *orphanTransactionID)
|
|
if len(orphans) == 0 {
|
|
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)
|
|
for _, orphan := range op.orphansByPreviousOutpoint[outpoint] {
|
|
// 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.virtualDAAScore()
|
|
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(), true)
|
|
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)
|
|
for _, orphan := range op.orphansByPreviousOutpoint[outpoint] {
|
|
for _, input := range orphan.Transaction().Inputs {
|
|
if input.PreviousOutpoint.TransactionID.Equal(removedTransaction.TransactionID()) {
|
|
input.UTXOEntry = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (op *orphansPool) randomOrphan() *model.OrphanTransaction {
|
|
for _, orphan := range op.allOrphans {
|
|
return orphan
|
|
}
|
|
|
|
return nil
|
|
}
|