From f1ff94fc5f838e6f9e9505bd082e90be1db20a45 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 23 Mar 2018 15:11:20 +0100 Subject: [PATCH 1/3] mempool: add CheckSpend method This commit adds a new method CheckSpend to the mempool, which takes an outpoint and returns any transaction in the mempool that spends this outpoint. --- mempool/mempool.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mempool/mempool.go b/mempool/mempool.go index 09ce5e17f2..abf7c7a221 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -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 From 0aeb17f21f69f016e79cd28fcdf9468a0bc82d62 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 23 Mar 2018 15:40:06 +0100 Subject: [PATCH 2/3] mempool_test: add TestCheckSpend --- mempool/mempool_test.go | 69 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index 1d39d0b173..421f6f0b2f 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -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) + } +} From b0bc7fcb951bed421d53ea94c58f5ccce93a2d3d Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 23 Mar 2018 15:44:35 +0100 Subject: [PATCH 3/3] rpcwebsocket: notify spends already in the mempool This commit adds a logic to the addSpentRequests that inspects the mempool for any spends of the outputs. Before this commit, a spend would only be checked when a transaction was first accepted into the mempool. --- rpcwebsocket.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/rpcwebsocket.go b/rpcwebsocket.go index bb316b8a64..a1f0504044 100644 --- a/rpcwebsocket.go +++ b/rpcwebsocket.go @@ -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 { @@ -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