/** * bitcoind.js - a binding for node.js which links to libbitcoind.so/dylib. * Copyright (c) 2015, BitPay (MIT License) * * libbitcoind.cc: * A bitcoind node.js binding. */ #include "libbitcoind.h" using namespace std; using namespace boost; using namespace node; using namespace v8; using Nan::New; using Nan::Null; using Nan::Set; using Nan::ThrowError; using Nan::GetCurrentContext; using Nan::GetFunction; using v8::FunctionTemplate; /** * Bitcoin Globals */ // These global functions and variables are // required to be defined/exposed here. extern void WaitForShutdown(boost::thread_group* threadGroup); static termios orig_termios; extern CTxMemPool mempool; extern int64_t nTimeBestReceived; /** * Node.js Internal Function Templates */ 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); static void async_tip_update_after(uv_work_t *req); static void async_start_node(uv_work_t *req); static void async_start_node_after(uv_work_t *req); static void async_blocks_ready(uv_work_t *req); static void async_blocks_ready_after(uv_work_t *req); static void async_stop_node(uv_work_t *req); static void async_stop_node_after(uv_work_t *req); static int start_node(void); static void start_node_thread(void); static void async_get_block(uv_work_t *req); static void async_get_block_after(uv_work_t *req); static void 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 queueTx(const CTransaction&); static bool queueTxLeave(const CTransaction&); extern "C" void init(Handle); /** * Private Global Variables * 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; static bool g_rpc = false; static bool g_testnet = false; static bool g_regtest = false; static bool g_txindex = false; static boost::thread_group threadGroup; /** * Private Structs * Used for async functions and necessary linked lists at points. */ struct async_tip_update_data { uv_work_t req; size_t result; Isolate* isolate; Persistent callback; }; /** * async_node_data * Where the uv async request data resides. */ struct async_block_ready_data { uv_work_t req; std::string err_msg; std::string result; Isolate* isolate; Persistent callback; }; /** * async_node_data * Where the uv async request data resides. */ struct async_node_data { uv_work_t req; std::string err_msg; std::string result; std::string datadir; bool rpc; bool testnet; bool regtest; bool txindex; Isolate* isolate; Persistent callback; }; /** * async_block_data */ struct async_block_data { uv_work_t req; std::string err_msg; uint256 hash; int64_t height; char* buffer; uint32_t size; CBlock cblock; CBlockIndex* cblock_index; Isolate* isolate; Persistent callback; }; /** * async_tx_data */ struct async_tx_data { uv_work_t req; std::string err_msg; std::string txid; std::string blockHash; uint32_t nTime; int64_t height; bool queryMempool; CTransaction ctx; Isolate* isolate; Persistent callback; }; /** * Helpers */ static bool set_cooked(void); /** * SyncPercentage() * bitcoind.syncPercentage() * provides a float value >= indicating the progress of the blockchain sync */ NAN_METHOD(SyncPercentage) { const CChainParams& chainParams = Params(); float progress = 0; progress = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.Tip()); info.GetReturnValue().Set(progress * 100); }; NAN_METHOD(GetBestBlockHash) { LOCK(cs_main); info.GetReturnValue().Set(New(chainActive.Tip()->GetBlockHash().GetHex()).ToLocalChecked()); } NAN_METHOD(GetNextBlockHash) { if (info.Length() < 1 || !info[0]->IsString()) { return ThrowError("Usage: bitcoind.getNextBlockHash(blockhash)"); } CBlockIndex* pblockindex; v8::String::Utf8Value param1(info[0]->ToString()); std::string *hash = new std::string(*param1); uint256 shash = uint256S(*hash); pblockindex = mapBlockIndex[shash]; CBlockIndex* pnextblockindex = chainActive.Next(pblockindex); if (pnextblockindex) { uint256 nexthash = pnextblockindex->GetBlockHash(); std::string rethash = nexthash.ToString(); info.GetReturnValue().Set(New(rethash).ToLocalChecked()); } else { info.GetReturnValue().Set(Null()); } } /** * IsSynced() * bitcoind.isSynced() * returns a boolean of bitcoin is fully synced */ NAN_METHOD(IsSynced) { bool isDownloading = IsInitialBlockDownload(); info.GetReturnValue().Set(New(!isDownloading)); }; NAN_METHOD(StartTxMon) { Isolate* isolate = info.GetIsolate(); Local callback = Local::Cast(info[0]); Eternal cb(isolate, callback); txmon_callback = cb; txmon_callback_available = true; CNodeSignals& nodeSignals = GetNodeSignals(); nodeSignals.TxToMemPool.connect(&queueTx); uv_async_init(uv_default_loop(), &txmon_async, tx_notifier); 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(); HandleScope scope(isolate); Local results = Array::New(isolate); int arrayIndex = 0; LOCK(cs_main); BOOST_FOREACH(const CTransaction& tx, txQueue) { 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()); Nan::Set(obj, New("mempool").ToLocalChecked(), New(true)); results->Set(arrayIndex, obj); arrayIndex++; } const unsigned argc = 1; Local argv[argc] = { Local::New(isolate, results) }; Local cb = txmon_callback.Get(isolate); cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); txQueue.clear(); } static bool queueTx(const CTransaction& tx) { LOCK(cs_main); txQueue.push_back(tx); uv_async_send(&txmon_async); 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 */ NAN_METHOD(OnTipUpdate) { Isolate* isolate = info.GetIsolate(); HandleScope scope(isolate); async_tip_update_data *req = new async_tip_update_data(); Local callback = Local::Cast(info[0]); req->callback.Reset(isolate, callback); req->req.data = req; req->isolate = isolate; int status = uv_queue_work(uv_default_loop(), &req->req, async_tip_update, (uv_after_work_cb)async_tip_update_after); assert(status == 0); info.GetReturnValue().Set(Null()); } static void async_tip_update(uv_work_t *req) { async_tip_update_data *data = reinterpret_cast(req->data); size_t lastHeight = chainActive.Height(); while(lastHeight == (size_t)chainActive.Height() && !shutdown_complete) { usleep(1E6); } data->result = chainActive.Height(); } static void async_tip_update_after(uv_work_t *r) { async_tip_update_data *req = reinterpret_cast(r->data); Isolate* isolate = req->isolate; HandleScope scope(isolate); Local cb = Local::New(isolate, req->callback); Nan::TryCatch try_catch; Local result = Undefined(isolate); if (!shutdown_complete) { result = New(req->result); } Local argv[1] = { Local::New(isolate, result) }; cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); if (try_catch.HasCaught()) { Nan::FatalException(try_catch); } req->callback.Reset(); } NAN_METHOD(OnBlocksReady) { Isolate* isolate = info.GetIsolate(); HandleScope scope(isolate); async_block_ready_data *req = new async_block_ready_data(); req->err_msg = std::string(""); req->result = std::string(""); req->req.data = req; req->isolate = isolate; Local callback = Local::Cast(info[0]); req->callback.Reset(isolate, callback); int status = uv_queue_work(uv_default_loop(), &req->req, async_blocks_ready, (uv_after_work_cb)async_blocks_ready_after); assert(status == 0); info.GetReturnValue().Set(Null()); } /** * async_start_node() * Call start_node() and start all our boost threads. */ static void async_blocks_ready(uv_work_t *req) { async_block_ready_data *data = reinterpret_cast(req->data); data->result = std::string(""); while(!chainActive.Tip()) { usleep(1E6); } CBlockIndex* tip = chainActive.Tip(); uint256 tipHash = tip->GetBlockHash(); // Wait to be able to query for blocks by hash while(mapBlockIndex.count(tipHash) == 0) { usleep(1E6); } // Wait for chainActive to be able to get the hash // for the genesis block for querying blocks by height while(chainActive[0] == NULL) { usleep(1E6); } //If the wallet is enabled, then we should make sure we can load it #ifdef ENABLE_WALLET while(pwalletMain == NULL || RPCIsInWarmup(NULL)) { usleep(1E6); } #endif // Wait until we can get a lock on cs_main // And therefore ready to be able to quickly // query for transactions from the mempool. LOCK(cs_main); { return; } } static void async_blocks_ready_after(uv_work_t *r) { async_block_ready_data* req = reinterpret_cast(r->data); Isolate* isolate = req->isolate; HandleScope scope(isolate); Nan::TryCatch try_catch; Local cb = Local::New(isolate, req->callback); if (req->err_msg != "") { Local err = Exception::Error(New(req->err_msg).ToLocalChecked()); Local argv[1] = { err }; cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); } else { Local argv[2] = { v8::Null(isolate), Local::New(isolate, New(req->result).ToLocalChecked()) }; cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); } if (try_catch.HasCaught()) { Nan::FatalException(try_catch); } req->callback.Reset(); } /** * StartBitcoind() * bitcoind.start(callback) * Start the bitcoind node with AppInit2() on a separate thread. */ NAN_METHOD(StartBitcoind) { Isolate* isolate = info.GetIsolate(); HandleScope scope(isolate); Local callback; std::string datadir = std::string(""); bool rpc = false; bool testnet = false; bool regtest = false; bool txindex = false; if (info.Length() >= 2 && info[0]->IsObject() && info[1]->IsFunction()) { Local options = Local::Cast(info[0]); if (options->Get(New("datadir").ToLocalChecked())->IsString()) { String::Utf8Value datadir_(options->Get(New("datadir").ToLocalChecked())->ToString()); datadir = std::string(*datadir_); } if (options->Get(New("rpc").ToLocalChecked())->IsBoolean()) { rpc = options->Get(New("rpc").ToLocalChecked())->ToBoolean()->IsTrue(); } if (options->Get(New("network").ToLocalChecked())->IsString()) { String::Utf8Value network_(options->Get(New("network").ToLocalChecked())->ToString()); std::string network = std::string(*network_); if (network == "testnet") { testnet = true; } else if (network == "regtest") { regtest = true; } } if (options->Get(New("txindex").ToLocalChecked())->IsBoolean()) { txindex = options->Get(New("txindex").ToLocalChecked())->ToBoolean()->IsTrue(); } callback = Local::Cast(info[1]); } else if (info.Length() >= 2 && (info[0]->IsUndefined() || info[0]->IsNull()) && info[1]->IsFunction()) { callback = Local::Cast(info[1]); } else if (info.Length() >= 1 && info[0]->IsFunction()) { callback = Local::Cast(info[0]); } else { return ThrowError( "Usage: bitcoind.start(callback)"); } // // Run bitcoind's StartNode() on a separate thread. // async_node_data *req = new async_node_data(); req->err_msg = std::string(""); req->result = std::string(""); req->datadir = datadir; req->rpc = rpc; req->testnet = testnet; req->regtest = regtest; req->txindex = txindex; req->isolate = isolate; req->callback.Reset(isolate, callback); req->req.data = req; int status = uv_queue_work(uv_default_loop(), &req->req, async_start_node, (uv_after_work_cb)async_start_node_after); assert(status == 0); info.GetReturnValue().Set(Null()); } /** * async_start_node() * Call start_node() and start all our boost threads. */ static void async_start_node(uv_work_t *req) { async_node_data *data = reinterpret_cast(req->data); if (data->datadir != "") { g_data_dir = (char *)data->datadir.c_str(); } else { g_data_dir = (char *)malloc(sizeof(char) * 512); snprintf(g_data_dir, sizeof(char) * 512, "%s/.bitcoind.js", getenv("HOME")); } g_rpc = (bool)data->rpc; g_testnet = (bool)data->testnet; g_regtest = (bool)data->regtest; g_txindex = (bool)data->txindex; tcgetattr(STDIN_FILENO, &orig_termios); start_node(); data->result = std::string("bitcoind opened."); } /** * async_start_node_after() * Execute our callback. */ static void async_start_node_after(uv_work_t *r) { async_node_data *req = reinterpret_cast(r->data); Isolate* isolate = req->isolate; HandleScope scope(isolate); Nan::TryCatch try_catch; Local cb = Local::New(isolate, req->callback); if (req->err_msg != "") { Local err = Exception::Error(New(req->err_msg).ToLocalChecked()); Local argv[1] = { err }; cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); } else { Local argv[2] = { v8::Null(isolate), Local::New(isolate, New(req->result).ToLocalChecked()) }; cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); } if (try_catch.HasCaught()) { Nan::FatalException(try_catch); } req->callback.Reset(); } /** * start_node(void) * Start AppInit2() on a separate thread, wait for * Unfortunately, we need to wait for the initialization * to unhook the signal handlers so we can use them * from node.js in javascript. */ static int start_node(void) { SetupEnvironment(); noui_connect(); new boost::thread(boost::bind(&start_node_thread)); return 0; } static void start_node_thread(void) { CScheduler scheduler; // Workaround for AppInit2() arg parsing. Not ideal, but it works. int argc = 0; char **argv = (char **)malloc((4 + 1) * sizeof(char **)); argv[argc] = (char *)"bitcoind"; argc++; if (g_data_dir) { const int argl = 9 + strlen(g_data_dir) + 1; char *arg = (char *)malloc(sizeof(char) * argl); int w = snprintf(arg, argl, "-datadir=%s", g_data_dir); if (w >= 10 && w <= argl) { arg[w] = '\0'; argv[argc] = arg; argc++; } else { if (set_cooked()) { fprintf(stderr, "bitcoind.js: Bad -datadir value.\n"); } } } if (g_rpc) { argv[argc] = (char *)"-server"; argc++; } if (g_testnet) { argv[argc] = (char *)"-testnet"; argc++; } if (g_regtest) { argv[argc] = (char *)"-regtest"; argc++; } argv[argc] = (char *)"-txindex"; argc++; argv[argc] = NULL; bool fRet = false; try { ParseParameters((const int)argc, (const char **)argv); if (!boost::filesystem::is_directory(GetDataDir(false))) { if (set_cooked()) { fprintf(stderr, "bitcoind.js: Specified data directory \"%s\" does not exist.\n", mapArgs["-datadir"].c_str()); } shutdown_complete = true; _exit(1); return; } try { ReadConfigFile(mapArgs, mapMultiArgs); } catch(std::exception &e) { if (set_cooked()) { fprintf(stderr, "bitcoind.js: Error reading configuration file: %s\n", e.what()); } shutdown_complete = true; _exit(1); return; } if (!SelectParamsFromCommandLine()) { if (set_cooked()) { fprintf(stderr, "bitcoind.js: Invalid combination of -regtest and -testnet.\n"); } shutdown_complete = true; _exit(1); return; } CreatePidFile(GetPidFile(), getpid()); fRet = AppInit2(threadGroup, scheduler); } catch (std::exception& e) { if (set_cooked()) { fprintf(stderr, "bitcoind.js: AppInit2(): std::exception\n"); } } catch (...) { if (set_cooked()) { fprintf(stderr, "bitcoind.js: AppInit2(): other exception\n"); } } if (!fRet) { threadGroup.interrupt_all(); } else { WaitForShutdown(&threadGroup); } Shutdown(); shutdown_complete = true; } /** * StopBitcoind() * bitcoind.stop(callback) */ NAN_METHOD(StopBitcoind) { Isolate* isolate = info.GetIsolate(); HandleScope scope(isolate); if (info.Length() < 1 || !info[0]->IsFunction()) { return ThrowError( "Usage: bitcoind.stop(callback)"); } Local callback = Local::Cast(info[0]); // // Run bitcoind's StartShutdown() on a separate thread. // async_node_data *req = new async_node_data(); req->err_msg = std::string(""); req->result = std::string(""); req->callback.Reset(isolate, callback); req->req.data = req; req->isolate = isolate; int status = uv_queue_work(uv_default_loop(), &req->req, async_stop_node, (uv_after_work_cb)async_stop_node_after); assert(status == 0); info.GetReturnValue().Set(Null()); } /** * async_stop_node() * Call StartShutdown() to join the boost threads, which will call Shutdown() * and set shutdown_complete to true to notify the main node.js thread. */ static void async_stop_node(uv_work_t *req) { async_node_data *data = reinterpret_cast(req->data); StartShutdown(); while(!shutdown_complete) { usleep(1E6); } data->result = std::string("bitcoind shutdown."); } /** * async_stop_node_after() * Execute our callback. */ static void async_stop_node_after(uv_work_t *r) { async_node_data* req = reinterpret_cast(r->data); Isolate* isolate = req->isolate; HandleScope scope(isolate); Nan::TryCatch try_catch; Local cb = Local::New(isolate, req->callback); if (req->err_msg != "") { Local err = Exception::Error(New(req->err_msg).ToLocalChecked()); Local argv[1] = { err }; cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); } else { Local argv[2] = { Local::New(isolate, Null()), Local::New(isolate, New(req->result).ToLocalChecked()) }; cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); } if (try_catch.HasCaught()) { Nan::FatalException(try_catch); } req->callback.Reset(); } /** * GetBlock() * bitcoind.getBlock([blockhash,blockheight], callback) * Read any block from disk asynchronously. */ NAN_METHOD(GetBlock) { Isolate* isolate = info.GetIsolate(); HandleScope scope(isolate); if (info.Length() < 2 || (!info[0]->IsString() && !info[0]->IsNumber()) || !info[1]->IsFunction()) { return ThrowError( "Usage: bitcoind.getBlock([blockhash,blockheight], callback)"); } async_block_data *req = new async_block_data(); if (info[0]->IsNumber()) { int64_t height = info[0]->IntegerValue(); req->err_msg = std::string(""); req->height = height; } else { std::string hash = *Nan::Utf8String(info[0]); req->err_msg = std::string(""); req->hash = uint256S(hash); req->height = -1; } Local callback = Local::Cast(info[1]); req->req.data = req; req->isolate = isolate; req->callback.Reset(isolate, callback); int status = uv_queue_work(uv_default_loop(), &req->req, async_get_block, (uv_after_work_cb)async_get_block_after); assert(status == 0); info.GetReturnValue().Set(Null()); } static void async_get_block(uv_work_t *req) { async_block_data* data = reinterpret_cast(req->data); CBlockIndex* pblockindex; if (data->height != -1) { pblockindex = chainActive[data->height]; if (pblockindex == NULL) { data->err_msg = std::string("Block not found."); return; } } else { if (mapBlockIndex.count(data->hash) == 0) { data->err_msg = std::string("Block not found."); return; } else { pblockindex = mapBlockIndex[data->hash]; } } const CDiskBlockPos& pos = pblockindex->GetBlockPos(); // We can read directly from the file, and pass that, we don't need to // deserialize the entire block only for it to then be serialized // and then deserialized again in JavaScript // Open history file to read CAutoFile filein(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION); if (filein.IsNull()) { data->err_msg = std::string("ReadBlockFromDisk: OpenBlockFile failed"); return; } // Get the actual file, seeked position and rewind a uint32_t FILE* blockFile = filein.release(); long int filePos = ftell(blockFile); fseek(blockFile, filePos - sizeof(uint32_t), SEEK_SET); // Read the size of the block uint32_t size = 0; fread(&size, sizeof(uint32_t), 1, blockFile); // Read block char* buffer = (char *)malloc(sizeof(char) * size); fread((void *)buffer, sizeof(char), size, blockFile); fclose(blockFile); data->buffer = buffer; data->size = size; data->cblock_index = pblockindex; } static void async_get_block_after(uv_work_t *r) { async_block_data* req = reinterpret_cast(r->data); Isolate *isolate = req->isolate; HandleScope scope(isolate); Nan::TryCatch try_catch; Local cb = Local::New(isolate, req->callback); if (req->err_msg != "") { Local err = Exception::Error(New(req->err_msg).ToLocalChecked()); Local argv[1] = { err }; cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); } else { Nan::MaybeLocal rawNodeBuffer = Nan::NewBuffer(req->buffer, req->size); Local argv[2] = { Local::New(isolate, Null()), rawNodeBuffer.ToLocalChecked() }; cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); } if (try_catch.HasCaught()) { Nan::FatalException(try_catch); } req->callback.Reset(); } /** * GetTransaction() * bitcoind.getTransaction(txid, queryMempool, callback) * Read any transaction from disk asynchronously. */ NAN_METHOD(GetTransaction) { Isolate* isolate = info.GetIsolate(); HandleScope scope(isolate); if (info.Length() < 3 || !info[0]->IsString() || !info[1]->IsBoolean() || !info[2]->IsFunction()) { return ThrowError( "Usage: daemon.getTransaction(txid, queryMempool, callback)"); } std::string txid = *Nan::Utf8String(info[0]); bool queryMempool = info[1]->BooleanValue(); Local callback = Local::Cast(info[2]); async_tx_data *req = new async_tx_data(); req->err_msg = std::string(""); req->txid = txid; req->queryMempool = queryMempool; req->isolate = isolate; req->req.data = req; req->callback.Reset(isolate, callback); int status = uv_queue_work(uv_default_loop(), &req->req, async_get_tx, (uv_after_work_cb)async_get_tx_after); assert(status == 0); info.GetReturnValue().Set(Null()); } static void async_get_tx(uv_work_t *req) { async_tx_data* data = reinterpret_cast(req->data); uint256 blockhash; uint256 hash = uint256S(data->txid); CTransaction ctx; if (data->queryMempool) { LOCK(cs_main); { if (mempool.lookup(hash, ctx)) { data->ctx = ctx; 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; } const int HEADER_SIZE = sizeof(int32_t) + sizeof(uint32_t) * 3 + sizeof(char) * 64; try { fseek(file.Get(), postx.nTxOffset + HEADER_SIZE, 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; } } } static void async_get_tx_after(uv_work_t *r) { async_tx_data* req = reinterpret_cast(r->data); Isolate* isolate = req->isolate; HandleScope scope(isolate); CTransaction ctx = req->ctx; Nan::TryCatch try_catch; Local cb = Local::New(isolate, req->callback); if (req->err_msg != "") { Local err = Exception::Error(New(req->err_msg).ToLocalChecked()); Local argv[1] = { err }; cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); } else { if (!ctx.IsNull()) { CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << ctx; std::string stx = ssTx.str(); Nan::MaybeLocal result = Nan::CopyBuffer((char *)stx.c_str(), stx.size()); Local argv[2] = { Local::New(isolate, Null()), result.ToLocalChecked() }; cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); } else { Local argv[2] = { Local::New(isolate, Null()), Local::New(isolate, Null()) }; cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); } } if (try_catch.HasCaught()) { Nan::FatalException(try_catch); } req->callback.Reset(); } /** * GetTransactionWithBlockInfo() * bitcoind.getTransactionWithBlockInfo(txid, queryMempool, callback) * Read any transaction from disk asynchronously with block timestamp and height. */ NAN_METHOD(GetTransactionWithBlockInfo) { Isolate* isolate = info.GetIsolate(); HandleScope scope(isolate); if (info.Length() < 3 || !info[0]->IsString() || !info[1]->IsBoolean() || !info[2]->IsFunction()) { return ThrowError( "Usage: bitcoind.getTransactionWithBlockInfo(txid, queryMempool, callback)"); } String::Utf8Value txid_(info[0]->ToString()); bool queryMempool = info[1]->BooleanValue(); Local callback = Local::Cast(info[2]); async_tx_data *req = new async_tx_data(); req->err_msg = std::string(""); req->txid = std::string(""); std::string txid = std::string(*txid_); req->txid = txid; req->queryMempool = queryMempool; req->req.data = req; req->isolate = isolate; req->callback.Reset(isolate, callback); int status = uv_queue_work(uv_default_loop(), &req->req, async_get_tx_and_info, (uv_after_work_cb)async_get_tx_and_info_after); assert(status == 0); info.GetReturnValue().Set(Null()); } static void async_get_tx_and_info(uv_work_t *req) { async_tx_data* data = reinterpret_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->blockHash = blockHash.GetHex(); 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]; if (!chainActive.Contains(blockIndex)) { data->height = -1; } else { data->height = blockIndex->nHeight; } } } } static void async_get_tx_and_info_after(uv_work_t *r) { async_tx_data* req = reinterpret_cast(r->data); Isolate* isolate = req->isolate; HandleScope scope(isolate); CTransaction ctx = req->ctx; Nan::TryCatch try_catch; Local cb = Local::New(isolate, req->callback); Local obj = New(); if (req->err_msg != "") { Local err = Exception::Error(New(req->err_msg).ToLocalChecked()); Local argv[1] = { err }; cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); } else { CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << ctx; std::string stx = ssTx.str(); Nan::MaybeLocal rawNodeBuffer = Nan::CopyBuffer((char *)stx.c_str(), stx.size()); Nan::Set(obj, New("blockHash").ToLocalChecked(), New(req->blockHash).ToLocalChecked()); Nan::Set(obj, New("height").ToLocalChecked(), New(req->height)); Nan::Set(obj, New("timestamp").ToLocalChecked(), New(req->nTime)); Nan::Set(obj, New("buffer").ToLocalChecked(), rawNodeBuffer.ToLocalChecked()); Local argv[2] = { Local::New(isolate, Null()), obj }; cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); } if (try_catch.HasCaught()) { Nan::FatalException(try_catch); } req->callback.Reset(); } /** * IsSpent() * bitcoind.isSpent() * Determine if an outpoint is spent */ NAN_METHOD(IsSpent) { if (info.Length() > 2) { return ThrowError( "Usage: bitcoind.isSpent(txid, outputIndex)"); } String::Utf8Value arg(info[0]->ToString()); std::string argStr = std::string(*arg); const uint256 txid = uint256S(argStr); int outputIndex = info[1]->IntegerValue(); { LOCK(mempool.cs); CCoinsView dummy; CCoinsViewCache view(&dummy); CCoinsViewMemPool viewMemPool(pcoinsTip, mempool); view.SetBackend(viewMemPool); if (view.HaveCoins(txid)) { const CCoins* coins = view.AccessCoins(txid); if (coins && coins->IsAvailable(outputIndex)) { info.GetReturnValue().Set(New(false)); return; } } } info.GetReturnValue().Set(New(true)); }; /** * GetBlockIndex() * bitcoind.getBlockIndex() * Get index information about a block by hash including: * - the total amount of work (expected number of hashes) in the chain up to * and including this block. * - the previous hash of the block */ NAN_METHOD(GetBlockIndex) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); CBlockIndex* blockIndex; if (info[0]->IsNumber()) { int64_t height = info[0]->IntegerValue(); blockIndex = chainActive[height]; if (blockIndex == NULL) { info.GetReturnValue().Set(Null()); return; } } else { String::Utf8Value hash_(info[0]->ToString()); std::string hashStr = std::string(*hash_); uint256 hash = uint256S(hashStr); if (mapBlockIndex.count(hash) == 0) { info.GetReturnValue().Set(Null()); } else { blockIndex = mapBlockIndex[hash]; } } Local obj = New(); arith_uint256 cw = blockIndex->nChainWork; CBlockIndex* prevBlockIndex = blockIndex->pprev; if (&prevBlockIndex->phashBlock != 0) { const uint256* prevHash = prevBlockIndex->phashBlock; Nan::Set(obj, New("prevHash").ToLocalChecked(), New(prevHash->GetHex()).ToLocalChecked()); } else { Nan::Set(obj, New("prevHash").ToLocalChecked(), Null()); } Nan::Set(obj, New("hash").ToLocalChecked(), New(blockIndex->phashBlock->GetHex()).ToLocalChecked()); Nan::Set(obj, New("chainWork").ToLocalChecked(), New(cw.GetHex()).ToLocalChecked()); Nan::Set(obj, New("height").ToLocalChecked(), New(blockIndex->nHeight)); info.GetReturnValue().Set(obj); }; /** * IsMainChain() * bitcoind.isMainChain() * * @param {string} - block hash * @returns {boolean} - True if the block is in the main chain. False if it is an orphan. */ NAN_METHOD(IsMainChain) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); CBlockIndex* blockIndex; String::Utf8Value hash_(info[0]->ToString()); std::string hashStr = std::string(*hash_); uint256 hash = uint256S(hashStr); if (mapBlockIndex.count(hash) == 0) { info.GetReturnValue().Set(Null()); } else { blockIndex = mapBlockIndex[hash]; } if (chainActive.Contains(blockIndex)) { info.GetReturnValue().Set(New(true)); } else { info.GetReturnValue().Set(New(false)); } } /** * GetInfo() * bitcoind.getInfo() * Get miscellaneous information */ NAN_METHOD(GetInfo) { if (info.Length() > 0) { return ThrowError( "Usage: bitcoind.getInfo()"); } Local obj = New(); proxyType proxy; GetProxy(NET_IPV4, proxy); Nan::Set(obj, New("version").ToLocalChecked(), New(CLIENT_VERSION)); Nan::Set(obj, New("protocolversion").ToLocalChecked(), New(PROTOCOL_VERSION)); Nan::Set(obj, New("blocks").ToLocalChecked(), New((int)chainActive.Height())->ToInt32()); Nan::Set(obj, New("timeoffset").ToLocalChecked(), New(GetTimeOffset())); Nan::Set(obj, New("connections").ToLocalChecked(), New((int)vNodes.size())->ToInt32()); Nan::Set(obj, New("difficulty").ToLocalChecked(), New((double)GetDifficulty())); Nan::Set(obj, New("testnet").ToLocalChecked(), New(Params().NetworkIDString() == "test")); Nan::Set(obj, New("network").ToLocalChecked(), New(Params().NetworkIDString()).ToLocalChecked()); Nan::Set(obj, New("relayfee").ToLocalChecked(), New(::minRelayTxFee.GetFeePerK())); // double Nan::Set(obj, New("errors").ToLocalChecked(), New(GetWarnings("statusbar")).ToLocalChecked()); info.GetReturnValue().Set(obj); } /** * Estimate Fee * @blocks {number} - The number of blocks until confirmed */ NAN_METHOD(EstimateFee) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); int nBlocks = info[0]->NumberValue(); if (nBlocks < 1) { nBlocks = 1; } CFeeRate feeRate = mempool.estimateFee(nBlocks); if (feeRate == CFeeRate(0)) { info.GetReturnValue().Set(New(-1.0)); return; } CAmount nFee = feeRate.GetFeePerK(); info.GetReturnValue().Set(New(nFee)); } /** * Send Transaction * bitcoind.sendTransaction() * Will add a transaction to the mempool and broadcast to connected peers. * @param {string} - The serialized hex string of the transaction. * @param {boolean} - Skip absurdly high fee checks */ NAN_METHOD(SendTransaction) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); LOCK(cs_main); // Decode the transaction v8::String::Utf8Value param1(info[0]->ToString()); std::string *input = new std::string(*param1); CTransaction tx; if (!DecodeHexTx(tx, *input)) { return ThrowError("TX decode failed"); } uint256 hashTx = tx.GetHash(); // Skip absurdly high fee check bool allowAbsurdFees = false; if (info.Length() > 1) { allowAbsurdFees = info[1]->BooleanValue(); } CCoinsViewCache &view = *pcoinsTip; const CCoins* existingCoins = view.AccessCoins(hashTx); bool fHaveMempool = mempool.exists(hashTx); bool fHaveChain = existingCoins && existingCoins->nHeight < 1000000000; if (!fHaveMempool && !fHaveChain) { CValidationState state; bool fMissingInputs; // Attempt to add the transaction to the mempool if (!AcceptToMemoryPool(mempool, state, tx, false, &fMissingInputs, !allowAbsurdFees)) { if (state.IsInvalid()) { return ThrowError((boost::lexical_cast(state.GetRejectCode()) + ": " + state.GetRejectReason()).c_str()); } else { if (fMissingInputs) { return ThrowError("Missing inputs"); } return ThrowError(state.GetRejectReason().c_str()); } } } else if (fHaveChain) { return ThrowError("transaction already in block chain"); } // Relay the transaction connect peers RelayTransaction(tx); info.GetReturnValue().Set(Local::New(isolate, New(hashTx.GetHex()).ToLocalChecked())); } /** * GetMempoolTransactions * bitcoind.getMempoolTransactions() * Will return an array of transaction buffers. */ NAN_METHOD(GetMempoolTransactions) { Isolate* isolate = info.GetIsolate(); HandleScope scope(isolate); Local transactions = Array::New(isolate); int arrayIndex = 0; { LOCK(mempool.cs); // Iterate through the entire mempool std::map mapTx = mempool.mapTx; for(std::map::iterator it = mapTx.begin(); it != mapTx.end(); it++) { CTxMemPoolEntry entry = it->second; const CTransaction tx = entry.GetTx(); CDataStream dataStreamTx(SER_NETWORK, PROTOCOL_VERSION); dataStreamTx << tx; std::string txString = dataStreamTx.str(); Nan::MaybeLocal txBuffer = Nan::CopyBuffer((char *)txString.c_str(), txString.size()); transactions->Set(arrayIndex, txBuffer.ToLocalChecked()); arrayIndex++; } } info.GetReturnValue().Set(transactions); } /** * AddMempoolUncheckedTransaction */ NAN_METHOD(AddMempoolUncheckedTransaction) { v8::String::Utf8Value param1(info[0]->ToString()); std::string *input = new std::string(*param1); CTransaction tx; if (!DecodeHexTx(tx, *input)) { return ThrowError("could not decode tx"); } bool added = mempool.addUnchecked(tx.GetHash(), CTxMemPoolEntry(tx, 0, 0, 0.0, 1)); info.GetReturnValue().Set(New(added)); } /** * Helpers */ static bool set_cooked(void) { uv_tty_t tty; tty.mode = 1; tty.orig_termios = orig_termios; if (!uv_tty_set_mode(&tty, 0)) { printf("\x1b[H\x1b[J"); return true; } return false; } /** * Init() * Initialize the singleton object known as bitcoind. */ NAN_MODULE_INIT(init) { Nan::Set(target, New("start").ToLocalChecked(), GetFunction(New(StartBitcoind)).ToLocalChecked()); Nan::Set(target, New("onBlocksReady").ToLocalChecked(), GetFunction(New(OnBlocksReady)).ToLocalChecked()); Nan::Set(target, New("onTipUpdate").ToLocalChecked(), GetFunction(New(OnTipUpdate)).ToLocalChecked()); Nan::Set(target, New("stop").ToLocalChecked(), GetFunction(New(StopBitcoind)).ToLocalChecked()); Nan::Set(target, New("getBlock").ToLocalChecked(), GetFunction(New(GetBlock)).ToLocalChecked()); Nan::Set(target, New("getTransaction").ToLocalChecked(), GetFunction(New(GetTransaction)).ToLocalChecked()); Nan::Set(target, New("getTransactionWithBlockInfo").ToLocalChecked(), GetFunction(New(GetTransactionWithBlockInfo)).ToLocalChecked()); Nan::Set(target, New("getInfo").ToLocalChecked(), GetFunction(New(GetInfo)).ToLocalChecked()); Nan::Set(target, New("isSpent").ToLocalChecked(), GetFunction(New(IsSpent)).ToLocalChecked()); Nan::Set(target, New("getBlockIndex").ToLocalChecked(), GetFunction(New(GetBlockIndex)).ToLocalChecked()); Nan::Set(target, New("isMainChain").ToLocalChecked(), GetFunction(New(IsMainChain)).ToLocalChecked()); Nan::Set(target, New("getMempoolTransactions").ToLocalChecked(), GetFunction(New(GetMempoolTransactions)).ToLocalChecked()); Nan::Set(target, New("addMempoolUncheckedTransaction").ToLocalChecked(), GetFunction(New(AddMempoolUncheckedTransaction)).ToLocalChecked()); 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()); Nan::Set(target, New("getNextBlockHash").ToLocalChecked(), GetFunction(New(GetNextBlockHash)).ToLocalChecked()); } NODE_MODULE(libbitcoind, init);