Compare commits

...

26 Commits

Author SHA1 Message Date
Mike Zak
45ba3bec43 Merge remote-tracking branch 'upstream/v0.11.0-dev' into mempool-redesign 2021-06-09 15:05:14 +03:00
Mike Zak
befc0172ee Add allowOrphans to ValidateAndInsertTransaction stub 2021-06-09 14:56:27 +03:00
Mike Zak
82e807cf5a Use maximumOrphanTransactionCount 2021-06-09 14:53:31 +03:00
Mike Zak
bfa804b300 If a transaction was removed from the mempool - update it's redeemers in orphan pool as well 2021-06-09 12:13:39 +03:00
Mike Zak
7115b54d3f Implement RemoveTransaction 2021-06-08 17:39:10 +03:00
Mike Zak
25cc6184d6 Implement transactionsPool.addTransaction 2021-06-08 16:30:53 +03:00
Mike Zak
4e46be67a4 Implemented processOrphansAfterAcceptedTransaction 2021-06-08 16:05:39 +03:00
Mike Zak
35eb4b9d87 Implement maybeAddOrphan and AddOrphan 2021-06-08 15:45:08 +03:00
Mike Zak
2402d48c3e Implement removeOrphan 2021-06-08 14:35:54 +03:00
Mike Zak
9a1d548e7f Implemented removeOrphan 2021-06-08 14:00:26 +03:00
Mike Zak
65da2462b8 Implemented mempoolUTXOSet.checkDoubleSpends 2021-06-08 10:44:02 +03:00
Mike Zak
4335a8eaf8 Implement removeTransaction, remove sanity checks 2021-06-08 10:35:52 +03:00
Mike Zak
d88fe305c5 Implemented mempoolUTXOSet.addTransaction 2021-06-08 10:27:06 +03:00
Mike Zak
099f023b5b Implement getParentsInPool 2021-06-08 10:16:45 +03:00
Mike Zak
2294559198 Move all model objects to model package 2021-06-08 10:01:46 +03:00
Mike Zak
71875c99d6 Invert the condition for banning when mempool rejects a transaction 2021-06-07 14:07:25 +03:00
Mike Zak
c099fd2986 Add error.go to mempool 2021-06-07 11:46:31 +03:00
Mike Zak
17357a0fca Orphan maps should be idToOrphan 2021-06-07 10:21:36 +03:00
Mike Zak
01edc43e36 Imeplement transactionsOrderedByFeeRate 2021-06-07 10:08:33 +03:00
Mike Zak
b412cd9bbc Revert "Rename isHighPriority to neverExpires"
This reverts commit b2da9a4a00.
2021-06-07 09:50:26 +03:00
Mike Zak
e5e266ff0b Add stub for checkDoubleSpends 2021-06-06 16:31:00 +03:00
Mike Zak
b2da9a4a00 Rename isHighPriority to neverExpires 2021-06-06 14:39:28 +03:00
Mike Zak
ff10ce145a implement expireOldTransactions and expireOrphanTransactions 2021-06-06 14:13:19 +03:00
Mike Zak
619b7ab8cd Implement BlockCandidateTransactions 2021-06-06 12:53:37 +03:00
Mike Zak
04c98ac6df Add constructors to all main objects 2021-06-06 12:49:52 +03:00
Mike Zak
94ddf0aab0 Added model and stubs for all main methods 2021-06-06 12:27:25 +03:00
25 changed files with 1997 additions and 1030 deletions

View File

