From f0ec42416154e2fd1442db842f8ee2e20cde3715 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 29 Oct 2015 15:27:35 -0400 Subject: [PATCH] Added bindings to be able to listen to tx leaving mempool. --- etc/bitcoin.patch | 17 +++++++- integration/regtest.js | 24 +++++++++++ lib/services/bitcoind.js | 9 ++++- src/libbitcoind.cc | 73 ++++++++++++++++++++++++++++++++++ test/services/bitcoind.unit.js | 6 ++- 5 files changed, 124 insertions(+), 5 deletions(-) diff --git a/etc/bitcoin.patch b/etc/bitcoin.patch index e262753e..8de1497d 100644 --- a/etc/bitcoin.patch +++ b/etc/bitcoin.patch @@ -367,14 +367,27 @@ index f94771a..72ee00e 100644 diff --git a/src/net.h b/src/net.h -index 17502b9..e181d68 100644 +index 17502b9..c9ae1b2 100644 --- a/src/net.h +++ b/src/net.h -@@ -99,6 +99,7 @@ struct CNodeSignals +@@ -99,6 +99,8 @@ struct CNodeSignals { boost::signals2::signal GetHeight; boost::signals2::signal ProcessMessages; + boost::signals2::signal TxToMemPool; ++ boost::signals2::signal TxLeaveMemPool; boost::signals2::signal SendMessages; boost::signals2::signal InitializeNode; boost::signals2::signal FinalizeNode; +diff --git a/src/txmempool.cpp b/src/txmempool.cpp +index c3d1b60..03e265d 100644 +--- a/src/txmempool.cpp ++++ b/src/txmempool.cpp +@@ -133,6 +133,7 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list& rem + if (!mapTx.count(hash)) + continue; + const CTransaction& tx = mapTx[hash].GetTx(); ++ GetNodeSignals().TxLeaveMemPool(tx); + if (fRecursive) { + for (unsigned int i = 0; i < tx.vout.size(); i++) { + std::map::iterator it = mapNextTx.find(COutPoint(hash, i)); diff --git a/integration/regtest.js b/integration/regtest.js index 9b80ac10..17f0b8e6 100644 --- a/integration/regtest.js +++ b/integration/regtest.js @@ -360,6 +360,30 @@ describe('Daemon Binding Functionality', function() { }); }); + describe('transactions leaving the mempool', function() { + it('receive event when transaction leaves', function(done) { + + // add transaction to build a new block + var tx = bitcore.Transaction(); + tx.from(utxos[4]); + tx.change(privateKey.toAddress()); + tx.to(destKey.toAddress(), utxos[4].amount * 1e8 - 1000); + tx.sign(bitcore.PrivateKey.fromWIF(utxos[4].privateKeyWIF)); + bitcoind.sendTransaction(tx.serialize()); + + bitcoind.once('txleave', function(txInfo) { + txInfo.hash.should.equal(tx.hash); + done(); + }); + + client.generate(1, function(err, response) { + if (err) { + throw err; + } + }); + }); + }); + describe('mempool functionality', function() { var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1'; diff --git a/lib/services/bitcoind.js b/lib/services/bitcoind.js index ece34092..e7a67467 100644 --- a/lib/services/bitcoind.js +++ b/lib/services/bitcoind.js @@ -110,12 +110,19 @@ Bitcoin.prototype._registerEventHandlers = function() { // Set the height and emit a new tip bindings.onTipUpdate(self._onTipUpdate.bind(this)); - // Register callback function to handle incoming transactions + // Register callback function to handle transactions entering the mempool bindings.startTxMon(function(txs) { for(var i = 0; i < txs.length; i++) { self.emit('tx', txs[i]); } }); + + // Register callback function to handle transactions leaving the mempool + bindings.startTxMonLeave(function(txs) { + for(var i = 0; i < txs.length; i++) { + self.emit('txleave', txs[i]); + } + }); }; Bitcoin.prototype._onReady = function(result, callback) { diff --git a/src/libbitcoind.cc b/src/libbitcoind.cc index c3e27274..b7b87b04 100644 --- a/src/libbitcoind.cc +++ b/src/libbitcoind.cc @@ -39,6 +39,9 @@ extern int64_t nTimeBestReceived; static void tx_notifier(uv_async_t *handle); +static void +txleave_notifier(uv_async_t *handle); + static void async_tip_update(uv_work_t *req); @@ -90,6 +93,9 @@ async_get_tx_and_info_after(uv_work_t *req); static bool queueTx(const CTransaction&); +static bool +queueTxLeave(const CTransaction&); + extern "C" void init(Handle); @@ -98,9 +104,13 @@ init(Handle); * Used only by bitcoind functions. */ static std::vector txQueue; +static std::vector txQueueLeave; static uv_async_t txmon_async; +static uv_async_t txmonleave_async; static Eternal txmon_callback; +static Eternal txmonleave_callback; static bool txmon_callback_available; +static bool txmonleave_callback_available; static volatile bool shutdown_complete = false; static char *g_data_dir = NULL; @@ -259,6 +269,21 @@ NAN_METHOD(StartTxMon) { info.GetReturnValue().Set(Null()); }; +NAN_METHOD(StartTxMonLeave) { + Isolate* isolate = info.GetIsolate(); + Local callback = Local::Cast(info[0]); + Eternal cb(isolate, callback); + txmonleave_callback = cb; + txmonleave_callback_available = true; + + CNodeSignals& nodeSignals = GetNodeSignals(); + nodeSignals.TxLeaveMemPool.connect(&queueTxLeave); + + uv_async_init(uv_default_loop(), &txmonleave_async, txleave_notifier); + + info.GetReturnValue().Set(Null()); +}; + static void tx_notifier(uv_async_t *handle) { Isolate* isolate = Isolate::GetCurrent(); @@ -307,6 +332,53 @@ queueTx(const CTransaction& tx) { return true; } +static void +txleave_notifier(uv_async_t *handle) { + Isolate* isolate = Isolate::GetCurrent(); + HandleScope scope(isolate); + + Local results = Array::New(isolate); + int arrayIndex = 0; + + LOCK(cs_main); + BOOST_FOREACH(const CTransaction& tx, txQueueLeave) { + + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << tx; + std::string stx = ssTx.str(); + Nan::MaybeLocal txBuffer = Nan::CopyBuffer((char *)stx.c_str(), stx.size()); + + uint256 hash = tx.GetHash(); + + Local obj = New(); + + Nan::Set(obj, New("buffer").ToLocalChecked(), txBuffer.ToLocalChecked()); + Nan::Set(obj, New("hash").ToLocalChecked(), New(hash.GetHex()).ToLocalChecked()); + + results->Set(arrayIndex, obj); + arrayIndex++; + } + + const unsigned argc = 1; + Local argv[argc] = { + Local::New(isolate, results) + }; + + Local cb = txmonleave_callback.Get(isolate); + + cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); + + txQueueLeave.clear(); + +} +static bool +queueTxLeave(const CTransaction& tx) { + LOCK(cs_main); + txQueueLeave.push_back(tx); + uv_async_send(&txmonleave_async); + return true; +} + /** * Functions */ @@ -1527,6 +1599,7 @@ NAN_MODULE_INIT(init) { Nan::Set(target, New("sendTransaction").ToLocalChecked(), GetFunction(New(SendTransaction)).ToLocalChecked()); Nan::Set(target, New("estimateFee").ToLocalChecked(), GetFunction(New(EstimateFee)).ToLocalChecked()); Nan::Set(target, New("startTxMon").ToLocalChecked(), GetFunction(New(StartTxMon)).ToLocalChecked()); + Nan::Set(target, New("startTxMonLeave").ToLocalChecked(), GetFunction(New(StartTxMonLeave)).ToLocalChecked()); Nan::Set(target, New("syncPercentage").ToLocalChecked(), GetFunction(New(SyncPercentage)).ToLocalChecked()); Nan::Set(target, New("isSynced").ToLocalChecked(), GetFunction(New(IsSynced)).ToLocalChecked()); Nan::Set(target, New("getBestBlockHash").ToLocalChecked(), GetFunction(New(GetBestBlockHash)).ToLocalChecked()); diff --git a/test/services/bitcoind.unit.js b/test/services/bitcoind.unit.js index 03853647..47156b50 100644 --- a/test/services/bitcoind.unit.js +++ b/test/services/bitcoind.unit.js @@ -144,7 +144,8 @@ describe('Bitcoin Service', function() { name.should.equal('bitcoind.node'); return { onTipUpdate: sinon.stub(), - startTxMon: sinon.stub().callsArgWith(0, [transaction]) + startTxMon: sinon.stub().callsArgWith(0, [transaction]), + startTxMonLeave: sinon.stub().callsArgWith(0, [transaction]) }; } }); @@ -175,7 +176,8 @@ describe('Bitcoin Service', function() { callback(height++); }); }, - startTxMon: sinon.stub() + startTxMon: sinon.stub(), + startTxMonLeave: sinon.stub() }; } });