diff --git a/integration/regtest.js b/integration/regtest.js index d96f2228..116e2789 100644 --- a/integration/regtest.js +++ b/integration/regtest.js @@ -304,4 +304,16 @@ describe('Daemon Binding Functionality', function() { }); + describe('get transaction with block info', function() { + it('should include tx buffer, height and timestamp', function(done) { + bitcoind.getTransactionWithBlockInfo(utxo.txid, true, function(err, data) { + should.not.exist(err); + data.height.should.equal(151); + should.exist(data.timestamp); + should.exist(data.buffer); + done(); + }); + }); + }); + }); diff --git a/lib/daemon.js b/lib/daemon.js index 0133bfb2..3f9f985d 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -362,6 +362,10 @@ Daemon.prototype.getTransactionWithBlock = function(txid, blockhash, callback) { }); }; +Daemon.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) { + return bitcoindjs.getTransactionWithBlockInfo(txid, queryMempool, callback); +}; + Daemon.prototype.getMempoolOutputs = function(address) { return bitcoindjs.getMempoolOutputs(address); }; diff --git a/lib/db.js b/lib/db.js index 621520fe..dd0d89b8 100644 --- a/lib/db.js +++ b/lib/db.js @@ -81,18 +81,17 @@ DB.prototype.getTransaction = function(txid, queryMempool, callback) { }; DB.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) { - this.getTransaction(txid, queryMempool, callback); - /*this.bitcoind.getTransactionWithBlockInfo(txid, queryMempool, function(err, obj) { + this.bitcoind.getTransactionWithBlockInfo(txid, queryMempool, function(err, obj) { if(err) { return callback(err); } var tx = Transaction().fromBuffer(obj.buffer); - tx.__blockHeight = obj.blockHeight; + tx.__height = obj.height; tx.__timestamp = obj.timestamp; callback(null, tx); - });*/ + }); } DB.prototype.validateBlockData = function(block, callback) { diff --git a/src/bitcoindjs.cc b/src/bitcoindjs.cc index 172286f9..fb153016 100644 --- a/src/bitcoindjs.cc +++ b/src/bitcoindjs.cc @@ -74,6 +74,12 @@ async_get_tx(uv_work_t *req); static void async_get_tx_after(uv_work_t *req); +static void +async_get_tx_and_info(uv_work_t *req); + +static void +async_get_tx_and_info_after(uv_work_t *req); + static bool process_messages(CNode* pfrom); @@ -156,6 +162,8 @@ struct async_tx_data { std::string err_msg; std::string txid; std::string blockhash; + uint32_t nTime; + int64_t height; bool queryMempool; CTransaction ctx; Eternal callback; @@ -1018,7 +1026,7 @@ async_get_block_after(uv_work_t *req) { /** * GetTransaction() - * bitcoind.getTransaction(txid, callback) + * bitcoind.getTransaction(txid, queryMempool, callback) * Read any transaction from disk asynchronously. */ @@ -1030,7 +1038,7 @@ NAN_METHOD(GetTransaction) { || !args[1]->IsBoolean() || !args[2]->IsFunction()) { return NanThrowError( - "Usage: bitcoindjs.getTransaction(txid, callback)"); + "Usage: daemon.getTransaction(txid, queryMempool, callback)"); } String::Utf8Value txid_(args[0]->ToString()); @@ -1145,6 +1153,154 @@ async_get_tx_after(uv_work_t *req) { delete req; } +/** + * GetTransactionWithBlockInfo() + * bitcoind.getTransactionWithBlockInfo(txid, queryMempool, callback) + * Read any transaction from disk asynchronously with block timestamp and height. + */ + +NAN_METHOD(GetTransactionWithBlockInfo) { + Isolate* isolate = Isolate::GetCurrent(); + HandleScope scope(isolate); + if (args.Length() < 3 + || !args[0]->IsString() + || !args[1]->IsBoolean() + || !args[2]->IsFunction()) { + return NanThrowError( + "Usage: bitcoindjs.getTransactionWithBlockInfo(txid, queryMempool, callback)"); + } + + String::Utf8Value txid_(args[0]->ToString()); + bool queryMempool = args[1]->BooleanValue(); + Local callback = Local::Cast(args[2]); + + async_tx_data *data = new async_tx_data(); + + data->err_msg = std::string(""); + data->txid = std::string(""); + + std::string txid = std::string(*txid_); + + data->txid = txid; + data->queryMempool = queryMempool; + Eternal eternal(isolate, callback); + data->callback = eternal; + + uv_work_t *req = new uv_work_t(); + req->data = data; + + int status = uv_queue_work(uv_default_loop(), + req, async_get_tx_and_info, + (uv_after_work_cb)async_get_tx_and_info_after); + + assert(status == 0); + + NanReturnValue(Undefined(isolate)); +} + +static void +async_get_tx_and_info(uv_work_t *req) { + async_tx_data* data = static_cast(req->data); + + uint256 hash = uint256S(data->txid); + uint256 blockHash; + CTransaction ctx; + + if (data->queryMempool) { + LOCK(mempool.cs); + map::const_iterator i = mempool.mapTx.find(hash); + if (i != mempool.mapTx.end()) { + data->ctx = i->second.GetTx(); + data->nTime = i->second.GetTime(); + data->height = -1; + return; + } + } + + CDiskTxPos postx; + if (pblocktree->ReadTxIndex(hash, postx)) { + + CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION); + + if (file.IsNull()) { + data->err_msg = std::string("%s: OpenBlockFile failed", __func__); + return; + } + + CBlockHeader blockHeader; + + try { + // Read header first to get block timestamp and hash + file >> blockHeader; + blockHash = blockHeader.GetHash(); + data->nTime = blockHeader.nTime; + fseek(file.Get(), postx.nTxOffset, SEEK_CUR); + file >> ctx; + data->ctx = ctx; + } catch (const std::exception& e) { + data->err_msg = std::string("Deserialize or I/O error - %s", __func__); + return; + } + + // get block height + CBlockIndex* blockIndex; + + if (mapBlockIndex.count(blockHash) == 0) { + data->height = -1; + } else { + blockIndex = mapBlockIndex[blockHash]; + data->height = blockIndex->nHeight; + } + + } + +} + +static void +async_get_tx_and_info_after(uv_work_t *req) { + Isolate* isolate = Isolate::GetCurrent(); + HandleScope scope(isolate); + async_tx_data* data = static_cast(req->data); + + CTransaction ctx = data->ctx; + Local cb = data->callback.Get(isolate); + Local obj = NanNew(); + + if (data->err_msg != "") { + Local err = Exception::Error(NanNew(data->err_msg)); + const unsigned argc = 1; + Local argv[argc] = { err }; + TryCatch try_catch; + cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); + if (try_catch.HasCaught()) { + node::FatalException(try_catch); + } + } else { + + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << ctx; + std::string stx = ssTx.str(); + Local rawNodeBuffer = node::Buffer::New(isolate, stx.c_str(), stx.size()); + + obj->Set(NanNew("height"), NanNew(data->height)); + obj->Set(NanNew("timestamp"), NanNew(data->nTime)); + obj->Set(NanNew("buffer"), rawNodeBuffer); + + const unsigned argc = 2; + Local argv[argc] = { + Local::New(isolate, NanNull()), + obj + }; + TryCatch try_catch; + cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); + if (try_catch.HasCaught()) { + node::FatalException(try_catch); + } + } + delete data; + delete req; +} + /** * IsSpent() * bitcoindjs.isSpent() @@ -1465,6 +1621,7 @@ init(Handle target) { NODE_SET_METHOD(target, "stopped", IsStopped); NODE_SET_METHOD(target, "getBlock", GetBlock); NODE_SET_METHOD(target, "getTransaction", GetTransaction); + NODE_SET_METHOD(target, "getTransactionWithBlockInfo", GetTransactionWithBlockInfo); NODE_SET_METHOD(target, "getInfo", GetInfo); NODE_SET_METHOD(target, "isSpent", IsSpent); NODE_SET_METHOD(target, "getBlockIndex", GetBlockIndex);