[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:
Evgeny Khirin 2019-01-23 16:38:46 +02:00 committed by stasatdaglabs
parent 9507ed0a97
commit b7850b382d
2 changed files with 170 additions and 62 deletions

View File

@ -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),

View File

@ -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.