Skip to content

Commit

Permalink
Merge pull request #20 from halseth/mempool-spends
Browse files Browse the repository at this point in the history
Mempool spends
  • Loading branch information
Roasbeef committed Apr 6, 2018
2 parents 6092648 + b0bc7fc commit 3b7cd03
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 1 deletion.
11 changes: 11 additions & 0 deletions mempool/mempool.go
Expand Up @@ -571,6 +571,17 @@ func (mp *TxPool) checkPoolDoubleSpend(tx *btcutil.Tx) error {
return nil
}

// CheckSpends checks whether the passed outpoint is already spent by a
// transaction in the mempool. If that's the case the spending transaction will
// be returned, if not nil will be returned.
func (mp *TxPool) CheckSpend(op wire.OutPoint) *btcutil.Tx {
mp.mtx.RLock()
txR := mp.outpoints[op]
mp.mtx.RUnlock()

return txR
}

// fetchInputUtxos loads utxo details about the input transactions referenced by
// the passed transaction. First, it loads the details form the viewpoint of
// the main chain, then it adjusts them based upon the contents of the
Expand Down
69 changes: 69 additions & 0 deletions mempool/mempool_test.go
Expand Up @@ -794,3 +794,72 @@ func TestMultiInputOrphanDoubleSpend(t *testing.T) {
// was not moved to the transaction pool.
testPoolMembership(tc, doubleSpendTx, false, false)
}

// TestCheckSpend tests that CheckSpend returns the expected spends found in
// the mempool.
func TestCheckSpend(t *testing.T) {
t.Parallel()

harness, outputs, err := newPoolHarness(&chaincfg.MainNetParams)
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}

// The mempool is empty, so none of the spendable outputs should have a
// spend there.
for _, op := range outputs {
spend := harness.txPool.CheckSpend(op.outPoint)
if spend != nil {
t.Fatalf("Unexpeced spend found in pool: %v", spend)
}
}

// Create a chain of transactions rooted with the first spendable
// output provided by the harness.
const txChainLength = 5
chainedTxns, err := harness.CreateTxChain(outputs[0], txChainLength)
if err != nil {
t.Fatalf("unable to create transaction chain: %v", err)
}
for _, tx := range chainedTxns {
_, err := harness.txPool.ProcessTransaction(tx, true,
false, 0)
if err != nil {
t.Fatalf("ProcessTransaction: failed to accept "+
"tx: %v", err)
}
}

// The first tx in the chain should be the spend of the spendable
// output.
op := outputs[0].outPoint
spend := harness.txPool.CheckSpend(op)
if spend != chainedTxns[0] {
t.Fatalf("expected %v to be spent by %v, instead "+
"got %v", op, chainedTxns[0], spend)
}

// Now all but the last tx should be spent by the next.
for i := 0; i < len(chainedTxns)-1; i++ {
op = wire.OutPoint{
Hash: *chainedTxns[i].Hash(),
Index: 0,
}
expSpend := chainedTxns[i+1]
spend = harness.txPool.CheckSpend(op)
if spend != expSpend {
t.Fatalf("expected %v to be spent by %v, instead "+
"got %v", op, expSpend, spend)
}
}

// The last tx should have no spend.
op = wire.OutPoint{
Hash: *chainedTxns[txChainLength-1].Hash(),
Index: 0,
}
spend = harness.txPool.CheckSpend(op)
if spend != nil {
t.Fatalf("Unexpeced spend found in pool: %v", spend)
}
}
18 changes: 17 additions & 1 deletion rpcwebsocket.go
Expand Up @@ -883,7 +883,7 @@ func (m *wsNotificationManager) RegisterSpentRequests(wsc *wsClient, ops []*wire
// addSpentRequests modifies a map of watched outpoints to sets of websocket
// clients to add a new request watch all of the outpoints in ops and create
// and send a notification when spent to the websocket client wsc.
func (*wsNotificationManager) addSpentRequests(opMap map[wire.OutPoint]map[chan struct{}]*wsClient,
func (m *wsNotificationManager) addSpentRequests(opMap map[wire.OutPoint]map[chan struct{}]*wsClient,
wsc *wsClient, ops []*wire.OutPoint) {

for _, op := range ops {
Expand All @@ -900,6 +900,22 @@ func (*wsNotificationManager) addSpentRequests(opMap map[wire.OutPoint]map[chan
}
cmap[wsc.quit] = wsc
}

// Check if any transactions spending these outputs already exists in
// the mempool, if so send the notification immediately.
spends := make(map[chainhash.Hash]*btcutil.Tx)
for _, op := range ops {
spend := m.server.cfg.TxMemPool.CheckSpend(*op)
if spend != nil {
rpcsLog.Debugf("Found existing mempool spend for "+
"outpoint<%v>: %v", op, spend.Hash())
spends[*spend.Hash()] = spend
}
}

for _, spend := range spends {
m.notifyForTx(opMap, nil, spend, nil)
}
}

// UnregisterSpentRequest removes a request from the passed websocket client
Expand Down

0 comments on commit 3b7cd03

Please sign in to comment.