@@ -7,7 +7,7 @@ import (
"github.com/kaspanet/kaspad/domain"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool_old"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/pkg/errors"
@@ -175,15 +175,15 @@ func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransact
err = flow.Domain().MiningManager().ValidateAndInsertTransaction(tx, true)
if err != nil {
ruleErr := &mempool.RuleError{}
ruleErr := &mempool_old.RuleError{}
if !errors.As(err, ruleErr) {
return errors.Wrapf(err, "failed to process transaction %s", txID)
}
shouldBan := true
if txRuleErr := (&mempool.TxRuleError{}); errors.As(ruleErr.Err, txRuleErr) {
if txRuleErr.RejectCode != mempool.RejectInvalid {
shouldBan = false
shouldBan := false
if txRuleErr := (&mempool_old.TxRuleError{}); errors.As(ruleErr.Err, txRuleErr) {
if txRuleErr.RejectCode == mempool_old.RejectInvalid {
shouldBan = true
}
}

View File

@@ -4,7 +4,7 @@ import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool_old"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/pkg/errors"
)
@@ -23,7 +23,7 @@ func HandleSubmitTransaction(context *rpccontext.Context, _ *router.Router, requ
transactionID := consensushashing.TransactionID(domainTransaction)
err = context.ProtocolManager.AddTransaction(domainTransaction)
if err != nil {
if !errors.As(err, &mempool.RuleError{}) {
if !errors.As(err, &mempool_old.RuleError{}) {
return nil, err
}

View File

@@ -4,7 +4,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/miningmanager/blocktemplatebuilder"
mempoolpkg "github.com/kaspanet/kaspad/domain/miningmanager/mempool"
mempoolpkg "github.com/kaspanet/kaspad/domain/miningmanager/mempool_old"
)
// Factory instantiates new mining managers

View File

@@ -0,0 +1,39 @@
package mempool
import (
"time"
"github.com/kaspanet/kaspad/domain/dagconfig"
)
const (
defaultTransactionExpireIntervalSeconds uint64 = 60
defaultTransactionExpireScanIntervalSeconds uint64 = 10
defaultOrphanExpireIntervalSeconds uint64 = 60
defaultOrphanExpireScanIntervalSeconds uint64 = 10
defaultMaximumOrphanTransactionSize = 100000
defaultMaximumOrphanTransactionCount = 50
)
type config struct {
transactionExpireIntervalDAAScore uint64
transactionExpireScanIntervalDAAScore uint64
orphanExpireIntervalDAAScore uint64
orphanExpireScanIntervalDAAScore uint64
maximumOrphanTransactionSize int
maximumOrphanTransactionCount int
}
func defaultConfig(dagParams *dagconfig.Params) *config {
targetBlocksPerSecond := uint64(dagParams.TargetTimePerBlock / time.Second)
return &config{
transactionExpireIntervalDAAScore: defaultTransactionExpireIntervalSeconds / targetBlocksPerSecond,
transactionExpireScanIntervalDAAScore: defaultTransactionExpireScanIntervalSeconds / targetBlocksPerSecond,
orphanExpireIntervalDAAScore: defaultOrphanExpireIntervalSeconds / targetBlocksPerSecond,
orphanExpireScanIntervalDAAScore: defaultOrphanExpireScanIntervalSeconds / targetBlocksPerSecond,
maximumOrphanTransactionSize: defaultMaximumOrphanTransactionSize,
maximumOrphanTransactionCount: defaultMaximumOrphanTransactionCount,
}
}

View File

@@ -39,32 +39,34 @@ type RejectCode uint8
// These constants define the various supported reject codes.
const (
RejectMalformed RejectCode = 0x01
RejectInvalid RejectCode = 0x10
RejectObsolete RejectCode = 0x11
RejectDuplicate RejectCode = 0x12
RejectNotRequested RejectCode = 0x13
RejectNonstandard RejectCode = 0x40
RejectDust RejectCode = 0x41
RejectInsufficientFee RejectCode = 0x42
RejectFinality RejectCode = 0x43
RejectDifficulty RejectCode = 0x44
RejectImmatureSpend RejectCode = 0x45
RejectMalformed RejectCode = 0x01
RejectInvalid RejectCode = 0x10
RejectObsolete RejectCode = 0x11
RejectDuplicate RejectCode = 0x12
RejectNotRequested RejectCode = 0x13
RejectNonstandard RejectCode = 0x40
RejectDust RejectCode = 0x41
RejectInsufficientFee RejectCode = 0x42
RejectFinality RejectCode = 0x43
RejectDifficulty RejectCode = 0x44
RejectImmatureSpend RejectCode = 0x45
RejectIncompatibleOrphan RejectCode = 0x64
)
// Map of reject codes back strings for pretty printing.
var rejectCodeStrings = map[RejectCode]string{
RejectMalformed: "REJECT_MALFORMED",
RejectInvalid: "REJECT_INVALID",
RejectObsolete: "REJECT_OBSOLETE",
RejectDuplicate: "REJECT_DUPLICATE",
RejectNonstandard: "REJECT_NONSTANDARD",
RejectDust: "REJECT_DUST",
RejectInsufficientFee: "REJECT_INSUFFICIENTFEE",
RejectFinality: "REJECT_FINALITY",
RejectDifficulty: "REJECT_DIFFICULTY",
RejectNotRequested: "REJECT_NOTREQUESTED",
RejectImmatureSpend: "REJECT_IMMATURESPEND",
RejectMalformed: "REJECT_MALFORMED",
RejectInvalid: "REJECT_INVALID",
RejectObsolete: "REJECT_OBSOLETE",
RejectDuplicate: "REJECT_DUPLICATE",
RejectNonstandard: "REJECT_NON_STANDARD",
RejectDust: "REJECT_DUST",
RejectInsufficientFee: "REJECT_INSUFFICIENT_FEE",
RejectFinality: "REJECT_FINALITY",
RejectDifficulty: "REJECT_DIFFICULTY",
RejectNotRequested: "REJECT_NOT_REQUESTED",
RejectImmatureSpend: "REJECT_IMMATURE_SPEND",
RejectIncompatibleOrphan: "REJECT_INCOMPATIBLE_ORPHAN",
}
// String returns the RejectCode in human-readable form.

View File

@@ -0,0 +1,9 @@
package mempool
func (mp *mempool) virtualDAAScore() (uint64, error) {
virtualInfo, err := mp.consensus.GetVirtualInfo()
if err != nil {
return 0, err
}
return virtualInfo.DAAScore, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
package mempool
import (
"fmt"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
)
type mempoolUTXOSet struct {
mempool *mempool
poolUnspentOutputs model.OutpointToUTXOEntry
transactionsByPreviousOutpoint model.OutpointToTransaction
}
func newMempoolUTXOSet(mp *mempool) *mempoolUTXOSet {
return &mempoolUTXOSet{
mempool: mp,
poolUnspentOutputs: model.OutpointToUTXOEntry{},
transactionsByPreviousOutpoint: model.OutpointToTransaction{},
}
}
func (mpus *mempoolUTXOSet) addTransaction(transaction *model.MempoolTransaction) {
outpoint := &externalapi.DomainOutpoint{TransactionID: *transaction.TransactionID()}
for i, input := range transaction.Transaction().Inputs {
outpoint.Index = uint32(i)
delete(mpus.poolUnspentOutputs, *outpoint)
mpus.transactionsByPreviousOutpoint[input.PreviousOutpoint] = transaction
}
for i, output := range transaction.Transaction().Outputs {
outpoint := externalapi.DomainOutpoint{TransactionID: *transaction.TransactionID(), Index: uint32(i)}
mpus.poolUnspentOutputs[outpoint] =
utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, model.UnacceptedDAAScore)
}
}
func (mpus *mempoolUTXOSet) removeTransaction(transaction *model.MempoolTransaction) {
for _, input := range transaction.Transaction().Inputs {
mpus.poolUnspentOutputs[input.PreviousOutpoint] = input.UTXOEntry
delete(mpus.transactionsByPreviousOutpoint, input.PreviousOutpoint)
}
outpoint := externalapi.DomainOutpoint{TransactionID: *transaction.TransactionID()}
for i := range transaction.Transaction().Outputs {
outpoint.Index = uint32(i)
delete(mpus.poolUnspentOutputs, outpoint)
}
}
func (mpus *mempoolUTXOSet) checkDoubleSpends(transaction *model.MempoolTransaction) error {
outpoint := externalapi.DomainOutpoint{TransactionID: *transaction.TransactionID()}
for i, input := range transaction.Transaction().Inputs {
outpoint.Index = uint32(i)
if existingTransaction, exists := mpus.transactionsByPreviousOutpoint[input.PreviousOutpoint]; exists {
str := fmt.Sprintf("output %s already spent by transaction %s in the memory pool",
input.PreviousOutpoint, existingTransaction.TransactionID())
return txRuleError(RejectDuplicate, str)
}
}
return nil
}

View File

@@ -0,0 +1,6 @@
package model
import "math"
// UnacceptedDAAScore is used to for UTXOEntries that were created by transactions in the mempool.
const UnacceptedDAAScore = math.MaxUint64

View File

@@ -0,0 +1,14 @@
package model
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
// IDToTransaction maps transactionID to a MempoolTransaction
type IDToTransaction map[externalapi.DomainTransactionID]*MempoolTransaction
// OutpointToUTXOEntry maps an outpoint to a UTXOEntry
type OutpointToUTXOEntry map[externalapi.DomainOutpoint]externalapi.UTXOEntry
// OutpointToTransaction maps an outpoint to a MempoolTransaction
type OutpointToTransaction map[externalapi.DomainOutpoint]*MempoolTransaction

View File

@@ -0,0 +1,49 @@
package model
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
)
// MempoolTransaction represents a transaction inside the main TransactionPool
type MempoolTransaction struct {
transaction *externalapi.DomainTransaction
parentTransactionsInPool OutpointToTransaction
isHighPriority bool
addedAtDAAScore uint64
}
func NewMempoolTransaction(
transaction *externalapi.DomainTransaction,
parentTransactionsInPool OutpointToTransaction,
isHighPriority bool,
addedAtDAAScore uint64,
) *MempoolTransaction {
return &MempoolTransaction{
transaction: transaction,
parentTransactionsInPool: parentTransactionsInPool,
isHighPriority: isHighPriority,
addedAtDAAScore: addedAtDAAScore,
}
}
// TransactionID returns the ID of this MempoolTransaction
func (mt *MempoolTransaction) TransactionID() *externalapi.DomainTransactionID {
return consensushashing.TransactionID(mt.transaction)
}
func (mt *MempoolTransaction) Transaction() *externalapi.DomainTransaction {
return mt.transaction
}
func (mt *MempoolTransaction) ParentTransactionsInPool() OutpointToTransaction {
return mt.parentTransactionsInPool
}
func (mt *MempoolTransaction) IsHighPriority() bool {
return mt.isHighPriority
}
func (mt *MempoolTransaction) AddedAtDAAScore() uint64 {
return mt.addedAtDAAScore
}

View File

@@ -0,0 +1,75 @@
package model
import (
"sort"
"github.com/pkg/errors"
)
// TransactionsOrderedByFeeRate represents a set of MempoolTransactions ordered by their fee / mass rate
type TransactionsOrderedByFeeRate struct {
slice []*MempoolTransaction
}
// Push inserts a transaction into the set, placing it in the correct place to preserve order
func (tobf *TransactionsOrderedByFeeRate) Push(transaction *MempoolTransaction) error {
index, err := tobf.findTransactionIndex(transaction)
if err != nil {
return err
}
tobf.slice = append(tobf.slice[:index],
append([]*MempoolTransaction{transaction}, tobf.slice[index:]...)...)
return nil
}
// Remove removes the given transaction from the set.
// Returns an error if transaction does not exist in the set, or if the given transaction does not have mass
// and fee filled in.
func (tobf *TransactionsOrderedByFeeRate) Remove(transaction *MempoolTransaction) error {
index, err := tobf.findTransactionIndex(transaction)
if err != nil {
return err
}
txID := transaction.TransactionID()
if !tobf.slice[index].TransactionID().Equal(txID) {
return errors.Errorf("Couldn't find %s in mp.orderedTransactionsByFeeRate", txID)
}
return tobf.RemoveAtIndex(index)
}
// RemoveAtIndex removes the transaction at the given index.
// Returns an error in case of out-of-bounds index.
func (tobf *TransactionsOrderedByFeeRate) RemoveAtIndex(index int) error {
if index < 0 || index > len(tobf.slice)-1 {
return errors.Errorf("Index %d is out of bound of this TransactionsOrderedByFeeRate", index)
}
tobf.slice = append(tobf.slice[:index], tobf.slice[index+1:]...)
return nil
}
func (tobf *TransactionsOrderedByFeeRate) findTransactionIndex(transaction *MempoolTransaction) (int, error) {
if transaction.Transaction().Fee == 0 || transaction.Transaction().Mass == 0 {
return 0, errors.Errorf("findTxIndexInOrderedTransactionsByFeeRate expects a transaction with " +
"populated fee and mass")
}
txID := transaction.TransactionID()
txFeeRate := float64(transaction.Transaction().Fee) / float64(transaction.Transaction().Mass)
return sort.Search(len(tobf.slice), func(i int) bool {
iElement := tobf.slice[i]
elementFeeRate := float64(iElement.Transaction().Fee) / float64(iElement.Transaction().Mass)
if elementFeeRate > txFeeRate {
return true
}
if elementFeeRate == txFeeRate && txID.LessOrEqual(iElement.TransactionID()) {
return true
}
return false
}), nil
}

View File

@@ -0,0 +1,41 @@
package model
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
)
// OrphanTransaction represents a transaction in the OrphanPool
type OrphanTransaction struct {
transaction *externalapi.DomainTransaction
isHighPriority bool
addedAtDAAScore uint64
}
func NewOrphanTransaction(
transaction *externalapi.DomainTransaction,
isHighPriority bool,
addedAtDAAScore uint64,
) *OrphanTransaction {
return &OrphanTransaction{
transaction: transaction,
isHighPriority: isHighPriority,
addedAtDAAScore: addedAtDAAScore,
}
}
// TransactionID returns the ID of this OrphanTransaction
func (ot *OrphanTransaction) TransactionID() *externalapi.DomainTransactionID {
return consensushashing.TransactionID(ot.transaction)
}
func (ot *OrphanTransaction) Transaction() *externalapi.DomainTransaction {
return ot.transaction
}
func (ot *OrphanTransaction) IsHighPriority() bool {
return ot.isHighPriority
}
func (ot *OrphanTransaction) AddedAtDAAScore() uint64 {
return ot.addedAtDAAScore
}

View File

@@ -0,0 +1,8 @@
package model
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
type Transaction interface {
TransactionID() *externalapi.DomainTransactionID
Transaction() *externalapi.DomainTransaction
}

View File

@@ -0,0 +1,260 @@
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
}

View File

@@ -0,0 +1,151 @@
package mempool
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
)
type transactionsPool struct {
mempool *mempool
allTransactions model.IDToTransaction
highPriorityTransactions model.IDToTransaction
chainedTransactionsByPreviousOutpoint model.OutpointToTransaction
transactionsOrderedByFeeRate model.TransactionsOrderedByFeeRate
lastExpireScan uint64
}
func newTransactionsPool(mp *mempool) *transactionsPool {
return &transactionsPool{
mempool: mp,
allTransactions: model.IDToTransaction{},
highPriorityTransactions: model.IDToTransaction{},
chainedTransactionsByPreviousOutpoint: model.OutpointToTransaction{},
transactionsOrderedByFeeRate: model.TransactionsOrderedByFeeRate{},
lastExpireScan: 0,
}
}
func (tp *transactionsPool) addTransaction(transaction *externalapi.DomainTransaction,
parentTransactionsInPool model.OutpointToTransaction, isHighPriority bool) error {
virtualDAAScore, err := tp.mempool.virtualDAAScore()
if err != nil {
return err
}
mempoolTransaction := model.NewMempoolTransaction(
transaction, parentTransactionsInPool, isHighPriority, virtualDAAScore)
return tp.addMempoolTransaction(mempoolTransaction)
}
func (tp *transactionsPool) addMempoolTransaction(transaction *model.MempoolTransaction) error {
tp.allTransactions[*transaction.TransactionID()] = transaction
for outpoint, parentTransactionInPool := range transaction.ParentTransactionsInPool() {
tp.chainedTransactionsByPreviousOutpoint[outpoint] = parentTransactionInPool
}
tp.mempool.mempoolUTXOSet.addTransaction(transaction)
err := tp.transactionsOrderedByFeeRate.Push(transaction)
if err != nil {
return err
}
if transaction.IsHighPriority() {
tp.highPriorityTransactions[*transaction.TransactionID()] = transaction
}
return nil
}
func (tp *transactionsPool) removeTransaction(transaction *model.MempoolTransaction) error {
delete(tp.allTransactions, *transaction.TransactionID())
err := tp.transactionsOrderedByFeeRate.Remove(transaction)
if err != nil {
return err
}
delete(tp.highPriorityTransactions, *transaction.TransactionID())
for outpoint := range transaction.ParentTransactionsInPool() {
delete(tp.chainedTransactionsByPreviousOutpoint, outpoint)
}
return nil
}
func (tp *transactionsPool) expireOldTransactions() error {
virtualDAAScore, err := tp.mempool.virtualDAAScore()
if err != nil {
return err
}
if virtualDAAScore-tp.lastExpireScan < tp.mempool.config.transactionExpireScanIntervalDAAScore {
return nil
}
for _, mempoolTransaction := range tp.allTransactions {
// Never expire high priority transactions
if mempoolTransaction.IsHighPriority() {
continue
}
// Remove all transactions whose addedAtDAAScore is older then transactionExpireIntervalDAAScore
if virtualDAAScore-mempoolTransaction.AddedAtDAAScore() > tp.mempool.config.transactionExpireIntervalDAAScore {
err = tp.mempool.RemoveTransaction(mempoolTransaction.TransactionID(), true)
if err != nil {
return err
}
}
}
tp.lastExpireScan = virtualDAAScore
return nil
}
func (tp *transactionsPool) allReadyTransactions() []*externalapi.DomainTransaction {
result := []*externalapi.DomainTransaction{}
for _, mempoolTransaction := range tp.allTransactions {
if len(mempoolTransaction.ParentTransactionsInPool()) == 0 {
result = append(result, mempoolTransaction.Transaction())
}
}
return result
}
func (tp *transactionsPool) getParentTransactionsInPool(
transaction *externalapi.DomainTransaction) model.OutpointToTransaction {
parentsTransactionsInPool := model.OutpointToTransaction{}
for _, input := range transaction.Inputs {
transaction := tp.allTransactions[input.PreviousOutpoint.TransactionID]
parentsTransactionsInPool[input.PreviousOutpoint] = transaction
}
return parentsTransactionsInPool
}
func (tp *transactionsPool) getRedeemers(transaction *model.MempoolTransaction) []*model.MempoolTransaction {
queue := []*model.MempoolTransaction{transaction}
redeemers := []*model.MempoolTransaction{}
for len(queue) > 0 {
var current *model.MempoolTransaction
current, queue = queue[0], queue[1:]
outpoint := externalapi.DomainOutpoint{TransactionID: *current.TransactionID()}
for i := range current.Transaction().Outputs {
outpoint.Index = uint32(i)
if redeemerTransaction, ok := tp.chainedTransactionsByPreviousOutpoint[outpoint]; ok {
queue = append(queue, redeemerTransaction)
redeemers = append(redeemers, redeemerTransaction)
}
}
}
return redeemers
}

View File

@@ -59,10 +59,10 @@ be an exhaustive list.
Errors
Errors returned by this package are either the raw errors provided by underlying
calls or of type mempool.RuleError. Since there are two classes of rules
calls or of type mempool_old.RuleError. Since there are two classes of rules
(mempool acceptance rules and blockDAG (consensus) acceptance rules), the
mempool.RuleError type contains a single Err field which will, in turn, either
be a mempool.TxRuleError or a ruleerrors.RuleError. The first indicates a
mempool_old.RuleError type contains a single Err field which will, in turn, either
be a mempool_old.TxRuleError or a ruleerrors.RuleError. The first indicates a
violation of mempool acceptance rules while the latter indicates a violation of
consensus acceptance rules. This allows the caller to easily differentiate
between unexpected errors, such as database errors, versus errors due to rule
@@ -70,4 +70,4 @@ violations through type assertions. In addition, callers can programmatically
determine the specific rule violation by type asserting the Err field to one of
the aforementioned types and examining their underlying ErrorCode field.
*/
package mempool
package mempool_old

View File

@@ -0,0 +1,124 @@
// Copyright (c) 2014-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package mempool_old
import (
"fmt"
"github.com/pkg/errors"
)
// RuleError identifies a rule violation. It is used to indicate that
// processing of a transaction failed due to one of the many validation
// rules. The caller can use type assertions to determine if a failure was
// specifically due to a rule violation and use the Err field to access the
// underlying error, which will be either a TxRuleError or a
// ruleerrors.RuleError.
type RuleError struct {
Err error
}
// Error satisfies the error interface and prints human-readable errors.
func (e RuleError) Error() string {
if e.Err == nil {
return "<nil>"
}
return e.Err.Error()
}
// Unwrap unwraps the wrapped error
func (e RuleError) Unwrap() error {
return e.Err
}
// RejectCode represents a numeric value by which a remote peer indicates
// why a message was rejected.
type RejectCode uint8
// These constants define the various supported reject codes.
const (
RejectMalformed RejectCode = 0x01
RejectInvalid RejectCode = 0x10
RejectObsolete RejectCode = 0x11
RejectDuplicate RejectCode = 0x12
RejectNotRequested RejectCode = 0x13
RejectNonstandard RejectCode = 0x40
RejectDust RejectCode = 0x41
RejectInsufficientFee RejectCode = 0x42
RejectFinality RejectCode = 0x43
RejectDifficulty RejectCode = 0x44
RejectImmatureSpend RejectCode = 0x45
)
// Map of reject codes back strings for pretty printing.
var rejectCodeStrings = map[RejectCode]string{
RejectMalformed: "REJECT_MALFORMED",
RejectInvalid: "REJECT_INVALID",
RejectObsolete: "REJECT_OBSOLETE",
RejectDuplicate: "REJECT_DUPLICATE",
RejectNonstandard: "REJECT_NONSTANDARD",
RejectDust: "REJECT_DUST",
RejectInsufficientFee: "REJECT_INSUFFICIENTFEE",
RejectFinality: "REJECT_FINALITY",
RejectDifficulty: "REJECT_DIFFICULTY",
RejectNotRequested: "REJECT_NOTREQUESTED",
RejectImmatureSpend: "REJECT_IMMATURESPEND",
}
// String returns the RejectCode in human-readable form.
func (code RejectCode) String() string {
if s, ok := rejectCodeStrings[code]; ok {
return s
}
return fmt.Sprintf("Unknown RejectCode (%d)", uint8(code))
}
// TxRuleError identifies a rule violation. It is used to indicate that
// processing of a transaction failed due to one of the many validation
// rules. The caller can use type assertions to determine if a failure was
// specifically due to a rule violation and access the ErrorCode field to
// ascertain the specific reason for the rule violation.
type TxRuleError struct {
RejectCode RejectCode // The code to send with reject messages
Description string // Human readable description of the issue
}
// Error satisfies the error interface and prints human-readable errors.
func (e TxRuleError) Error() string {
return e.Description
}
// txRuleError creates an underlying TxRuleError with the given a set of
// arguments and returns a RuleError that encapsulates it.
func txRuleError(c RejectCode, desc string) RuleError {
return RuleError{
Err: TxRuleError{RejectCode: c, Description: desc},
}
}
func newRuleError(err error) RuleError {
return RuleError{
Err: err,
}
}
// extractRejectCode attempts to return a relevant reject code for a given error
// by examining the error for known types. It will return true if a code
// was successfully extracted.
func extractRejectCode(err error) (RejectCode, bool) {
// Pull the underlying error out of a RuleError.
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); ok {
err = ruleErr.Err
}
var trErr TxRuleError
if errors.As(err, &trErr) {
return trErr.RejectCode, true
}
return RejectInvalid, false
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package mempool_old
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
)
var log = logger.RegisterSubSystem("TXMP")

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
package mempool
package mempool_old
import (
"math"

View File

@@ -2,10 +2,11 @@
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package mempool
package mempool_old
import (
"fmt"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package mempool
package mempool_old
import (
"bytes"

View File

@@ -1,10 +1,11 @@
package miningmanager_test
import (
"github.com/kaspanet/kaspad/domain/miningmanager/mempool"
"strings"
"testing"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool_old"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
@@ -84,8 +85,8 @@ func TestImmatureSpend(t *testing.T) {
miningManager := miningFactory.NewMiningManager(tc, &consensusConfig.Params)
tx := createTransactionWithUTXOEntry(t, 0)
err = miningManager.ValidateAndInsertTransaction(tx, false)
txRuleError := &mempool.TxRuleError{}
if !errors.As(err, txRuleError) || txRuleError.RejectCode != mempool.RejectImmatureSpend {
txRuleError := &mempool_old.TxRuleError{}
if !errors.As(err, txRuleError) || txRuleError.RejectCode != mempool_old.RejectImmatureSpend {
t.Fatalf("Unexpected error %+v", err)
}
transactionsFromMempool := miningManager.AllTransactions()