mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-07-06 04:42:31 +00:00
[DEV-339] Check how mempool handles dependent transactions, see if there's any problem there (if so - fix it), and tell the team about it (#169)
* [DEV-339] Handling of dependent transactions in mempool * [DEV-339] Small fixes after code review * [DEV-339] Fixed compilation * [DEV-339] Removed extra loop in addTransaction function * [DEV-339] Changed addTransaction do not loop on inputs second time
This commit is contained in:
parent
9507ed0a97
commit
b7850b382d
@ -147,6 +147,11 @@ type TxDesc struct {
|
|||||||
// StartingPriority is the priority of the transaction when it was added
|
// StartingPriority is the priority of the transaction when it was added
|
||||||
// to the pool.
|
// to the pool.
|
||||||
StartingPriority float64
|
StartingPriority float64
|
||||||
|
|
||||||
|
// depCount is not 0 for dependent transaction. Dependent transaction is
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// orphanTx is normal transaction that references an ancestor transaction
|
// orphanTx is normal transaction that references an ancestor transaction
|
||||||
@ -168,6 +173,8 @@ type TxPool struct {
|
|||||||
mtx sync.RWMutex
|
mtx sync.RWMutex
|
||||||
cfg Config
|
cfg Config
|
||||||
pool map[daghash.Hash]*TxDesc
|
pool map[daghash.Hash]*TxDesc
|
||||||
|
depends map[daghash.Hash]*TxDesc
|
||||||
|
dependsByPrev map[wire.OutPoint]map[daghash.Hash]*TxDesc
|
||||||
orphans map[daghash.Hash]*orphanTx
|
orphans map[daghash.Hash]*orphanTx
|
||||||
orphansByPrev map[wire.OutPoint]map[daghash.Hash]*util.Tx
|
orphansByPrev map[wire.OutPoint]map[daghash.Hash]*util.Tx
|
||||||
outpoints map[wire.OutPoint]*util.Tx
|
outpoints map[wire.OutPoint]*util.Tx
|
||||||
@ -390,8 +397,7 @@ func (mp *TxPool) isTransactionInPool(hash *daghash.Hash) bool {
|
|||||||
if _, exists := mp.pool[*hash]; exists {
|
if _, exists := mp.pool[*hash]; exists {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
return mp.isInDependPool(hash)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTransactionInPool returns whether or not the passed transaction already
|
// IsTransactionInPool returns whether or not the passed transaction already
|
||||||
@ -407,6 +413,29 @@ func (mp *TxPool) IsTransactionInPool(hash *daghash.Hash) bool {
|
|||||||
return inPool
|
return inPool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isInDependPool returns whether or not the passed transaction already
|
||||||
|
// exists in the depend pool.
|
||||||
|
//
|
||||||
|
// This function MUST be called with the mempool lock held (for reads).
|
||||||
|
func (mp *TxPool) isInDependPool(hash *daghash.Hash) bool {
|
||||||
|
if _, exists := mp.depends[*hash]; exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInDependPool returns whether or not the passed transaction already
|
||||||
|
// exists in the main pool.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent access.
|
||||||
|
func (mp *TxPool) IsInDependPool(hash *daghash.Hash) bool {
|
||||||
|
// Protect concurrent access.
|
||||||
|
mp.mtx.RLock()
|
||||||
|
defer mp.mtx.RUnlock()
|
||||||
|
return mp.isInDependPool(hash)
|
||||||
|
}
|
||||||
|
|
||||||
// isOrphanInPool returns whether or not the passed transaction already exists
|
// isOrphanInPool returns whether or not the passed transaction already exists
|
||||||
// in the orphan pool.
|
// in the orphan pool.
|
||||||
//
|
//
|
||||||
@ -470,7 +499,7 @@ func (mp *TxPool) removeTransaction(tx *util.Tx, removeRedeemers bool, restoreIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove the transaction if needed.
|
// Remove the transaction if needed.
|
||||||
if txDesc, exists := mp.pool[*txID]; exists {
|
if txDesc, exists := mp.fetchTransaction(txID); exists {
|
||||||
// Remove unconfirmed address index entries associated with the
|
// Remove unconfirmed address index entries associated with the
|
||||||
// transaction if enabled.
|
// transaction if enabled.
|
||||||
if mp.cfg.AddrIndex != nil {
|
if mp.cfg.AddrIndex != nil {
|
||||||
@ -488,10 +517,46 @@ func (mp *TxPool) removeTransaction(tx *util.Tx, removeRedeemers bool, restoreIn
|
|||||||
entry := blockdag.NewUTXOEntry(prevOut, false, mining.UnminedHeight)
|
entry := blockdag.NewUTXOEntry(prevOut, false, mining.UnminedHeight)
|
||||||
diff.AddEntry(txIn.PreviousOutPoint, entry)
|
diff.AddEntry(txIn.PreviousOutPoint, entry)
|
||||||
}
|
}
|
||||||
|
if prevTxDesc, exists := mp.depends[txIn.PreviousOutPoint.TxID]; exists {
|
||||||
|
prevOut := prevTxDesc.Tx.MsgTx().TxOut[txIn.PreviousOutPoint.Index]
|
||||||
|
entry := blockdag.NewUTXOEntry(prevOut, false, mining.UnminedHeight)
|
||||||
|
diff.AddEntry(txIn.PreviousOutPoint, entry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delete(mp.outpoints, txIn.PreviousOutPoint)
|
delete(mp.outpoints, txIn.PreviousOutPoint)
|
||||||
}
|
}
|
||||||
delete(mp.pool, *txID)
|
|
||||||
|
if txDesc.depCount == 0 {
|
||||||
|
delete(mp.pool, *txID)
|
||||||
|
} else {
|
||||||
|
delete(mp.depends, *txID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process dependent transactions
|
||||||
|
prevOut := wire.OutPoint{TxID: *txID}
|
||||||
|
for txOutIdx := range tx.MsgTx().TxOut {
|
||||||
|
// Skip to the next available output if there are none.
|
||||||
|
prevOut.Index = uint32(txOutIdx)
|
||||||
|
depends, exists := mp.dependsByPrev[prevOut]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move independent transactions into main pool
|
||||||
|
for _, txD := range depends {
|
||||||
|
txD.depCount--
|
||||||
|
if txD.depCount == 0 {
|
||||||
|
// Transaction may be already removed by recursive calls, if removeRedeemers is true.
|
||||||
|
// So avoid moving it into main pool
|
||||||
|
if _, ok := mp.depends[*txD.Tx.ID()]; ok {
|
||||||
|
delete(mp.depends, *txD.Tx.ID())
|
||||||
|
mp.pool[*txD.Tx.ID()] = txD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(mp.dependsByPrev, prevOut)
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
mp.mpUTXOSet, err = mp.mpUTXOSet.WithDiff(diff)
|
mp.mpUTXOSet, err = mp.mpUTXOSet.WithDiff(diff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -540,7 +605,7 @@ func (mp *TxPool) RemoveDoubleSpends(tx *util.Tx) {
|
|||||||
// helper for maybeAcceptTransaction.
|
// helper for maybeAcceptTransaction.
|
||||||
//
|
//
|
||||||
// This function MUST be called with the mempool lock held (for writes).
|
// This function MUST be called with the mempool lock held (for writes).
|
||||||
func (mp *TxPool) addTransaction(tx *util.Tx, height int32, fee uint64) *TxDesc {
|
func (mp *TxPool) addTransaction(tx *util.Tx, height int32, fee uint64, parentsInPool []*wire.OutPoint) *TxDesc {
|
||||||
mp.cfg.DAG.UTXORLock()
|
mp.cfg.DAG.UTXORLock()
|
||||||
defer mp.cfg.DAG.UTXORUnlock()
|
defer mp.cfg.DAG.UTXORUnlock()
|
||||||
// Add the transaction to the pool and mark the referenced outpoints
|
// Add the transaction to the pool and mark the referenced outpoints
|
||||||
@ -554,9 +619,21 @@ func (mp *TxPool) addTransaction(tx *util.Tx, height int32, fee uint64) *TxDesc
|
|||||||
FeePerKB: fee * 1000 / uint64(tx.MsgTx().SerializeSize()),
|
FeePerKB: fee * 1000 / uint64(tx.MsgTx().SerializeSize()),
|
||||||
},
|
},
|
||||||
StartingPriority: mining.CalcPriority(tx.MsgTx(), mp.mpUTXOSet, height),
|
StartingPriority: mining.CalcPriority(tx.MsgTx(), mp.mpUTXOSet, height),
|
||||||
|
depCount: len(parentsInPool),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parentsInPool) == 0 {
|
||||||
|
mp.pool[*tx.ID()] = txD
|
||||||
|
} else {
|
||||||
|
mp.depends[*tx.ID()] = txD
|
||||||
|
for _, previousOutPoint := range parentsInPool {
|
||||||
|
if _, exists := mp.dependsByPrev[*previousOutPoint]; !exists {
|
||||||
|
mp.dependsByPrev[*previousOutPoint] = make(map[daghash.Hash]*TxDesc)
|
||||||
|
}
|
||||||
|
mp.dependsByPrev[*previousOutPoint][*tx.ID()] = txD
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mp.pool[*tx.ID()] = txD
|
|
||||||
for _, txIn := range tx.MsgTx().TxIn {
|
for _, txIn := range tx.MsgTx().TxIn {
|
||||||
mp.outpoints[txIn.PreviousOutPoint] = tx
|
mp.outpoints[txIn.PreviousOutPoint] = tx
|
||||||
}
|
}
|
||||||
@ -588,7 +665,7 @@ func (mp *TxPool) checkPoolDoubleSpend(tx *util.Tx) error {
|
|||||||
if txR, exists := mp.outpoints[txIn.PreviousOutPoint]; exists {
|
if txR, exists := mp.outpoints[txIn.PreviousOutPoint]; exists {
|
||||||
str := fmt.Sprintf("output %v already spent by "+
|
str := fmt.Sprintf("output %v already spent by "+
|
||||||
"transaction %v in the memory pool",
|
"transaction %v in the memory pool",
|
||||||
txIn.PreviousOutPoint, txR.Hash())
|
txIn.PreviousOutPoint, txR.ID())
|
||||||
return txRuleError(wire.RejectDuplicate, str)
|
return txRuleError(wire.RejectDuplicate, str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -607,6 +684,15 @@ func (mp *TxPool) CheckSpend(op wire.OutPoint) *util.Tx {
|
|||||||
return txR
|
return txR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function MUST be called with the mempool lock held (for reads).
|
||||||
|
func (mp *TxPool) fetchTransaction(txID *daghash.Hash) (*TxDesc, bool) {
|
||||||
|
txDesc, exists := mp.pool[*txID]
|
||||||
|
if !exists {
|
||||||
|
txDesc, exists = mp.depends[*txID]
|
||||||
|
}
|
||||||
|
return txDesc, exists
|
||||||
|
}
|
||||||
|
|
||||||
// FetchTransaction returns the requested transaction from the transaction pool.
|
// FetchTransaction returns the requested transaction from the transaction pool.
|
||||||
// This only fetches from the main transaction pool and does not include
|
// This only fetches from the main transaction pool and does not include
|
||||||
// orphans.
|
// orphans.
|
||||||
@ -615,10 +701,9 @@ func (mp *TxPool) CheckSpend(op wire.OutPoint) *util.Tx {
|
|||||||
func (mp *TxPool) FetchTransaction(txID *daghash.Hash) (*util.Tx, error) {
|
func (mp *TxPool) FetchTransaction(txID *daghash.Hash) (*util.Tx, error) {
|
||||||
// Protect concurrent access.
|
// Protect concurrent access.
|
||||||
mp.mtx.RLock()
|
mp.mtx.RLock()
|
||||||
txDesc, exists := mp.pool[*txID]
|
defer mp.mtx.RUnlock()
|
||||||
mp.mtx.RUnlock()
|
|
||||||
|
|
||||||
if exists {
|
if txDesc, exists := mp.fetchTransaction(txID); exists {
|
||||||
return txDesc.Tx, nil
|
return txDesc.Tx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -746,6 +831,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
|
|||||||
// is not handled by this function, and the caller should use
|
// is not handled by this function, and the caller should use
|
||||||
// maybeAddOrphan if this behavior is desired.
|
// maybeAddOrphan if this behavior is desired.
|
||||||
var missingParents []*daghash.Hash
|
var missingParents []*daghash.Hash
|
||||||
|
var parentsInPool []*wire.OutPoint
|
||||||
for _, txIn := range tx.MsgTx().TxIn {
|
for _, txIn := range tx.MsgTx().TxIn {
|
||||||
if _, ok := mp.mpUTXOSet.Get(txIn.PreviousOutPoint); !ok {
|
if _, ok := mp.mpUTXOSet.Get(txIn.PreviousOutPoint); !ok {
|
||||||
// Must make a copy of the hash here since the iterator
|
// Must make a copy of the hash here since the iterator
|
||||||
@ -755,6 +841,9 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
|
|||||||
hashCopy := txIn.PreviousOutPoint.TxID
|
hashCopy := txIn.PreviousOutPoint.TxID
|
||||||
missingParents = append(missingParents, &hashCopy)
|
missingParents = append(missingParents, &hashCopy)
|
||||||
}
|
}
|
||||||
|
if mp.isTransactionInPool(&txIn.PreviousOutPoint.TxID) {
|
||||||
|
parentsInPool = append(parentsInPool, &txIn.PreviousOutPoint)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(missingParents) > 0 {
|
if len(missingParents) > 0 {
|
||||||
return missingParents, nil, nil
|
return missingParents, nil, nil
|
||||||
@ -901,7 +990,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add to transaction pool.
|
// Add to transaction pool.
|
||||||
txD := mp.addTransaction(tx, bestHeight, txFee)
|
txD := mp.addTransaction(tx, bestHeight, txFee, parentsInPool)
|
||||||
|
|
||||||
log.Debugf("Accepted transaction %v (pool size: %v)", txID,
|
log.Debugf("Accepted transaction %v (pool size: %v)", txID,
|
||||||
len(mp.pool))
|
len(mp.pool))
|
||||||
@ -1106,6 +1195,16 @@ func (mp *TxPool) Count() int {
|
|||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DepCount returns the number of dependent transactions in the main pool. It does not
|
||||||
|
// include the orphan pool.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent access.
|
||||||
|
func (mp *TxPool) DepCount() int {
|
||||||
|
mp.mtx.RLock()
|
||||||
|
defer mp.mtx.RUnlock()
|
||||||
|
return len(mp.depends)
|
||||||
|
}
|
||||||
|
|
||||||
// TxIDs returns a slice of IDs for all of the transactions in the memory
|
// TxIDs returns a slice of IDs for all of the transactions in the memory
|
||||||
// pool.
|
// pool.
|
||||||
//
|
//
|
||||||
@ -1250,6 +1349,8 @@ func New(cfg *Config) *TxPool {
|
|||||||
return &TxPool{
|
return &TxPool{
|
||||||
cfg: *cfg,
|
cfg: *cfg,
|
||||||
pool: make(map[daghash.Hash]*TxDesc),
|
pool: make(map[daghash.Hash]*TxDesc),
|
||||||
|
depends: make(map[daghash.Hash]*TxDesc),
|
||||||
|
dependsByPrev: make(map[wire.OutPoint]map[daghash.Hash]*TxDesc),
|
||||||
orphans: make(map[daghash.Hash]*orphanTx),
|
orphans: make(map[daghash.Hash]*orphanTx),
|
||||||
orphansByPrev: make(map[wire.OutPoint]map[daghash.Hash]*util.Tx),
|
orphansByPrev: make(map[wire.OutPoint]map[daghash.Hash]*util.Tx),
|
||||||
nextExpireScan: time.Now().Add(orphanExpireScanInterval),
|
nextExpireScan: time.Now().Add(orphanExpireScanInterval),
|
||||||
|
@ -358,7 +358,7 @@ type testContext struct {
|
|||||||
// orphan pool and transaction pool status. It also further determines if it
|
// orphan pool and transaction pool status. It also further determines if it
|
||||||
// should be reported as available by the HaveTransaction function based upon
|
// should be reported as available by the HaveTransaction function based upon
|
||||||
// the two flags and tests that condition as well.
|
// the two flags and tests that condition as well.
|
||||||
func testPoolMembership(tc *testContext, tx *util.Tx, inOrphanPool, inTxPool bool) {
|
func testPoolMembership(tc *testContext, tx *util.Tx, inOrphanPool, inTxPool bool, isDepends bool) {
|
||||||
txID := tx.ID()
|
txID := tx.ID()
|
||||||
gotOrphanPool := tc.harness.txPool.IsOrphanInPool(txID)
|
gotOrphanPool := tc.harness.txPool.IsOrphanInPool(txID)
|
||||||
if inOrphanPool != gotOrphanPool {
|
if inOrphanPool != gotOrphanPool {
|
||||||
@ -374,6 +374,13 @@ func testPoolMembership(tc *testContext, tx *util.Tx, inOrphanPool, inTxPool boo
|
|||||||
file, line, inTxPool, gotTxPool)
|
file, line, inTxPool, gotTxPool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gotIsDepends := tc.harness.txPool.IsInDependPool(txID)
|
||||||
|
if isDepends != gotIsDepends {
|
||||||
|
_, file, line, _ := runtime.Caller(1)
|
||||||
|
tc.t.Fatalf("%s:%d -- IsInDependPool: want %v, got %v",
|
||||||
|
file, line, isDepends, gotIsDepends)
|
||||||
|
}
|
||||||
|
|
||||||
gotHaveTx := tc.harness.txPool.HaveTransaction(txID)
|
gotHaveTx := tc.harness.txPool.HaveTransaction(txID)
|
||||||
wantHaveTx := inOrphanPool || inTxPool
|
wantHaveTx := inOrphanPool || inTxPool
|
||||||
if wantHaveTx != gotHaveTx {
|
if wantHaveTx != gotHaveTx {
|
||||||
@ -389,7 +396,7 @@ func testPoolMembership(tc *testContext, tx *util.Tx, inOrphanPool, inTxPool boo
|
|||||||
if count != len(txIDs) || count != len(txDescs) || count != len(txMiningDescs) {
|
if count != len(txIDs) || count != len(txDescs) || count != len(txMiningDescs) {
|
||||||
tc.t.Error("mempool.TxIDs(), mempool.TxDescs() and mempool.MiningDescs() have different length")
|
tc.t.Error("mempool.TxIDs(), mempool.TxDescs() and mempool.MiningDescs() have different length")
|
||||||
}
|
}
|
||||||
if inTxPool {
|
if inTxPool && !isDepends {
|
||||||
wasFound := false
|
wasFound := false
|
||||||
for _, txI := range txIDs {
|
for _, txI := range txIDs {
|
||||||
if *txID == *txI {
|
if *txID == *txI {
|
||||||
@ -490,7 +497,7 @@ func TestProcessTransaction(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("ProcessTransaction: unexpected error: %v", err)
|
t.Errorf("ProcessTransaction: unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
testPoolMembership(tc, orphanedTx, false, false)
|
testPoolMembership(tc, orphanedTx, false, false, false)
|
||||||
|
|
||||||
harness.txPool.cfg.Policy.MaxOrphanTxs = 5
|
harness.txPool.cfg.Policy.MaxOrphanTxs = 5
|
||||||
_, err = harness.txPool.ProcessTransaction(orphanedTx, true, false, 0)
|
_, err = harness.txPool.ProcessTransaction(orphanedTx, true, false, 0)
|
||||||
@ -850,8 +857,8 @@ func TestDoubleSpends(t *testing.T) {
|
|||||||
t.Fatalf("unable to create transaction: %v", err)
|
t.Fatalf("unable to create transaction: %v", err)
|
||||||
}
|
}
|
||||||
harness.txPool.ProcessTransaction(tx2, true, false, 0)
|
harness.txPool.ProcessTransaction(tx2, true, false, 0)
|
||||||
testPoolMembership(tc, tx1, false, true)
|
testPoolMembership(tc, tx1, false, true, false)
|
||||||
testPoolMembership(tc, tx2, false, true)
|
testPoolMembership(tc, tx2, false, true, false)
|
||||||
|
|
||||||
//Spends the same outpoint as tx2
|
//Spends the same outpoint as tx2
|
||||||
tx3, err := harness.createTx(spendableOuts[0], 2, 1) //We put here different fee to create different transaction hash
|
tx3, err := harness.createTx(spendableOuts[0], 2, 1) //We put here different fee to create different transaction hash
|
||||||
@ -867,15 +874,15 @@ func TestDoubleSpends(t *testing.T) {
|
|||||||
if code, _ := extractRejectCode(err); code != wire.RejectDuplicate {
|
if code, _ := extractRejectCode(err); code != wire.RejectDuplicate {
|
||||||
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectDuplicate, code)
|
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectDuplicate, code)
|
||||||
}
|
}
|
||||||
testPoolMembership(tc, tx3, false, false)
|
testPoolMembership(tc, tx3, false, false, false)
|
||||||
|
|
||||||
//Then we assume tx3 is already in the DAG, so we need to remove
|
//Then we assume tx3 is already in the DAG, so we need to remove
|
||||||
//transactions that spends the same outpoints from the mempool
|
//transactions that spends the same outpoints from the mempool
|
||||||
harness.txPool.RemoveDoubleSpends(tx3)
|
harness.txPool.RemoveDoubleSpends(tx3)
|
||||||
//Ensures that only the transaction that double spends the same
|
//Ensures that only the transaction that double spends the same
|
||||||
//funds as tx3 is removed, and the other one remains unaffected
|
//funds as tx3 is removed, and the other one remains unaffected
|
||||||
testPoolMembership(tc, tx1, false, false)
|
testPoolMembership(tc, tx1, false, false, false)
|
||||||
testPoolMembership(tc, tx2, false, true)
|
testPoolMembership(tc, tx2, false, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
//TestFetchTransaction checks that FetchTransaction
|
//TestFetchTransaction checks that FetchTransaction
|
||||||
@ -896,7 +903,7 @@ func TestFetchTransaction(t *testing.T) {
|
|||||||
t.Fatalf("unable to create signed tx: %v", err)
|
t.Fatalf("unable to create signed tx: %v", err)
|
||||||
}
|
}
|
||||||
harness.txPool.ProcessTransaction(orphanedTx, true, false, 0)
|
harness.txPool.ProcessTransaction(orphanedTx, true, false, 0)
|
||||||
testPoolMembership(tc, orphanedTx, true, false)
|
testPoolMembership(tc, orphanedTx, true, false, false)
|
||||||
fetchedorphanedTx, err := harness.txPool.FetchTransaction(orphanedTx.ID())
|
fetchedorphanedTx, err := harness.txPool.FetchTransaction(orphanedTx.ID())
|
||||||
if fetchedorphanedTx != nil {
|
if fetchedorphanedTx != nil {
|
||||||
t.Fatalf("FetchTransaction: expected fetchedorphanedTx to be nil")
|
t.Fatalf("FetchTransaction: expected fetchedorphanedTx to be nil")
|
||||||
@ -910,7 +917,7 @@ func TestFetchTransaction(t *testing.T) {
|
|||||||
t.Fatalf("unable to create transaction: %v", err)
|
t.Fatalf("unable to create transaction: %v", err)
|
||||||
}
|
}
|
||||||
harness.txPool.ProcessTransaction(tx, true, false, 0)
|
harness.txPool.ProcessTransaction(tx, true, false, 0)
|
||||||
testPoolMembership(tc, tx, false, true)
|
testPoolMembership(tc, tx, false, true, false)
|
||||||
fetchedTx, err := harness.txPool.FetchTransaction(tx.ID())
|
fetchedTx, err := harness.txPool.FetchTransaction(tx.ID())
|
||||||
if !reflect.DeepEqual(fetchedTx, tx) {
|
if !reflect.DeepEqual(fetchedTx, tx) {
|
||||||
t.Fatalf("FetchTransaction: returned a transaction, but not the right one")
|
t.Fatalf("FetchTransaction: returned a transaction, but not the right one")
|
||||||
@ -961,7 +968,7 @@ func TestSimpleOrphanChain(t *testing.T) {
|
|||||||
|
|
||||||
// Ensure the transaction is in the orphan pool, is not in the
|
// Ensure the transaction is in the orphan pool, is not in the
|
||||||
// transaction pool, and is reported as available.
|
// transaction pool, and is reported as available.
|
||||||
testPoolMembership(tc, tx, true, false)
|
testPoolMembership(tc, tx, true, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the transaction which completes the orphan chain and ensure they
|
// Add the transaction which completes the orphan chain and ensure they
|
||||||
@ -982,7 +989,7 @@ func TestSimpleOrphanChain(t *testing.T) {
|
|||||||
for _, txD := range acceptedTxns {
|
for _, txD := range acceptedTxns {
|
||||||
// Ensure the transaction is no longer in the orphan pool, is
|
// Ensure the transaction is no longer in the orphan pool, is
|
||||||
// now in the transaction pool, and is reported as available.
|
// now in the transaction pool, and is reported as available.
|
||||||
testPoolMembership(tc, txD.Tx, false, true)
|
testPoolMembership(tc, txD.Tx, false, true, txD.Tx != chainedTxns[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1036,7 +1043,7 @@ func TestOrphanReject(t *testing.T) {
|
|||||||
|
|
||||||
// Ensure the transaction is not in the orphan pool, not in the
|
// Ensure the transaction is not in the orphan pool, not in the
|
||||||
// transaction pool, and not reported as available
|
// transaction pool, and not reported as available
|
||||||
testPoolMembership(tc, tx, false, false)
|
testPoolMembership(tc, tx, false, false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1067,8 +1074,8 @@ func TestOrphanExpiration(t *testing.T) {
|
|||||||
false, 0)
|
false, 0)
|
||||||
|
|
||||||
//First check that expired orphan transactions are not removed before nextExpireScan
|
//First check that expired orphan transactions are not removed before nextExpireScan
|
||||||
testPoolMembership(tc, tx1, true, false)
|
testPoolMembership(tc, tx1, true, false, false)
|
||||||
testPoolMembership(tc, expiredTx, true, false)
|
testPoolMembership(tc, expiredTx, true, false, false)
|
||||||
|
|
||||||
//Force nextExpireScan to be in the past
|
//Force nextExpireScan to be in the past
|
||||||
harness.txPool.nextExpireScan = time.Unix(0, 0)
|
harness.txPool.nextExpireScan = time.Unix(0, 0)
|
||||||
@ -1081,9 +1088,9 @@ func TestOrphanExpiration(t *testing.T) {
|
|||||||
harness.txPool.ProcessTransaction(tx2, true,
|
harness.txPool.ProcessTransaction(tx2, true,
|
||||||
false, 0)
|
false, 0)
|
||||||
//Check that only expired orphan transactions are removed
|
//Check that only expired orphan transactions are removed
|
||||||
testPoolMembership(tc, tx1, true, false)
|
testPoolMembership(tc, tx1, true, false, false)
|
||||||
testPoolMembership(tc, tx2, true, false)
|
testPoolMembership(tc, tx2, true, false, false)
|
||||||
testPoolMembership(tc, expiredTx, false, false)
|
testPoolMembership(tc, expiredTx, false, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
//TestMaxOrphanTxSize ensures that a transaction that is
|
//TestMaxOrphanTxSize ensures that a transaction that is
|
||||||
@ -1107,12 +1114,12 @@ func TestMaxOrphanTxSize(t *testing.T) {
|
|||||||
harness.txPool.ProcessTransaction(tx, true,
|
harness.txPool.ProcessTransaction(tx, true,
|
||||||
false, 0)
|
false, 0)
|
||||||
|
|
||||||
testPoolMembership(tc, tx, false, false)
|
testPoolMembership(tc, tx, false, false, false)
|
||||||
|
|
||||||
harness.txPool.cfg.Policy.MaxOrphanTxSize = math.MaxInt32
|
harness.txPool.cfg.Policy.MaxOrphanTxSize = math.MaxInt32
|
||||||
harness.txPool.ProcessTransaction(tx, true,
|
harness.txPool.ProcessTransaction(tx, true,
|
||||||
false, 0)
|
false, 0)
|
||||||
testPoolMembership(tc, tx, true, false)
|
testPoolMembership(tc, tx, true, false, false)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1128,26 +1135,26 @@ func TestRemoveTransaction(t *testing.T) {
|
|||||||
t.Fatalf("unable to create transaction chain: %v", err)
|
t.Fatalf("unable to create transaction chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tx := range chainedTxns {
|
for i, tx := range chainedTxns {
|
||||||
_, err := harness.txPool.ProcessTransaction(tx, true,
|
_, err := harness.txPool.ProcessTransaction(tx, true,
|
||||||
false, 0)
|
false, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ProcessTransaction: %v", err)
|
t.Fatalf("ProcessTransaction: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
testPoolMembership(tc, tx, false, true)
|
testPoolMembership(tc, tx, false, true, i != 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Checks that when removeRedeemers is false, the specified transaction is the only transaction that gets removed
|
//Checks that when removeRedeemers is false, the specified transaction is the only transaction that gets removed
|
||||||
harness.txPool.RemoveTransaction(chainedTxns[3], false, true)
|
harness.txPool.RemoveTransaction(chainedTxns[3], false, true)
|
||||||
testPoolMembership(tc, chainedTxns[3], false, false)
|
testPoolMembership(tc, chainedTxns[3], false, false, false)
|
||||||
testPoolMembership(tc, chainedTxns[4], false, true)
|
testPoolMembership(tc, chainedTxns[4], false, true, false)
|
||||||
|
|
||||||
//Checks that when removeRedeemers is true, all of the transaction that are dependent on it get removed
|
//Checks that when removeRedeemers is true, all of the transaction that are dependent on it get removed
|
||||||
harness.txPool.RemoveTransaction(chainedTxns[1], true, true)
|
harness.txPool.RemoveTransaction(chainedTxns[1], true, true)
|
||||||
testPoolMembership(tc, chainedTxns[0], false, true)
|
testPoolMembership(tc, chainedTxns[0], false, true, false)
|
||||||
testPoolMembership(tc, chainedTxns[1], false, false)
|
testPoolMembership(tc, chainedTxns[1], false, false, false)
|
||||||
testPoolMembership(tc, chainedTxns[2], false, false)
|
testPoolMembership(tc, chainedTxns[2], false, false, false)
|
||||||
|
|
||||||
fakeWithDiffErr := "error from WithDiff"
|
fakeWithDiffErr := "error from WithDiff"
|
||||||
guard := monkey.Patch((*blockdag.DiffUTXOSet).WithDiff, func(_ *blockdag.DiffUTXOSet, _ *blockdag.UTXODiff) (blockdag.UTXOSet, error) {
|
guard := monkey.Patch((*blockdag.DiffUTXOSet).WithDiff, func(_ *blockdag.DiffUTXOSet, _ *blockdag.UTXODiff) (blockdag.UTXOSet, error) {
|
||||||
@ -1198,7 +1205,7 @@ func TestOrphanEviction(t *testing.T) {
|
|||||||
|
|
||||||
// Ensure the transaction is in the orphan pool, is not in the
|
// Ensure the transaction is in the orphan pool, is not in the
|
||||||
// transaction pool, and is reported as available.
|
// transaction pool, and is reported as available.
|
||||||
testPoolMembership(tc, tx, true, false)
|
testPoolMembership(tc, tx, true, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Figure out which transactions were evicted and make sure the number
|
// Figure out which transactions were evicted and make sure the number
|
||||||
@ -1218,7 +1225,7 @@ func TestOrphanEviction(t *testing.T) {
|
|||||||
// Ensure none of the evicted transactions ended up in the transaction
|
// Ensure none of the evicted transactions ended up in the transaction
|
||||||
// pool.
|
// pool.
|
||||||
for _, tx := range evictedTxns {
|
for _, tx := range evictedTxns {
|
||||||
testPoolMembership(tc, tx, false, false)
|
testPoolMembership(tc, tx, false, false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1271,10 +1278,10 @@ func TestRemoveOrphansByTag(t *testing.T) {
|
|||||||
false, 2)
|
false, 2)
|
||||||
|
|
||||||
harness.txPool.RemoveOrphansByTag(1)
|
harness.txPool.RemoveOrphansByTag(1)
|
||||||
testPoolMembership(tc, orphanedTx1, false, false)
|
testPoolMembership(tc, orphanedTx1, false, false, false)
|
||||||
testPoolMembership(tc, orphanedTx2, false, false)
|
testPoolMembership(tc, orphanedTx2, false, false, false)
|
||||||
testPoolMembership(tc, orphanedTx3, false, false)
|
testPoolMembership(tc, orphanedTx3, false, false, false)
|
||||||
testPoolMembership(tc, orphanedTx4, true, false)
|
testPoolMembership(tc, orphanedTx4, true, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestBasicOrphanRemoval ensure that orphan removal works as expected when an
|
// TestBasicOrphanRemoval ensure that orphan removal works as expected when an
|
||||||
@ -1316,7 +1323,7 @@ func TestBasicOrphanRemoval(t *testing.T) {
|
|||||||
|
|
||||||
// Ensure the transaction is in the orphan pool, not in the
|
// Ensure the transaction is in the orphan pool, not in the
|
||||||
// transaction pool, and reported as available.
|
// transaction pool, and reported as available.
|
||||||
testPoolMembership(tc, tx, true, false)
|
testPoolMembership(tc, tx, true, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to remove an orphan that has no redeemers and is not present,
|
// Attempt to remove an orphan that has no redeemers and is not present,
|
||||||
@ -1330,25 +1337,25 @@ func TestBasicOrphanRemoval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
harness.txPool.RemoveOrphan(nonChainedOrphanTx)
|
harness.txPool.RemoveOrphan(nonChainedOrphanTx)
|
||||||
testPoolMembership(tc, nonChainedOrphanTx, false, false)
|
testPoolMembership(tc, nonChainedOrphanTx, false, false, false)
|
||||||
for _, tx := range chainedTxns[1 : maxOrphans+1] {
|
for _, tx := range chainedTxns[1 : maxOrphans+1] {
|
||||||
testPoolMembership(tc, tx, true, false)
|
testPoolMembership(tc, tx, true, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to remove an orphan that has a existing redeemer but itself
|
// Attempt to remove an orphan that has a existing redeemer but itself
|
||||||
// is not present and ensure the state of all other orphans (including
|
// is not present and ensure the state of all other orphans (including
|
||||||
// the one that redeems it) are unaffected.
|
// the one that redeems it) are unaffected.
|
||||||
harness.txPool.RemoveOrphan(chainedTxns[0])
|
harness.txPool.RemoveOrphan(chainedTxns[0])
|
||||||
testPoolMembership(tc, chainedTxns[0], false, false)
|
testPoolMembership(tc, chainedTxns[0], false, false, false)
|
||||||
for _, tx := range chainedTxns[1 : maxOrphans+1] {
|
for _, tx := range chainedTxns[1 : maxOrphans+1] {
|
||||||
testPoolMembership(tc, tx, true, false)
|
testPoolMembership(tc, tx, true, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove each orphan one-by-one and ensure they are removed as
|
// Remove each orphan one-by-one and ensure they are removed as
|
||||||
// expected.
|
// expected.
|
||||||
for _, tx := range chainedTxns[1 : maxOrphans+1] {
|
for _, tx := range chainedTxns[1 : maxOrphans+1] {
|
||||||
harness.txPool.RemoveOrphan(tx)
|
harness.txPool.RemoveOrphan(tx)
|
||||||
testPoolMembership(tc, tx, false, false)
|
testPoolMembership(tc, tx, false, false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1390,7 +1397,7 @@ func TestOrphanChainRemoval(t *testing.T) {
|
|||||||
|
|
||||||
// Ensure the transaction is in the orphan pool, not in the
|
// Ensure the transaction is in the orphan pool, not in the
|
||||||
// transaction pool, and reported as available.
|
// transaction pool, and reported as available.
|
||||||
testPoolMembership(tc, tx, true, false)
|
testPoolMembership(tc, tx, true, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the first orphan that starts the orphan chain without the
|
// Remove the first orphan that starts the orphan chain without the
|
||||||
@ -1399,9 +1406,9 @@ func TestOrphanChainRemoval(t *testing.T) {
|
|||||||
harness.txPool.mtx.Lock()
|
harness.txPool.mtx.Lock()
|
||||||
harness.txPool.removeOrphan(chainedTxns[1], false)
|
harness.txPool.removeOrphan(chainedTxns[1], false)
|
||||||
harness.txPool.mtx.Unlock()
|
harness.txPool.mtx.Unlock()
|
||||||
testPoolMembership(tc, chainedTxns[1], false, false)
|
testPoolMembership(tc, chainedTxns[1], false, false, false)
|
||||||
for _, tx := range chainedTxns[2 : maxOrphans+1] {
|
for _, tx := range chainedTxns[2 : maxOrphans+1] {
|
||||||
testPoolMembership(tc, tx, true, false)
|
testPoolMembership(tc, tx, true, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the first remaining orphan that starts the orphan chain with
|
// Remove the first remaining orphan that starts the orphan chain with
|
||||||
@ -1410,7 +1417,7 @@ func TestOrphanChainRemoval(t *testing.T) {
|
|||||||
harness.txPool.removeOrphan(chainedTxns[2], true)
|
harness.txPool.removeOrphan(chainedTxns[2], true)
|
||||||
harness.txPool.mtx.Unlock()
|
harness.txPool.mtx.Unlock()
|
||||||
for _, tx := range chainedTxns[2 : maxOrphans+1] {
|
for _, tx := range chainedTxns[2 : maxOrphans+1] {
|
||||||
testPoolMembership(tc, tx, false, false)
|
testPoolMembership(tc, tx, false, false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1446,7 +1453,7 @@ func TestMultiInputOrphanDoubleSpend(t *testing.T) {
|
|||||||
t.Fatalf("ProcessTransaction: reported %d accepted transactions "+
|
t.Fatalf("ProcessTransaction: reported %d accepted transactions "+
|
||||||
"from what should be an orphan", len(acceptedTxns))
|
"from what should be an orphan", len(acceptedTxns))
|
||||||
}
|
}
|
||||||
testPoolMembership(tc, tx, true, false)
|
testPoolMembership(tc, tx, true, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure a transaction that contains a double spend of the same output
|
// Ensure a transaction that contains a double spend of the same output
|
||||||
@ -1472,7 +1479,7 @@ func TestMultiInputOrphanDoubleSpend(t *testing.T) {
|
|||||||
t.Fatalf("ProcessTransaction: reported %d accepted transactions "+
|
t.Fatalf("ProcessTransaction: reported %d accepted transactions "+
|
||||||
"from what should be an orphan", len(acceptedTxns))
|
"from what should be an orphan", len(acceptedTxns))
|
||||||
}
|
}
|
||||||
testPoolMembership(tc, doubleSpendTx, true, false)
|
testPoolMembership(tc, doubleSpendTx, true, false, false)
|
||||||
|
|
||||||
// Add the transaction which completes the orphan chain and ensure the
|
// Add the transaction which completes the orphan chain and ensure the
|
||||||
// chain gets accepted. Notice the accept orphans flag is also false
|
// chain gets accepted. Notice the accept orphans flag is also false
|
||||||
@ -1494,12 +1501,12 @@ func TestMultiInputOrphanDoubleSpend(t *testing.T) {
|
|||||||
for _, txD := range acceptedTxns {
|
for _, txD := range acceptedTxns {
|
||||||
// Ensure the transaction is no longer in the orphan pool, is
|
// Ensure the transaction is no longer in the orphan pool, is
|
||||||
// in the transaction pool, and is reported as available.
|
// in the transaction pool, and is reported as available.
|
||||||
testPoolMembership(tc, txD.Tx, false, true)
|
testPoolMembership(tc, txD.Tx, false, true, txD.Tx != chainedTxns[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the double spending orphan is no longer in the orphan pool and
|
// Ensure the double spending orphan is no longer in the orphan pool and
|
||||||
// was not moved to the transaction pool.
|
// was not moved to the transaction pool.
|
||||||
testPoolMembership(tc, doubleSpendTx, false, false)
|
testPoolMembership(tc, doubleSpendTx, false, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCheckSpend tests that CheckSpend returns the expected spends found in
|
// TestCheckSpend tests that CheckSpend returns the expected spends found in
|
||||||
@ -1590,7 +1597,7 @@ func TestCount(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("ProcessTransaction: unexpected error: %v", err)
|
t.Errorf("ProcessTransaction: unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if harness.txPool.Count() != i+1 {
|
if harness.txPool.Count()+harness.txPool.DepCount() != i+1 {
|
||||||
t.Errorf("TestCount: txPool expected to have %v transactions but got %v", i+1, harness.txPool.Count())
|
t.Errorf("TestCount: txPool expected to have %v transactions but got %v", i+1, harness.txPool.Count())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1599,7 +1606,7 @@ func TestCount(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("harness.CreateTxChain: unexpected error: %v", err)
|
t.Fatalf("harness.CreateTxChain: unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if harness.txPool.Count() != 2 {
|
if harness.txPool.Count()+harness.txPool.DepCount() != 2 {
|
||||||
t.Errorf("TestCount: txPool expected to have 2 transactions but got %v", harness.txPool.Count())
|
t.Errorf("TestCount: txPool expected to have 2 transactions but got %v", harness.txPool.Count())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1708,7 +1715,7 @@ func TestHandleNewBlock(t *testing.T) {
|
|||||||
t.Fatalf("ProcessTransaction: unexpected error: %v", err)
|
t.Fatalf("ProcessTransaction: unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
// ensure that transaction added to orphan pool
|
// ensure that transaction added to orphan pool
|
||||||
testPoolMembership(tc, orphanTx, true, false)
|
testPoolMembership(tc, orphanTx, true, false, false)
|
||||||
|
|
||||||
// Add one more transaction to block
|
// Add one more transaction to block
|
||||||
blockTx2, err := harness.CreateSignedTx(spendableOuts[1:], 1)
|
blockTx2, err := harness.CreateSignedTx(spendableOuts[1:], 1)
|
||||||
@ -1768,7 +1775,7 @@ func TestHandleNewBlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ensure that orphan transaction moved to main pool
|
// ensure that orphan transaction moved to main pool
|
||||||
testPoolMembership(tc, orphanTx, false, true)
|
testPoolMembership(tc, orphanTx, false, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dummyBlock defines a block on the block DAG. It is used to test block operations.
|
// dummyBlock defines a block on the block DAG. It is used to test block operations.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user