/** * bitcoind.js - a binding for node.js which links to libbitcoind.so/dylib. * Copyright (c) 2015, BitPay (MIT License) * * bitcoindjs.cc: * A bitcoind node.js binding. */ #include "bitcoindjs.h" using namespace std; using namespace boost; using namespace node; using namespace v8; /** * Bitcoin Globals */ // These global functions and variables are // required to be defined/exposed here. extern void DetectShutdownThread(boost::thread_group*); extern int nScriptCheckThreads; extern std::map mapArgs; extern CFeeRate payTxFee; extern const std::string strMessageMagic; extern std::string EncodeDumpTime(int64_t nTime); extern int64_t DecodeDumpTime(const std::string &str); extern std::string EncodeDumpString(const std::string &str); extern std::string DecodeDumpString(const std::string &str); extern bool fTxIndex; static termios orig_termios; /** * Node.js Internal Function Templates */ 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_progress(uv_work_t *req); static void async_get_progress_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_addrtx(uv_work_t *req); static void async_get_addrtx_after(uv_work_t *req); static void async_broadcast_tx(uv_work_t *req); static void async_broadcast_tx_after(uv_work_t *req); static void async_block_tx(uv_work_t *req); static void async_block_tx_after(uv_work_t *req); static void async_block_time(uv_work_t *req); static void async_block_time_after(uv_work_t *req); static void async_from_tx(uv_work_t *req); static void async_from_tx_after(uv_work_t *req); static inline void cblock_to_jsblock(const CBlock& cblock, CBlockIndex* cblock_index, Local jsblock, bool is_new); static inline void ctx_to_jstx(const CTransaction& ctx, uint256 blockhash, Local jstx); static inline void jsblock_to_cblock(const Local jsblock, CBlock& cblock); static inline void jstx_to_ctx(const Local jstx, CTransaction& ctx); static void hook_packets(void); static void unhook_packets(void); static bool process_packets(CNode* pfrom); static bool process_packet(CNode* pfrom, string strCommand, CDataStream& vRecv, int64_t nTimeReceived); static int get_tx(uint256 txid, uint256& blockhash, CTransaction& ctx); extern "C" void init(Handle); /** * Private Global Variables * Used only by bitcoindjs functions. */ 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_txindex = false; /** * Private Structs * Used for async functions and necessary linked lists at points. */ /** * async_node_data * Where the uv async request data resides. */ struct async_block_ready_data { std::string err_msg; std::string result; Eternal callback; }; /** * async_node_data * Where the uv async request data resides. */ struct async_node_data { std::string err_msg; std::string result; std::string datadir; bool rpc; bool testnet; bool txindex; Eternal callback; }; /** * async_block_data */ struct async_block_data { std::string err_msg; std::string hash; int64_t height; CBlock cblock; CBlockIndex* cblock_index; Eternal callback; }; /** * async_tx_data */ struct async_tx_data { std::string err_msg; std::string txid; std::string blockhash; CTransaction ctx; Eternal callback; }; /** * async_block_tx_data */ struct async_block_tx_data { std::string err_msg; std::string txid; CBlock cblock; CBlockIndex* cblock_index; CTransaction ctx; Eternal callback; }; /** * async_block_time_data */ typedef struct _cblocks_list { CBlock cblock; CBlockIndex* cblock_index; struct _cblocks_list *next; std::string err_msg; } cblocks_list; struct async_block_time_data { std::string err_msg; uint32_t gte; uint32_t lte; int64_t limit; cblocks_list *cblocks; Eternal callback; }; /** * async_addrtx_data */ typedef struct _ctx_list { CTransaction ctx; uint256 blockhash; struct _ctx_list *next; std::string err_msg; } ctx_list; struct async_addrtx_data { std::string err_msg; std::string addr; ctx_list *ctxs; int64_t blockheight; int64_t blocktime; Eternal callback; }; /** * async_broadcast_tx_data */ struct async_broadcast_tx_data { std::string err_msg; Eternal jstx; CTransaction ctx; std::string txid; bool override_fees; bool own_only; Eternal callback; }; /** * async_from_tx_data */ struct async_from_tx_data { std::string err_msg; std::string txid; ctx_list *ctxs; Eternal callback; }; /** * Read Raw DB */ #if USE_LDB_ADDR static ctx_list * read_addr(const std::string addr, const int64_t blockheight, const int64_t blocktime); #endif #if USE_LDB_TX static bool get_block_by_tx(const std::string itxid, CBlock& rcblock, CBlockIndex **rcblock_index, CTransaction& rctx); #endif /** * Helpers */ static bool set_cooked(void); /** * Functions */ NAN_METHOD(OnBlocksReady) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); Local callback; callback = Local::Cast(args[0]); async_block_ready_data *data = new async_block_ready_data(); data->err_msg = std::string(""); data->result = std::string(""); 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_blocks_ready, (uv_after_work_cb)async_blocks_ready_after); assert(status == 0); NanReturnValue(Undefined(isolate)); } /** * 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 = static_cast(req->data); data->result = std::string(""); while(!chainActive.Tip()) { usleep(1E4); } } static void async_blocks_ready_after(uv_work_t *req) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); async_block_ready_data *data = static_cast(req->data); Local cb = data->callback.Get(isolate); 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 { const unsigned argc = 2; Local argv[argc] = { v8::Null(isolate), Local::New(isolate, NanNew(data->result)) }; TryCatch try_catch; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } delete data; delete req; } /** * StartBitcoind() * bitcoind.start(callback) * Start the bitcoind node with AppInit2() on a separate thread. */ NAN_METHOD(StartBitcoind) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); Local callback; std::string datadir = std::string(""); bool rpc = false; bool testnet = false; bool txindex = false; if (args.Length() >= 2 && args[0]->IsObject() && args[1]->IsFunction()) { Local options = Local::Cast(args[0]); if (options->Get(NanNew("datadir"))->IsString()) { String::Utf8Value datadir_(options->Get(NanNew("datadir"))->ToString()); datadir = std::string(*datadir_); } if (options->Get(NanNew("rpc"))->IsBoolean()) { rpc = options->Get(NanNew("rpc"))->ToBoolean()->IsTrue(); } if (options->Get(NanNew("testnet"))->IsBoolean()) { testnet = options->Get(NanNew("testnet"))->ToBoolean()->IsTrue(); } if (options->Get(NanNew("txindex"))->IsBoolean()) { txindex = options->Get(NanNew("txindex"))->ToBoolean()->IsTrue(); } callback = Local::Cast(args[1]); } else if (args.Length() >= 2 && (args[0]->IsUndefined() || args[0]->IsNull()) && args[1]->IsFunction()) { callback = Local::Cast(args[1]); } else if (args.Length() >= 1 && args[0]->IsFunction()) { callback = Local::Cast(args[0]); } else { return NanThrowError( "Usage: bitcoind.start(callback)"); } // // Run bitcoind's StartNode() on a separate thread. // async_node_data *data = new async_node_data(); data->err_msg = std::string(""); data->result = std::string(""); data->datadir = datadir; data->rpc = rpc; data->testnet = testnet; data->txindex = txindex; 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_start_node, (uv_after_work_cb)async_start_node_after); assert(status == 0); NanReturnValue(Undefined(isolate)); } /** * 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 = static_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_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 *req) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); async_node_data *data = static_cast(req->data); Local cb = data->callback.Get(isolate); 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 { const unsigned argc = 2; Local argv[argc] = { v8::Null(isolate), Local::New(isolate, NanNew(data->result)) }; TryCatch try_catch; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } delete data; delete req; } /** * 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)); // Drop the bitcoind signal handlers: we want our own. signal(SIGINT, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGQUIT, SIG_DFL); // Hook into packet handling new boost::thread(boost::bind(&hook_packets)); return 0; } static void start_node_thread(void) { boost::thread_group threadGroup; boost::thread* detectShutdownThread = NULL; // 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_txindex) { 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()); detectShutdownThread = new boost::thread( boost::bind(&DetectShutdownThread, &threadGroup)); fRet = AppInit2(threadGroup); } catch (std::exception& e) { if (set_cooked()) { fprintf(stderr, "bitcoind.js: AppInit(): std::exception\n"); } } catch (...) { if (set_cooked()) { fprintf(stderr, "bitcoind.js: AppInit(): other exception\n"); } } if (!fRet) { if (detectShutdownThread) { detectShutdownThread->interrupt(); } threadGroup.interrupt_all(); } if (detectShutdownThread) { detectShutdownThread->join(); delete detectShutdownThread; detectShutdownThread = NULL; } Shutdown(); // bitcoind is shutdown. Notify the main thread // which is polling this variable: shutdown_complete = true; } /** * StopBitcoind() * bitcoind.stop(callback) */ NAN_METHOD(StopBitcoind) { fprintf(stderr, "Stopping Bitcoind please wait!"); Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsFunction()) { return NanThrowError( "Usage: bitcoind.stop(callback)"); } Local callback = Local::Cast(args[0]); // // Run bitcoind's StartShutdown() on a separate thread. // async_node_data *data = new async_node_data(); data->err_msg = std::string(""); data->result = std::string(""); 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_stop_node, (uv_after_work_cb)async_stop_node_after); assert(status == 0); NanReturnValue(Undefined(isolate)); } /** * 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 = static_cast(req->data); unhook_packets(); StartShutdown(); data->result = std::string("bitcoind shutdown."); } /** * async_stop_node_after() * Execute our callback. */ static void async_stop_node_after(uv_work_t *req) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); async_node_data* data = static_cast(req->data); Local cb = data->callback.Get(isolate); 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 { const unsigned argc = 2; Local argv[argc] = { Local::New(isolate, NanNull()), Local::New(isolate, NanNew(data->result)) }; TryCatch try_catch; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } delete data; delete req; } /** * IsStopping() * bitcoind.stopping() * Check whether bitcoind is in the process of shutting down. This is polled * from javascript. */ NAN_METHOD(IsStopping) { NanScope(); NanReturnValue(NanNew(ShutdownRequested())); } /** * IsStopped() * bitcoind.stopped() * Check whether bitcoind has shutdown completely. This will be polled by * javascript to check whether the libuv event loop is safe to stop. */ NAN_METHOD(IsStopped) { NanScope(); NanReturnValue(NanNew(shutdown_complete)); } /** * GetBlock() * bitcoind.getBlock([blockhash,blockheight], callback) * Read any block from disk asynchronously. */ NAN_METHOD(GetBlock) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.Length() < 2 || (!args[0]->IsString() && !args[0]->IsNumber()) || !args[1]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.getBlock([blockhash,blockheight], callback)"); } async_block_data *data = new async_block_data(); if (args[0]->IsNumber()) { int64_t height = args[0]->IntegerValue(); data->err_msg = std::string(""); data->hash = std::string(""); data->height = height; } else { String::Utf8Value hash_(args[0]->ToString()); std::string hash = std::string(*hash_); data->err_msg = std::string(""); data->hash = hash; data->height = -1; } Local callback = Local::Cast(args[1]); 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_block, (uv_after_work_cb)async_get_block_after); assert(status == 0); NanReturnValue(Undefined(isolate)); } static void async_get_block(uv_work_t *req) { async_block_data* data = static_cast(req->data); if (data->height != -1) { CBlockIndex* pblockindex = chainActive[data->height]; CBlock cblock; if (ReadBlockFromDisk(cblock, pblockindex)) { data->cblock = cblock; data->cblock_index = pblockindex; } else { data->err_msg = std::string("Block not found."); } return; } std::string strHash = data->hash; uint256 hash(strHash); if (mapBlockIndex.count(hash) == 0) { data->err_msg = std::string("Block not found."); } else { CBlock block; CBlockIndex* pblockindex = mapBlockIndex[hash]; if(!ReadBlockFromDisk(block, pblockindex)) { data->err_msg = std::string("Can't read block from disk"); } else { data->cblock = block; data->cblock_index = pblockindex; } } } static void async_get_block_after(uv_work_t *req) { Isolate *isolate = Isolate::GetCurrent(); HandleScope scope(isolate); async_block_data* data = static_cast(req->data); Local cb = data->callback.Get(isolate); 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 { const CBlock& cblock = data->cblock; CBlockIndex* cblock_index = data->cblock_index; Local jsblock = NanNew(); cblock_to_jsblock(cblock, cblock_index, jsblock, false); const unsigned argc = 2; Local argv[argc] = { Local::New(isolate, NanNull()), Local::New(isolate, jsblock) }; TryCatch try_catch; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } delete data; delete req; } /** * GetTransaction() * bitcoind.getTransaction(txid, [blockhash], callback) * Read any transaction from disk asynchronously. */ NAN_METHOD(GetTransaction) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.Length() < 3 || !args[0]->IsString() || !args[1]->IsString() || !args[2]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.getTransaction(txid, [blockhash], callback)"); } String::Utf8Value txid_(args[0]->ToString()); String::Utf8Value blockhash_(args[1]->ToString()); Local callback = Local::Cast(args[2]); std::string txid = std::string(*txid_); std::string blockhash = std::string(*blockhash_); if (blockhash == "") { blockhash = uint256(0).GetHex(); } async_tx_data *data = new async_tx_data(); data->err_msg = std::string(""); data->txid = txid; data->blockhash = blockhash; 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, (uv_after_work_cb)async_get_tx_after); assert(status == 0); NanReturnValue(Undefined(isolate)); } static void async_get_tx(uv_work_t *req) { async_tx_data* data = static_cast(req->data); uint256 hash(data->txid); uint256 blockhash(data->blockhash); CTransaction ctx; if (get_tx(hash, blockhash, ctx)) { data->ctx = ctx; data->blockhash = blockhash.GetHex(); } else { data->err_msg = std::string("Transaction not found."); } } static void async_get_tx_after(uv_work_t *req) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); async_tx_data* data = static_cast(req->data); CTransaction ctx = data->ctx; uint256 blockhash(data->blockhash); Local cb = data->callback.Get(isolate); 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 { Local jstx = NanNew(); ctx_to_jstx(ctx, blockhash, jstx); const unsigned argc = 2; Local argv[argc] = { Local::New(isolate, NanNull()), Local::New(isolate, jstx) }; TryCatch try_catch; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } delete data; delete req; } /** * BroadcastTx() * bitcoind.broadcastTx(tx, override_fees, own_only, callback) * Broadcast a raw transaction. This can be used to relay transaction received * or to broadcast one's own transaction. */ NAN_METHOD(BroadcastTx) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.Length() < 4 || !args[0]->IsObject() || !args[1]->IsBoolean() || !args[2]->IsBoolean() || !args[3]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.broadcastTx(tx, override_fees, own_only, callback)"); } Local jstx = Local::Cast(args[0]); Local callback = Local::Cast(args[3]); async_broadcast_tx_data *data = new async_broadcast_tx_data(); data->override_fees = args[1]->ToBoolean()->IsTrue(); data->own_only = args[2]->ToBoolean()->IsTrue(); data->err_msg = std::string(""); Eternal eternal(isolate, callback); data->callback = eternal; Eternal eternalObject(isolate, jstx); data->jstx = eternalObject; CTransaction ctx; jstx_to_ctx(jstx, ctx); data->ctx = ctx; uv_work_t *req = new uv_work_t(); req->data = data; int status = uv_queue_work(uv_default_loop(), req, async_broadcast_tx, (uv_after_work_cb)async_broadcast_tx_after); assert(status == 0); NanReturnValue(Undefined(isolate)); } static void async_broadcast_tx(uv_work_t *req) { async_broadcast_tx_data* data = static_cast(req->data); bool fOverrideFees = false; bool fOwnOnly = false; if (data->override_fees) { fOverrideFees = true; } if (data->own_only) { fOwnOnly = true; } CTransaction ctx = data->ctx; uint256 hashTx = ctx.GetHash(); bool fHave = false; CCoinsViewCache &view = *pcoinsTip; CCoins existingCoins; if (fOwnOnly) { fHave = view.GetCoins(hashTx, existingCoins); if (!fHave) { CValidationState state; if (!AcceptToMemoryPool(mempool, state, ctx, false, NULL, !fOverrideFees)) { data->err_msg = std::string("TX rejected"); return; } } } if (fHave) { if (existingCoins.nHeight < 1000000000) { data->err_msg = std::string("transaction already in block chain"); return; } } RelayTransaction(ctx); data->txid = hashTx.GetHex(); } static void async_broadcast_tx_after(uv_work_t *req) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); async_broadcast_tx_data* data = static_cast(req->data); Local cb = data->callback.Get(isolate); Local obj = data->jstx.Get(isolate); 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 { const unsigned argc = 3; Local argv[argc] = { Local::New(isolate, NanNull()), Local::New(isolate, NanNew(data->txid)), Local::New(isolate, obj) }; TryCatch try_catch; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } delete data; delete req; } /** * VerifyBlock() * bitcoindjs.verifyBlock(block) * This will verify the authenticity of a block (merkleRoot, etc) * using the internal bitcoind functions. */ NAN_METHOD(VerifyBlock) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.verifyBlock(block)"); } Local jsblock = Local::Cast(args[0]); String::Utf8Value block_hex_(jsblock->Get(NanNew("hex"))->ToString()); std::string block_hex = std::string(*block_hex_); CBlock cblock; jsblock_to_cblock(jsblock, cblock); CValidationState state; bool valid = CheckBlock(cblock, state); NanReturnValue(NanNew(valid)); } /** * VerifyTransaction() * bitcoindjs.verifyTransaction(tx) * This will verify a transaction, ensuring it is signed properly using the * internal bitcoind functions. */ NAN_METHOD(VerifyTransaction) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.verifyTransaction(tx)"); } Local jstx = Local::Cast(args[0]); String::Utf8Value tx_hex_(jstx->Get(NanNew("hex"))->ToString()); std::string tx_hex = std::string(*tx_hex_); CTransaction ctx; jstx_to_ctx(jstx, ctx); CValidationState state; bool valid = CheckTransaction(ctx, state); std::string reason; bool standard = IsStandardTx(ctx, reason); NanReturnValue(NanNew(valid && standard)); } /** * GetInfo() * bitcoindjs.getInfo() * Get miscellaneous information */ NAN_METHOD(GetInfo) { NanScope(); if (args.Length() > 0) { return NanThrowError( "Usage: bitcoindjs.getInfo()"); } Local obj = NanNew(); proxyType proxy; GetProxy(NET_IPV4, proxy); obj->Set(NanNew("version"), NanNew(CLIENT_VERSION)); obj->Set(NanNew("protocolversion"), NanNew(PROTOCOL_VERSION)); obj->Set(NanNew("blocks"), NanNew((int)chainActive.Height())->ToInt32()); obj->Set(NanNew("timeoffset"), NanNew(GetTimeOffset())); obj->Set(NanNew("connections"), NanNew((int)vNodes.size())->ToInt32()); obj->Set(NanNew("proxy"), NanNew(proxy.IsValid() ? proxy.ToStringIPPort() : std::string(""))); obj->Set(NanNew("difficulty"), NanNew((double)GetDifficulty())); obj->Set(NanNew("testnet"), NanNew(Params().NetworkIDString() == "test")); obj->Set(NanNew("relayfee"), NanNew(::minRelayTxFee.GetFeePerK())); // double obj->Set(NanNew("errors"), NanNew(GetWarnings("statusbar"))); NanReturnValue(obj); } /** * GetPeerInfo() * bitcoindjs.getPeerInfo() * Get peer information */ NAN_METHOD(GetPeerInfo) { NanScope(); if (args.Length() > 0) { return NanThrowError( "Usage: bitcoindjs.getPeerInfo()"); } Local array = NanNew(); int i = 0; vector vstats; vstats.clear(); LOCK(cs_vNodes); vstats.reserve(vNodes.size()); BOOST_FOREACH(CNode* pnode, vNodes) { CNodeStats stats; pnode->copyStats(stats); vstats.push_back(stats); } BOOST_FOREACH(const CNodeStats& stats, vstats) { Local obj = NanNew(); CNodeStateStats statestats; bool fStateStats = GetNodeStateStats(stats.nodeid, statestats); obj->Set(NanNew("id"), NanNew(stats.nodeid)); obj->Set(NanNew("addr"), NanNew(stats.addrName)); if (!(stats.addrLocal.empty())) { obj->Set(NanNew("addrlocal"), NanNew(stats.addrLocal)); } obj->Set(NanNew("services"), NanNew(strprintf("%016x", stats.nServices))); obj->Set(NanNew("lastsend"), NanNew(stats.nLastSend)); obj->Set(NanNew("lastrecv"), NanNew(stats.nLastRecv)); obj->Set(NanNew("bytessent"), NanNew(stats.nSendBytes)); obj->Set(NanNew("bytesrecv"), NanNew(stats.nRecvBytes)); obj->Set(NanNew("conntime"), NanNew(stats.nTimeConnected)); obj->Set(NanNew("pingtime"), NanNew(stats.dPingTime)); // double if (stats.dPingWait > 0.0) { obj->Set(NanNew("pingwait"), NanNew(stats.dPingWait)); // double } obj->Set(NanNew("version"), NanNew(stats.nVersion)); obj->Set(NanNew("subver"), NanNew(stats.cleanSubVer)); obj->Set(NanNew("inbound"), NanNew(stats.fInbound)); obj->Set(NanNew("startingheight"), NanNew(stats.nStartingHeight)); if (fStateStats) { obj->Set(NanNew("banscore"), NanNew(statestats.nMisbehavior)); obj->Set(NanNew("syncheight"), NanNew(statestats.nSyncHeight)->ToInt32()); obj->Set(NanNew("synced_headers"), NanNew(statestats.nSyncHeight)->ToInt32()); obj->Set(NanNew("synced_blocks"), NanNew(statestats.nCommonHeight)->ToInt32()); Local heights = NanNew(); int hi = 0; BOOST_FOREACH(int height, statestats.vHeightInFlight) { heights->Set(hi, NanNew(height)); hi++; } obj->Set(NanNew("inflight"), heights); } obj->Set(NanNew("whitelisted"), NanNew(stats.fWhitelisted)); // obj->Set(NanNew("relaytxes"), NanNew(stats.fRelayTxes)); array->Set(i, obj); i++; } NanReturnValue(array); } /** * GetAddresses() * bitcoindjs.getAddresses() * Get all addresses */ NAN_METHOD(GetAddresses) { NanScope(); if (args.Length() > 0) { return NanThrowError( "Usage: bitcoindjs.getAddresses()"); } Local array = NanNew(); int i = 0; std::vector vAddr = addrman.GetAddr(); BOOST_FOREACH(const CAddress& addr, vAddr) { Local obj = NanNew(); char nServices[21] = {0}; int written = snprintf(nServices, sizeof(nServices), "%020llu", (uint64_t)addr.nServices); assert(written == 20); obj->Set(NanNew("services"), NanNew((char *)nServices)); obj->Set(NanNew("time"), NanNew((unsigned int)addr.nTime)->ToUint32()); obj->Set(NanNew("last"), NanNew((int64_t)addr.nLastTry)); obj->Set(NanNew("ip"), NanNew((std::string)addr.ToStringIP())); obj->Set(NanNew("port"), NanNew((unsigned short)addr.GetPort())->ToUint32()); obj->Set(NanNew("address"), NanNew((std::string)addr.ToStringIPPort())); array->Set(i, obj); i++; } NanReturnValue(array); } /** * GetProgress() * bitcoindjs.getProgress(callback) * Get progress of blockchain download */ NAN_METHOD(GetProgress) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.getProgress(callback)"); } Local callback = Local::Cast(args[0]); async_block_data *data = new async_block_data(); data->err_msg = std::string(""); CBlockIndex *pindex = chainActive.Tip(); data->hash = pindex->GetBlockHash().GetHex(); data->height = -1; 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_progress, (uv_after_work_cb)async_get_progress_after); assert(status == 0); NanReturnValue(Undefined(isolate)); } static void async_get_progress(uv_work_t *req) { async_get_block(req); } static void async_get_progress_after(uv_work_t *req) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); async_block_data* data = static_cast(req->data); Local cb = data->callback.Get(isolate); 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 { const CBlock& cblock = data->cblock; CBlockIndex* cblock_index = data->cblock_index; Local jsblock = NanNew(); cblock_to_jsblock(cblock, cblock_index, jsblock, false); const CBlock& cgenesis = Params().GenesisBlock(); Local genesis = NanNew(); cblock_to_jsblock(cgenesis, NULL, genesis, false); // Get progress: double progress = Checkpoints::GuessVerificationProgress(cblock_index, false); // Get time left (assume last block was ten minutes ago): int64_t now = ((int64_t)time(NULL) - (10 * 60)); int64_t left = now - (progress * now); // Calculate tangible progress: unsigned int hours_behind = left / 60 / 60; unsigned int days_behind = left / 60 / 60 / 24; unsigned int percent = (unsigned int)(progress * 100.0); if (percent == 100 || left < 0) { hours_behind = 0; days_behind = 0; } Local result = NanNew(); result->Set(NanNew("blocks"), NanNew(cblock_index->nHeight)); result->Set(NanNew("connections"), NanNew((int)vNodes.size())->ToInt32()); result->Set(NanNew("genesisBlock"), genesis); result->Set(NanNew("currentBlock"), jsblock); result->Set(NanNew("hoursBehind"), NanNew(hours_behind)); result->Set(NanNew("daysBehind"), NanNew(days_behind)); result->Set(NanNew("percent"), NanNew(percent)); const unsigned argc = 2; Local argv[argc] = { Local::New(isolate, NanNull()), Local::New(isolate,result) }; TryCatch try_catch; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } delete data; delete req; } /** * GetMiningInfo() * bitcoindjs.getMiningInfo() * Get coin generation / mining information */ NAN_METHOD(GetMiningInfo) { NanScope(); Local obj = NanNew(); json_spirit::Array empty_params; obj->Set(NanNew("blocks"), NanNew((int)chainActive.Height())); obj->Set(NanNew("currentblocksize"), NanNew((uint64_t)nLastBlockSize)); obj->Set(NanNew("currentblocktx"), NanNew((uint64_t)nLastBlockTx)); obj->Set(NanNew("difficulty"), NanNew((double)GetDifficulty())); obj->Set(NanNew("errors"), NanNew(GetWarnings("statusbar"))); obj->Set(NanNew("genproclimit"), NanNew((int)GetArg("-genproclimit", -1))); obj->Set(NanNew("networkhashps"), NanNew( (int64_t)getnetworkhashps(empty_params, false).get_int64())); obj->Set(NanNew("pooledtx"), NanNew((uint64_t)mempool.size())); obj->Set(NanNew("testnet"), NanNew(Params().NetworkIDString() == "test")); obj->Set(NanNew("chain"), NanNew(Params().NetworkIDString())); NanReturnValue(obj); } /** * GetAddrTransactions() * bitcoind.getAddrTransactions(addr, callback) * Read any transaction from disk asynchronously. */ NAN_METHOD(GetAddrTransactions) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.Length() < 2 || (!args[0]->IsString() && !args[0]->IsObject()) || !args[1]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.getAddrTransactions(addr, callback)"); } std::string addr = ""; int64_t blockheight = -1; int64_t blocktime = -1; if (args[0]->IsString()) { String::Utf8Value addr_(args[0]->ToString()); addr = std::string(*addr_); } else if (args[0]->IsObject()) { Local options = Local::Cast(args[0]); if (options->Get(NanNew("address"))->IsString()) { String::Utf8Value s_(options->Get(NanNew("address"))->ToString()); addr = std::string(*s_); } if (options->Get(NanNew("addr"))->IsString()) { String::Utf8Value s_(options->Get(NanNew("addr"))->ToString()); addr = std::string(*s_); } if (options->Get(NanNew("height"))->IsNumber()) { blockheight = options->Get(NanNew("height"))->IntegerValue(); } if (options->Get(NanNew("blockheight"))->IsNumber()) { blockheight = options->Get(NanNew("blockheight"))->IntegerValue(); } if (options->Get(NanNew("time"))->IsNumber()) { blocktime = options->Get(NanNew("time"))->IntegerValue(); } if (options->Get(NanNew("blocktime"))->IsNumber()) { blocktime = options->Get(NanNew("blocktime"))->IntegerValue(); } } Local callback = Local::Cast(args[1]); async_addrtx_data *data = new async_addrtx_data(); data->err_msg = std::string(""); data->addr = addr; data->ctxs = NULL; data->blockheight = blockheight; data->blocktime = blocktime; 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_addrtx, (uv_after_work_cb)async_get_addrtx_after); assert(status == 0); NanReturnValue(Undefined(isolate)); } static void async_get_addrtx(uv_work_t *req) { async_addrtx_data* data = static_cast(req->data); if (data->addr.empty()) { data->err_msg = std::string("Invalid address."); return; } CBitcoinAddress address = CBitcoinAddress(data->addr); if (!address.IsValid()) { data->err_msg = std::string("Invalid address."); return; } #if !USE_LDB_ADDR CScript expected = GetScriptForDestination(address.Get()); int64_t i = 0; if (data->blockheight != -1) { i = data->blockheight; } int64_t height = chainActive.Height(); for (; i <= height; i++) { CBlockIndex* pblockindex = chainActive[i]; CBlock cblock; if (ReadBlockFromDisk(cblock, pblockindex)) { BOOST_FOREACH(const CTransaction& ctx, cblock.vtx) { // vin BOOST_FOREACH(const CTxIn& txin, ctx.vin) { if (txin.scriptSig.ToString() == expected.ToString()) { ctx_list *item = new ctx_list(); item->ctx = ctx; item->blockhash = cblock.GetHash(); if (data->ctxs == NULL) { data->ctxs = item; } else { data->ctxs->next = item; data->ctxs = item; } goto done; } } // vout for (unsigned int vo = 0; vo < ctx.vout.size(); vo++) { const CTxOut& txout = ctx.vout[vo]; const CScript& scriptPubKey = txout.scriptPubKey; txnouttype type; vector addresses; int nRequired; if (ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { BOOST_FOREACH(const CTxDestination& addr, addresses) { std::string str_addr = CBitcoinAddress(addr).ToString(); if (data->addr == str_addr) { ctx_list *item = new ctx_list(); item->ctx = ctx; item->blockhash = cblock.GetHash(); if (data->ctxs == NULL) { data->ctxs = item; } else { data->ctxs->next = item; data->ctxs = item; } goto done; } } } } } done: continue; } else { data->err_msg = std::string("Address not found."); break; } } return; #else ctx_list *ctxs = read_addr(data->addr, data->blockheight, data->blocktime); if (!ctxs->err_msg.empty()) { data->err_msg = ctxs->err_msg; return; } data->ctxs = ctxs; if (data->ctxs == NULL) { data->err_msg = std::string("Could not read database."); } #endif } static void async_get_addrtx_after(uv_work_t *req) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); async_addrtx_data* data = static_cast(req->data); Local cb = data->callback.Get(isolate); 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 { const unsigned argc = 2; Local result = NanNew(); Local tx = NanNew(); int i = 0; ctx_list *next; for (ctx_list *item = data->ctxs; item; item = next) { Local jstx = NanNew(); ctx_to_jstx(item->ctx, item->blockhash, jstx); tx->Set(i, jstx); i++; next = item->next; delete item; } result->Set(NanNew("address"), NanNew(data->addr)); result->Set(NanNew("tx"), tx); Local argv[argc] = { Local::New(isolate, NanNull()), Local::New(isolate, result) }; TryCatch try_catch; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } delete data; delete req; } /** * GetBestBlock() * bitcoindjs.getBestBlock() * Get the best block */ NAN_METHOD(GetBestBlock) { NanScope(); if (args.Length() < 0) { return NanThrowError( "Usage: bitcoindjs.getBestBlock()"); } uint256 hash = pcoinsTip->GetBestBlock(); NanReturnValue(NanNew(hash.GetHex())); } /** * GetChainHeight() * bitcoindjs.getChainHeight() * Get miscellaneous information */ NAN_METHOD(GetChainHeight) { NanScope(); if (args.Length() > 0) { return NanThrowError( "Usage: bitcoindjs.getChainHeight()"); } NanReturnValue(NanNew((int)chainActive.Height())->ToInt32()); } /** * GetBlockByTx() * bitcoindjs.getBlockByTx() * Get block by tx hash (requires -txindex or it's very slow) */ NAN_METHOD(GetBlockByTx) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.getBlockByTx(txid, callback)"); } async_block_tx_data *data = new async_block_tx_data(); uv_work_t *req = new uv_work_t(); req->data = data; String::Utf8Value txid_(args[0]->ToString()); std::string txid = std::string(*txid_); data->err_msg = std::string(""); data->txid = txid; Local callback = Local::Cast(args[1]); Eternal eternal(isolate, callback); data->callback = eternal; int status = uv_queue_work(uv_default_loop(), req, async_block_tx, (uv_after_work_cb)async_block_tx_after); assert(status == 0); NanReturnValue(Undefined(isolate)); } static void async_block_tx(uv_work_t *req) { async_block_tx_data* data = static_cast(req->data); #if USE_LDB_TX if (!g_txindex) { parse: #endif int64_t i = 0; int64_t height = chainActive.Height(); for (; i <= height; i++) { CBlockIndex* pblockindex = chainActive[i]; CBlock cblock; if (ReadBlockFromDisk(cblock, pblockindex)) { BOOST_FOREACH(const CTransaction& tx, cblock.vtx) { if (tx.GetHash().GetHex() == data->txid) { data->cblock = cblock; data->cblock_index = pblockindex; data->ctx = tx; return; } } } } data->err_msg = std::string("Block not found."); return; #if USE_LDB_TX } if (!get_block_by_tx(data->txid, data->cblock, &data->cblock_index, data->ctx)) { goto parse; } #endif } static void async_block_tx_after(uv_work_t *req) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); async_block_tx_data* data = static_cast(req->data); Local cb = data->callback.Get(isolate); 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 { const CBlock& cblock = data->cblock; CBlockIndex* cblock_index = data->cblock_index; const CTransaction& ctx = data->ctx; Local jsblock = NanNew(); cblock_to_jsblock(cblock, cblock_index, jsblock, false); Local jstx = NanNew(); ctx_to_jstx(ctx, cblock.GetHash(), jstx); const unsigned argc = 3; Local argv[argc] = { Local::New(isolate, NanNull()), Local::New(isolate, jsblock), Local::New(isolate, jstx) }; TryCatch try_catch; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } delete data; delete req; } /** * GetBlocksByTime() * bitcoindjs.getBlocksByTime() * Get block by tx hash (requires -txindex or it's very slow) */ NAN_METHOD(GetBlocksByTime) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.getBlocksByTime(options, callback)"); } async_block_time_data *data = new async_block_time_data(); data->gte = 0; data->lte = 0; uv_work_t *req = new uv_work_t(); req->data = data; Local options = Local::Cast(args[0]); if (options->Get(NanNew("gte"))->IsNumber()) { data->gte = options->Get(NanNew("gte"))->IntegerValue(); } if (options->Get(NanNew("lte"))->IsNumber()) { data->lte = options->Get(NanNew("lte"))->IntegerValue(); } if (options->Get(NanNew("limit"))->IsNumber()) { data->limit = options->Get(NanNew("limit"))->IntegerValue(); } data->err_msg = std::string(""); data->cblocks = NULL; Local callback = Local::Cast(args[1]); Eternal eternal(isolate, callback); data->callback = eternal; int status = uv_queue_work(uv_default_loop(), req, async_block_time, (uv_after_work_cb)async_block_time_after); assert(status == 0); NanReturnValue(Undefined(isolate)); } static void async_block_time(uv_work_t *req) { async_block_time_data* data = static_cast(req->data); if (!data->gte && !data->lte) { data->err_msg = std::string("gte and lte not found."); return; } int64_t i = 0; // XXX Slow: figure out how to ballpark the height based on gte and lte. int64_t height = chainActive.Height(); bool found_range = false; int64_t found = 0; for (; i <= height; i++) { CBlockIndex* pblockindex = chainActive[i]; CBlock cblock; if (ReadBlockFromDisk(cblock, pblockindex)) { uint32_t blocktime = cblock.GetBlockTime(); if (blocktime >= data->gte && blocktime <= data->lte) { found_range = true; cblocks_list *item = new cblocks_list(); item->cblock = cblock; item->cblock_index = pblockindex; if (data->cblocks == NULL) { data->cblocks = item; } else { data->cblocks->next = item; data->cblocks = item; } found++; if (found >= data->limit) return; } else { if (found_range) return; } } } data->err_msg = std::string("Block not found."); } static void async_block_time_after(uv_work_t *req) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); async_block_time_data* data = static_cast(req->data); Local cb = data->callback.Get(isolate); 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 { Local jsblocks = NanNew(); int i = 0; cblocks_list *next; for (cblocks_list *item = data->cblocks; item; item = next) { Local jsblock = NanNew(); cblock_to_jsblock(item->cblock, item->cblock_index, jsblock, false); jsblocks->Set(i, jsblock); i++; next = item->next; delete item; } const unsigned argc = 2; Local argv[argc] = { Local::New(isolate, NanNull()), Local::New(isolate, jsblocks) }; TryCatch try_catch; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } delete data; delete req; } /** * GetFromTx() * bitcoindjs.getFromTx() * Get all TXes beyond a txid */ NAN_METHOD(GetFromTx) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.getFromTx(txid, callback)"); } async_from_tx_data *data = new async_from_tx_data(); uv_work_t *req = new uv_work_t(); req->data = data; String::Utf8Value txid_(args[0]->ToString()); std::string txid = std::string(*txid_); data->txid = txid; data->ctxs = NULL; data->err_msg = std::string(""); Local callback = Local::Cast(args[1]); Eternal eternal(isolate, callback); data->callback = eternal; int status = uv_queue_work(uv_default_loop(), req, async_from_tx, (uv_after_work_cb)async_from_tx_after); assert(status == 0); NanReturnValue(Undefined(isolate)); } static void async_from_tx(uv_work_t *req) { async_from_tx_data* data = static_cast(req->data); uint256 txid(data->txid); bool found = false; int64_t i = 0; int64_t height = chainActive.Height(); for (; i <= height; i++) { CBlockIndex* pblockindex = chainActive[i]; CBlock cblock; if (ReadBlockFromDisk(cblock, pblockindex)) { BOOST_FOREACH(const CTransaction& ctx, cblock.vtx) { if (found || ctx.GetHash() == txid) { if (!found) found = true; ctx_list *item = new ctx_list(); item->ctx = ctx; item->blockhash = cblock.GetHash(); if (data->ctxs == NULL) { data->ctxs = item; } else { data->ctxs->next = item; data->ctxs = item; } } } } else { data->err_msg = std::string("TX not found."); break; } } } static void async_from_tx_after(uv_work_t *req) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); async_from_tx_data* data = static_cast(req->data); Local cb = data->callback.Get(isolate); 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 { const unsigned argc = 2; Local tx = NanNew(); int i = 0; ctx_list *next; for (ctx_list *item = data->ctxs; item; item = next) { Local jstx = NanNew(); ctx_to_jstx(item->ctx, item->blockhash, jstx); tx->Set(i, jstx); i++; next = item->next; delete item; } Local argv[argc] = { Local::New(isolate, NanNull()), Local::New(isolate, tx) }; TryCatch try_catch; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } delete data; delete req; } /** * GetLastFileIndex() * bitcoindjs.getLastFileIndex() * Get last file index */ NAN_METHOD(GetLastFileIndex) { NanScope(); if (args.Length() > 0) { return NanThrowError( "Usage: bitcoindjs.getLastFileIndex(callback)"); } CBlockIndex *pindex = chainActive.Tip(); CDiskBlockPos blockPos = pindex->GetBlockPos(); NanReturnValue(NanNew(blockPos.nFile)); } /** * GetBlockHex() * bitcoindjs.getBlockHex(callback) * This will return the hex value as well as hash of a javascript block object * (after being converted to a CBlock). */ NAN_METHOD(GetBlockHex) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.getBlockHex(block)"); } Local jsblock = Local::Cast(args[0]); CBlock cblock; jsblock_to_cblock(jsblock, cblock); Local data = NanNew(); data->Set(NanNew("hash"), NanNew(cblock.GetHash().GetHex())); CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION); ssBlock << cblock; std::string strHex = HexStr(ssBlock.begin(), ssBlock.end()); data->Set(NanNew("hex"), NanNew(strHex)); NanReturnValue(data); } /** * GetTxHex() * bitcoindjs.getTxHex(tx) * This will return the hex value and hash for any tx, converting a js tx * object to a CTransaction. */ NAN_METHOD(GetTxHex) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.getTxHex(tx)"); } Local jstx = Local::Cast(args[0]); CTransaction ctx; jstx_to_ctx(jstx, ctx); Local data = NanNew(); data->Set(NanNew("hash"), NanNew(ctx.GetHash().GetHex())); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << ctx; std::string strHex = HexStr(ssTx.begin(), ssTx.end()); data->Set(NanNew("hex"), NanNew(strHex)); NanReturnValue(data); } /** * BlockFromHex() * bitcoindjs.blockFromHex(hex) * Create a javascript block from a hex string. */ NAN_METHOD(BlockFromHex) { NanScope(); if (args.Length() < 1 || !args[0]->IsString()) { return NanThrowError( "Usage: bitcoindjs.blockFromHex(hex)"); } String::Utf8Value hex_string_(args[0]->ToString()); std::string hex_string = *hex_string_; CBlock cblock; CDataStream ssData(ParseHex(hex_string), SER_NETWORK, PROTOCOL_VERSION); try { ssData >> cblock; } catch (std::exception &e) { return NanThrowError("Bad Block decode"); } Local jsblock = NanNew(); cblock_to_jsblock(cblock, NULL, jsblock, false); NanReturnValue(jsblock); } /** * TxFromHex() * bitcoindjs.txFromHex(hex) * Create a javascript tx from a hex string. */ NAN_METHOD(TxFromHex) { NanScope(); if (args.Length() < 1 || !args[0]->IsString()) { return NanThrowError( "Usage: bitcoindjs.txFromHex(hex)"); } String::Utf8Value hex_string_(args[0]->ToString()); std::string hex_string = *hex_string_; CTransaction ctx; CDataStream ssData(ParseHex(hex_string), SER_NETWORK, PROTOCOL_VERSION); try { ssData >> ctx; } catch (std::exception &e) { return NanThrowError("Bad Block decode"); } Local jstx = NanNew(); ctx_to_jstx(ctx, 0, jstx); NanReturnValue(jstx); } /** * Linked List for queued packets */ typedef struct _poll_packets_list { CNode *pfrom; char *strCommand; CDataStream *vRecv; int64_t nTimeReceived; struct _poll_packets_list *next; } poll_packets_list; poll_packets_list *packets_queue_head = NULL; poll_packets_list *packets_queue_tail = NULL; boost::mutex poll_packets_mutex; /** * HookPackets() * bitcoind.hookPackets(callback) */ NAN_METHOD(HookPackets) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); Local obj = NanNew(); poll_packets_list *cur = NULL; poll_packets_list *next = NULL; int i = 0; poll_packets_mutex.lock(); for (cur = packets_queue_head; cur; cur = next) { CNode *pfrom = cur->pfrom; std::string strCommand(cur->strCommand); CDataStream vRecv = *cur->vRecv; int64_t nTimeReceived = cur->nTimeReceived; Local o = NanNew(); o->Set(NanNew("name"), NanNew(strCommand)); o->Set(NanNew("received"), NanNew((int64_t)nTimeReceived)); o->Set(NanNew("peerId"), NanNew(pfrom->id)); o->Set(NanNew("userAgent"), NanNew(pfrom->cleanSubVer)); if (strCommand == "version") { bool fRelayTxes = false; int nStartingHeight = 0; int cleanSubVer = 0; std::string strSubVer = pfrom->strSubVer; int nVersion = pfrom->nVersion; uint64_t nServices = pfrom->nServices; int64_t nTime; CAddress addrMe; CAddress addrFrom; uint64_t nNonce = 1; vRecv >> nVersion >> nServices >> nTime >> addrMe; if (pfrom->nVersion < MIN_PEER_PROTO_VERSION) { // disconnect from peers older than this proto version NanReturnValue(Undefined(isolate)); } if (nVersion == 10300) { nVersion = 300; } if (!vRecv.empty()) { vRecv >> addrFrom >> nNonce; } if (!vRecv.empty()) { vRecv >> LIMITED_STRING(strSubVer, 256); //cleanSubVer = SanitizeString(strSubVer); cleanSubVer = atoi(strSubVer.c_str()); } if (!vRecv.empty()) { vRecv >> nStartingHeight; } if (!vRecv.empty()) { fRelayTxes = false; } else { fRelayTxes = true; } // Disconnect if we connected to ourself if (nNonce == nLocalHostNonce && nNonce > 1) { NanReturnValue(obj); } o->Set(NanNew("receiveVersion"), NanNew(cleanSubVer)); o->Set(NanNew("version"), NanNew(nVersion)); o->Set(NanNew("height"), NanNew(nStartingHeight)); o->Set(NanNew("us"), NanNew(addrMe.ToString())); o->Set(NanNew("address"), NanNew(pfrom->addr.ToString())); o->Set(NanNew("relay"), NanNew(fRelayTxes)); } else if (pfrom->nVersion == 0) { // Must have a version message before anything else NanReturnValue(Undefined(isolate)); } else if (strCommand == "verack") { o->Set(NanNew("receiveVersion"), NanNew(min(pfrom->nVersion, PROTOCOL_VERSION))); } else if (strCommand == "addr") { vector vAddr; vRecv >> vAddr; // Don't want addr from older versions unless seeding if (pfrom->nVersion < CADDR_TIME_VERSION && addrman.size() > 1000) { NanReturnValue(obj); } // Bad address size if (vAddr.size() > 1000) { NanReturnValue(Undefined(isolate)); } Local array = NanNew(); int i = 0; // Get the new addresses int64_t nNow = GetAdjustedTime(); BOOST_FOREACH(CAddress& addr, vAddr) { boost::this_thread::interruption_point(); unsigned int nTime = addr.nTime; if (nTime <= 100000000 || nTime > nNow + 10 * 60) { nTime = nNow - 5 * 24 * 60 * 60; } bool fReachable = IsReachable(addr); Local obj = NanNew(); char nServices[21] = {0}; int written = snprintf(nServices, sizeof(nServices), "%020llu", (uint64_t)addr.nServices); assert(written == 20); obj->Set(NanNew("services"), NanNew((char *)nServices)); obj->Set(NanNew("time"), NanNew((unsigned int)nTime)->ToUint32()); obj->Set(NanNew("last"), NanNew((int64_t)addr.nLastTry)); obj->Set(NanNew("ip"), NanNew((std::string)addr.ToStringIP())); obj->Set(NanNew("port"), NanNew((unsigned short)addr.GetPort())->ToUint32()); obj->Set(NanNew("address"), NanNew((std::string)addr.ToStringIPPort())); obj->Set(NanNew("reachable"), NanNew((bool)fReachable)); array->Set(i, obj); i++; } o->Set(NanNew("addresses"), array); } else if (strCommand == "inv") { vector vInv; vRecv >> vInv; // Bad size if (vInv.size() > MAX_INV_SZ) { NanReturnValue(Undefined(isolate)); } LOCK(cs_main); Local array = NanNew(); int i = 0; for (unsigned int nInv = 0; nInv < vInv.size(); nInv++) { const CInv &inv = vInv[nInv]; boost::this_thread::interruption_point(); // Bad size if (pfrom->nSendSize > (SendBufferSize() * 2)) { NanReturnValue(Undefined(isolate)); } Local item = NanNew(); item->Set(NanNew("hash"), NanNew(inv.hash.GetHex())); item->Set(NanNew("type"), NanNew( inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK ? "block" : "tx")); if (inv.type == MSG_FILTERED_BLOCK) { item->Set(NanNew("filtered"), NanNew(true)); } else if (inv.type == MSG_BLOCK) { item->Set(NanNew("filtered"), NanNew(false)); } array->Set(i, item); i++; } o->Set(NanNew("items"), array); } else if (strCommand == "getdata") { vector vInv; vRecv >> vInv; // Bad size if (vInv.size() > MAX_INV_SZ) { NanReturnValue(Undefined(isolate)); } o->Set(NanNew("size"), NanNew(vInv.size())); if (vInv.size() > 0) { o->Set(NanNew("first"), NanNew(vInv[0].ToString())); } } else if (strCommand == "getblocks") { CBlockLocator locator; uint256 hashStop; vRecv >> locator >> hashStop; LOCK(cs_main); // Find the last block the caller has in the main chain CBlockIndex* pindex = FindForkInGlobalIndex(chainActive, locator); // Send the rest of the chain if (pindex) { pindex = chainActive.Next(pindex); } o->Set(NanNew("fromHeight"), NanNew(pindex ? pindex->nHeight : -1)); o->Set(NanNew("toHash"), NanNew( hashStop == uint256(0) ? "end" : hashStop.GetHex())); o->Set(NanNew("limit"), NanNew(500)); } else if (strCommand == "getheaders") { CBlockLocator locator; uint256 hashStop; vRecv >> locator >> hashStop; LOCK(cs_main); CBlockIndex* pindex = NULL; if (locator.IsNull()) { // If locator is null, return the hashStop block BlockMap::iterator mi = mapBlockIndex.find(hashStop); if (mi == mapBlockIndex.end()) { NanReturnValue(obj); } pindex = (*mi).second; } else { // Find the last block the caller has in the main chain pindex = FindForkInGlobalIndex(chainActive, locator); if (pindex) { pindex = chainActive.Next(pindex); } } o->Set(NanNew("fromHeight"), NanNew(pindex ? pindex->nHeight : -1)); o->Set(NanNew("toHash"), NanNew(hashStop.GetHex())); } else if (strCommand == "tx") { // XXX May be able to do prev_list asynchronously // XXX Potentially check for "reject" in original code CTransaction tx; vRecv >> tx; Local jstx = NanNew(); ctx_to_jstx(tx, 0, jstx); o->Set(NanNew("tx"), jstx); CNodeStats stats; pfrom->copyStats(stats); jstx->Set(NanNew("from"), NanNew(stats.addrName)); if (!stats.addrLocal.empty()) { jstx->Set(NanNew("fromlocal"), NanNew(stats.addrLocal)); } } else if (strCommand == "block" && !fImporting && !fReindex) { // XXX May be able to do prev_list asynchronously CBlock block; vRecv >> block; Local jsblock = NanNew(); cblock_to_jsblock(block, NULL, jsblock, true); o->Set(NanNew("block"), jsblock); CNodeStats stats; pfrom->copyStats(stats); jsblock->Set(NanNew("from"), NanNew(stats.addrName)); if (!stats.addrLocal.empty()) { jsblock->Set(NanNew("fromlocal"), NanNew(stats.addrLocal)); } } else if (strCommand == "getaddr") { ; // not much other information in getaddr as long as we know we got a getaddr } else if (strCommand == "mempool") { ; // not much other information in getaddr as long as we know we got a getaddr } else if (strCommand == "ping") { if (pfrom->nVersion > BIP0031_VERSION) { uint64_t nonce = 0; vRecv >> nonce; char sNonce[21] = {0}; int written = snprintf(sNonce, sizeof(sNonce), "%020llu", (uint64_t)nonce); assert(written == 20); o->Set(NanNew("nonce"), NanNew(sNonce)); } else { char sNonce[21] = {0}; int written = snprintf(sNonce, sizeof(sNonce), "%020llu", (uint64_t)0); assert(written == 20); o->Set(NanNew("nonce"), NanNew(sNonce)); } } else if (strCommand == "pong") { int64_t pingUsecEnd = nTimeReceived; uint64_t nonce = 0; size_t nAvail = vRecv.in_avail(); bool bPingFinished = false; std::string sProblem; if (nAvail >= sizeof(nonce)) { vRecv >> nonce; // Only process pong message if there is an outstanding ping (old ping without nonce should never pong) if (pfrom->nPingNonceSent != 0) { if (nonce == pfrom->nPingNonceSent) { // Matching pong received, this ping is no longer outstanding bPingFinished = true; int64_t pingUsecTime = pingUsecEnd - pfrom->nPingUsecStart; if (pingUsecTime > 0) { // Successful ping time measurement, replace previous ; } else { // This should never happen sProblem = "Timing mishap"; } } else { // Nonce mismatches are normal when pings are overlapping sProblem = "Nonce mismatch"; if (nonce == 0) { // This is most likely a bug in another implementation somewhere, cancel this ping bPingFinished = true; sProblem = "Nonce zero"; } } } else { sProblem = "Unsolicited pong without ping"; } } else { // This is most likely a bug in another implementation somewhere, cancel this ping bPingFinished = true; sProblem = "Short payload"; } char sNonce[21] = {0}; int written = snprintf(sNonce, sizeof(sNonce), "%020llu", (uint64_t)nonce); assert(written == 20); char sPingNonceSent[21] = {0}; written = snprintf(sPingNonceSent, sizeof(sPingNonceSent), "%020llu", (uint64_t)pfrom->nPingNonceSent); assert(written == 20); o->Set(NanNew("expected"), NanNew(sPingNonceSent)); o->Set(NanNew("received"), NanNew(sNonce)); o->Set(NanNew("bytes"), NanNew((unsigned int)nAvail)); if (!(sProblem.empty())) { o->Set(NanNew("problem"), NanNew(sProblem)); } if (bPingFinished) { o->Set(NanNew("finished"), NanNew(true)); } else { o->Set(NanNew("finished"), NanNew(false)); } } else if (strCommand == "alert") { CAlert alert; vRecv >> alert; uint256 alertHash = alert.GetHash(); o->Set(NanNew("hash"), NanNew(alertHash.GetHex())); if (pfrom->setKnown.count(alertHash) == 0) { if (alert.ProcessAlert()) { std::string vchMsg(alert.vchMsg.begin(), alert.vchMsg.end()); std::string vchSig(alert.vchSig.begin(), alert.vchSig.end()); o->Set(NanNew("message"), NanNew(vchMsg)); o->Set(NanNew("signature"), NanNew(vchSig)); o->Set(NanNew("misbehaving"), NanNew(false)); } else { // Small DoS penalty so peers that send us lots of // duplicate/expired/invalid-signature/whatever alerts // eventually get banned. // This isn't a Misbehaving(100) (immediate ban) because the // peer might be an older or different implementation with // a different signature key, etc. o->Set(NanNew("misbehaving"), NanNew(true)); } } } else if (strCommand == "filterload") { CBloomFilter filter; vRecv >> filter; if (!filter.IsWithinSizeConstraints()) { // There is no excuse for sending a too-large filter o->Set(NanNew("misbehaving"), NanNew(true)); } else { LOCK(pfrom->cs_filter); filter.UpdateEmptyFull(); o->Set(NanNew("misbehaving"), NanNew(false)); } } else if (strCommand == "filteradd") { vector vData; vRecv >> vData; // Nodes must NEVER send a data item > 520 bytes (the max size for a script data object, // and thus, the maximum size any matched object can have) in a filteradd message if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) { o->Set(NanNew("misbehaving"), NanNew(true)); } else { LOCK(pfrom->cs_filter); if (pfrom->pfilter) { o->Set(NanNew("misbehaving"), NanNew(false)); } else { o->Set(NanNew("misbehaving"), NanNew(true)); } } } else if (strCommand == "filterclear") { ; // nothing much to grab from this packet } else if (strCommand == "reject") { ; // nothing much to grab from this packet } else { o->Set(NanNew("unknown"), NanNew(true)); } // Update the last seen time for this node's address if (pfrom->fNetworkNode) { if (strCommand == "version" || strCommand == "addr" || strCommand == "inv" || strCommand == "getdata" || strCommand == "ping") { o->Set(NanNew("connected"), NanNew(true)); } } obj->Set(i, o); i++; if (cur == packets_queue_head) { packets_queue_head = NULL; } if (cur == packets_queue_tail) { packets_queue_tail = NULL; } next = cur->next; free(cur->strCommand); delete cur->vRecv; free(cur); } poll_packets_mutex.unlock(); NanReturnValue(obj); } static void hook_packets(void) { CNodeSignals& nodeSignals = GetNodeSignals(); nodeSignals.ProcessMessages.connect(&process_packets); } static void unhook_packets(void) { CNodeSignals& nodeSignals = GetNodeSignals(); nodeSignals.ProcessMessages.disconnect(&process_packets); } static bool process_packets(CNode* pfrom) { bool fOk = true; std::deque::iterator it = pfrom->vRecvMsg.begin(); while (!pfrom->fDisconnect && it != pfrom->vRecvMsg.end()) { // Don't bother if send buffer is too full to respond anyway if (pfrom->nSendSize >= SendBufferSize()) { break; } // get next message CNetMessage& msg = *it; // end, if an incomplete message is found if (!msg.complete()) { break; } // at this point, any failure means we can delete the current message it++; // Scan for message start if (memcmp(msg.hdr.pchMessageStart, Params().MessageStart(), MESSAGE_START_SIZE) != 0) { fOk = false; break; } // Read header CMessageHeader& hdr = msg.hdr; if (!hdr.IsValid()) { continue; } string strCommand = hdr.GetCommand(); // Message size unsigned int nMessageSize = hdr.nMessageSize; // Checksum CDataStream& vRecv = msg.vRecv; uint256 hash = Hash(vRecv.begin(), vRecv.begin() + nMessageSize); unsigned int nChecksum = 0; memcpy(&nChecksum, &hash, sizeof(nChecksum)); if (nChecksum != hdr.nChecksum) { continue; } // Process message process_packet(pfrom, strCommand, vRecv, msg.nTime); boost::this_thread::interruption_point(); break; } return fOk; } static bool process_packet(CNode* pfrom, string strCommand, CDataStream& vRecv, int64_t nTimeReceived) { poll_packets_mutex.lock(); poll_packets_list *cur = (poll_packets_list *)malloc(sizeof(poll_packets_list)); if (!packets_queue_head) { packets_queue_head = cur; packets_queue_tail = cur; } else { packets_queue_tail->next = cur; packets_queue_tail = cur; } cur->pfrom = pfrom; // NOTE: Copy the data stream. CDataStream *vRecv_ = new CDataStream(vRecv.begin(), vRecv.end(), vRecv.GetType(), vRecv.GetVersion()); cur->vRecv = vRecv_; cur->nTimeReceived = nTimeReceived; cur->strCommand = strdup(strCommand.c_str()); cur->next = NULL; poll_packets_mutex.unlock(); return true; } /** * Conversions * cblock_to_jsblock(cblock, cblock_index, jsblock, is_new) * ctx_to_jstx(ctx, blockhash, jstx) * jsblock_to_cblock(jsblock, cblock) * jstx_to_ctx(jstx, ctx) * These functions, only callable from C++, are used to convert javascript * blocks and tx objects to bitcoin block and tx objects (CBlocks and * CTransactions), and vice versa. */ // XXX Potentially add entire function's code. If there's a race // condition, the duplicate check will handle it. CBlockIndex * find_new_block_index(uint256 hash, uint256 hashPrevBlock, bool *is_allocated) { // Check for duplicate BlockMap::iterator it = mapBlockIndex.find(hash); if (it != mapBlockIndex.end()) { return it->second; } // Construct new block index object CBlockIndex* pindexNew = new CBlockIndex(); assert(pindexNew); BlockMap::iterator miPrev = mapBlockIndex.find(hashPrevBlock); if (miPrev != mapBlockIndex.end()) { pindexNew->pprev = (*miPrev).second; pindexNew->nHeight = pindexNew->pprev->nHeight + 1; } *is_allocated = true; return pindexNew; } static inline void cblock_to_jsblock(const CBlock& cblock, CBlockIndex* cblock_index, Local jsblock, bool is_new) { bool is_allocated = false; if (!cblock_index && is_new) { cblock_index = find_new_block_index(cblock.GetHash(), cblock.hashPrevBlock, &is_allocated); } uint256 blockhash = cblock.GetHash(); jsblock->Set(NanNew("hash"), NanNew(blockhash.GetHex())); jsblock->Set(NanNew("size"), NanNew((int)::GetSerializeSize(cblock, SER_NETWORK, PROTOCOL_VERSION))->ToInt32()); if (cblock_index) { jsblock->Set(NanNew("height"), NanNew(cblock_index->nHeight)); } // // Headers // jsblock->Set(NanNew("version"), NanNew((int32_t)cblock.nVersion)); jsblock->Set(NanNew("previousblockhash"), NanNew((std::string)cblock.hashPrevBlock.ToString())); jsblock->Set(NanNew("merkleroot"), NanNew((std::string)cblock.hashMerkleRoot.GetHex())); jsblock->Set(NanNew("time"), NanNew((uint32_t)cblock.GetBlockTime())->ToUint32()); jsblock->Set(NanNew("bits"), NanNew((uint32_t)cblock.nBits)->ToUint32()); jsblock->Set(NanNew("nonce"), NanNew((uint32_t)cblock.nNonce)->ToUint32()); if (cblock_index) { jsblock->Set(NanNew("difficulty"), NanNew(GetDifficulty(cblock_index))); jsblock->Set(NanNew("chainwork"), NanNew(cblock_index->nChainWork.GetHex())); } if (cblock_index) { CBlockIndex *pnext = chainActive.Next(cblock_index); if (pnext) { jsblock->Set(NanNew("nextblockhash"), NanNew(pnext->GetBlockHash().GetHex())); } } // Build merkle tree if (cblock.vMerkleTree.empty()) { cblock.BuildMerkleTree(); } Local merkle = NanNew(); int mi = 0; BOOST_FOREACH(uint256& hash, cblock.vMerkleTree) { merkle->Set(mi, NanNew(hash.ToString())); mi++; } jsblock->Set(NanNew("merkletree"), merkle); Local txs = NanNew(); int ti = 0; BOOST_FOREACH(const CTransaction& ctx, cblock.vtx) { Local jstx = NanNew(); ctx_to_jstx(ctx, blockhash, jstx); txs->Set(ti, jstx); ti++; } jsblock->Set(NanNew("tx"), txs); CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION); ssBlock << cblock; std::string strHex = HexStr(ssBlock.begin(), ssBlock.end()); jsblock->Set(NanNew("hex"), NanNew(strHex)); // Was it allocated in find_new_block_index(), or did it already exist? // (race condition here) if (is_allocated) { delete cblock_index; } } static int get_tx(uint256 txid, uint256& blockhash, CTransaction& ctx) { if (GetTransaction(txid, ctx, blockhash, true)) { return 1; } else if (blockhash != 0) { CBlock block; CBlockIndex* pblockindex = mapBlockIndex[blockhash]; if (ReadBlockFromDisk(block, pblockindex)) { BOOST_FOREACH(const CTransaction& tx, block.vtx) { if (tx.GetHash() == txid) { ctx = tx; blockhash = block.GetHash(); return -1; } } } } return 0; } static inline void ctx_to_jstx(const CTransaction& ctx, uint256 blockhash, Local jstx) { // Find block hash if it's in our wallet jstx->Set(NanNew("current_version"), NanNew((int)ctx.CURRENT_VERSION)->ToInt32()); jstx->Set(NanNew("txid"), NanNew(ctx.GetHash().GetHex())); jstx->Set(NanNew("version"), NanNew((int)ctx.nVersion)->ToInt32()); jstx->Set(NanNew("locktime"), NanNew((unsigned int)ctx.nLockTime)->ToUint32()); jstx->Set(NanNew("size"), NanNew((int)::GetSerializeSize(ctx, SER_NETWORK, PROTOCOL_VERSION))->ToInt32()); Local vin = NanNew(); int vi = 0; BOOST_FOREACH(const CTxIn& txin, ctx.vin) { Local in = NanNew(); if (ctx.IsCoinBase()) { in->Set(NanNew("coinbase"), NanNew(HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); } in->Set(NanNew("txid"), NanNew(txin.prevout.hash.GetHex())); in->Set(NanNew("vout"), NanNew((unsigned int)txin.prevout.n)->ToUint32()); Local o = NanNew(); o->Set(NanNew("asm"), NanNew(txin.scriptSig.ToString())); o->Set(NanNew("hex"), NanNew(HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); Local jsprev = NanNew(); CTransaction prev_tx; if (get_tx(txin.prevout.hash, blockhash, prev_tx)) { CTxDestination from; CTxOut prev_out = prev_tx.vout[txin.prevout.n]; ExtractDestination(prev_out.scriptPubKey, from); CBitcoinAddress addrFrom(from); jsprev->Set(NanNew("address"), NanNew(addrFrom.ToString())); jsprev->Set(NanNew("value"), NanNew((int64_t)prev_out.nValue)->ToInteger()); } else { const CTxOut& txout = ctx.vout[0]; const CScript& scriptPubKey = txout.scriptPubKey; txnouttype type; vector addresses; int nRequired; if (ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { // Unknowns usually have the same first addr as the first output: // https://blockexplorer.com/testnet/block/ const CTxDestination& addr = addresses[0]; jsprev->Set(NanNew("address"), NanNew(CBitcoinAddress(addr).ToString() + std::string("?"))); jsprev->Set(NanNew("value"), NanNew((int64_t)txout.nValue)->ToInteger()); } else { jsprev->Set(NanNew("address"), NanNew(std::string("Unknown"))); jsprev->Set(NanNew("value"), NanNew(0)); } } in->Set(NanNew("prev"), jsprev); in->Set(NanNew("scriptSig"), o); in->Set(NanNew("sequence"), NanNew((unsigned int)txin.nSequence)->ToUint32()); vin->Set(vi, in); vi++; } jstx->Set(NanNew("vin"), vin); Local vout = NanNew(); for (unsigned int vo = 0; vo < ctx.vout.size(); vo++) { const CTxOut& txout = ctx.vout[vo]; Local out = NanNew(); out->Set(NanNew("value"), NanNew((int64_t)txout.nValue)->ToInteger()); out->Set(NanNew("n"), NanNew((unsigned int)vo)->ToUint32()); Local o = NanNew(); { const CScript& scriptPubKey = txout.scriptPubKey; Local out = o; txnouttype type; vector addresses; int nRequired; out->Set(NanNew("asm"), NanNew(scriptPubKey.ToString())); out->Set(NanNew("hex"), NanNew(HexStr(scriptPubKey.begin(), scriptPubKey.end()))); if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { out->Set(NanNew("type"), NanNew(GetTxnOutputType(type))); } else { out->Set(NanNew("reqSigs"), NanNew((int)nRequired)->ToInt32()); out->Set(NanNew("type"), NanNew(GetTxnOutputType(type))); Local a = NanNew(); int ai = 0; BOOST_FOREACH(const CTxDestination& addr, addresses) { a->Set(ai, NanNew(CBitcoinAddress(addr).ToString())); ai++; } out->Set(NanNew("addresses"), a); } } out->Set(NanNew("scriptPubKey"), o); vout->Set(vo, out); } jstx->Set(NanNew("vout"), vout); if (blockhash != 0) { jstx->Set(NanNew("blockhash"), NanNew(blockhash.GetHex())); if (ctx.IsCoinBase()) { jstx->Set(NanNew("generated"), NanNew(true)); } if (mapBlockIndex.count(blockhash) > 0) { CBlockIndex* pindex = mapBlockIndex[blockhash]; jstx->Set(NanNew("confirmations"), NanNew(pindex->nHeight)); // XXX Not really index: jstx->Set(NanNew("blockindex"), NanNew(pindex->nHeight)); jstx->Set(NanNew("blockheight"), NanNew(pindex->nHeight)); jstx->Set(NanNew("blocktime"), NanNew((int64_t)pindex->GetBlockTime())->ToInteger()); jstx->Set(NanNew("time"), NanNew((int64_t)pindex->GetBlockTime())->ToInteger()); jstx->Set(NanNew("timereceived"), NanNew((int64_t)pindex->GetBlockTime())->ToInteger()); } else { jstx->Set(NanNew("confirmations"), NanNew(0)); // XXX Not really index: jstx->Set(NanNew("blockindex"), NanNew(-1)); jstx->Set(NanNew("blockheight"), NanNew(-1)); jstx->Set(NanNew("blocktime"), NanNew(0)); jstx->Set(NanNew("time"), NanNew(0)); jstx->Set(NanNew("timereceived"), NanNew(0)); } jstx->Set(NanNew("walletconflicts"), NanNew()); } else { jstx->Set(NanNew("blockhash"), NanNew(uint256(0).GetHex())); jstx->Set(NanNew("generated"), NanNew(false)); jstx->Set(NanNew("confirmations"), NanNew(-1)); // XXX Not really index: jstx->Set(NanNew("blockindex"), NanNew(-1)); jstx->Set(NanNew("blockheight"), NanNew(-1)); jstx->Set(NanNew("blocktime"), NanNew(0)); jstx->Set(NanNew("walletconflicts"), NanNew()); jstx->Set(NanNew("time"), NanNew(0)); jstx->Set(NanNew("timereceived"), NanNew(0)); } CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << ctx; std::string strHex = HexStr(ssTx.begin(), ssTx.end()); jstx->Set(NanNew("hex"), NanNew(strHex)); } static inline void jsblock_to_cblock(const Local jsblock, CBlock& cblock) { cblock.nVersion = (int32_t)jsblock->Get(NanNew("version"))->Int32Value(); if (jsblock->Get(NanNew("previousblockhash"))->IsString()) { String::Utf8Value hash__(jsblock->Get(NanNew("previousblockhash"))->ToString()); std::string hash_ = *hash__; uint256 hash(hash_); cblock.hashPrevBlock = (uint256)hash; } else { // genesis block cblock.hashPrevBlock = (uint256)uint256(0); } String::Utf8Value mhash__(jsblock->Get(NanNew("merkleroot"))->ToString()); std::string mhash_ = *mhash__; uint256 mhash(mhash_); cblock.hashMerkleRoot = (uint256)mhash; cblock.nTime = (uint32_t)jsblock->Get(NanNew("time"))->Uint32Value(); cblock.nBits = (uint32_t)jsblock->Get(NanNew("bits"))->Uint32Value(); cblock.nNonce = (uint32_t)jsblock->Get(NanNew("nonce"))->Uint32Value(); Local txs = Local::Cast(jsblock->Get(NanNew("tx"))); for (unsigned int ti = 0; ti < txs->Length(); ti++) { Local jstx = Local::Cast(txs->Get(ti)); CTransaction ctx; jstx_to_ctx(jstx, ctx); cblock.vtx.push_back(ctx); } if (cblock.vMerkleTree.empty()) { cblock.BuildMerkleTree(); } } // NOTE: For whatever reason when converting a jstx to a CTransaction via // setting CTransaction properties, the binary output of a jstx is not the same // as what went in. It is unknow why this occurs. For now we are are using a // workaround by carrying the original hex value on the object which is changed // when the tx is changed. static inline void jstx_to_ctx(const Local jstx, CTransaction& ctx_) { String::Utf8Value hex_string_(jstx->Get(NanNew("hex"))->ToString()); std::string hex_string = *hex_string_; CDataStream ssData(ParseHex(hex_string), SER_NETWORK, PROTOCOL_VERSION); try { ssData >> ctx_; } catch (std::exception &e) { NanThrowError("Bad TX decode"); return; } return; CMutableTransaction& ctx = (CMutableTransaction&)ctx_; // With v0.9.0 // ctx.nMinTxFee = (int64_t)jstx->Get(NanNew("mintxfee"))->IntegerValue(); // ctx.nMinRelayTxFee = (int64_t)jstx->Get(NanNew("minrelaytxfee"))->IntegerValue(); // ctx.CURRENT_VERSION = (unsigned int)jstx->Get(NanNew("current_version"))->Int32Value(); ctx.nVersion = (int)jstx->Get(NanNew("version"))->Int32Value(); Local vin = Local::Cast(jstx->Get(NanNew("vin"))); for (unsigned int vi = 0; vi < vin->Length(); vi++) { CTxIn txin; Local in = Local::Cast(vin->Get(vi)); String::Utf8Value phash__(in->Get(NanNew("txid"))->ToString()); std::string phash_ = *phash__; uint256 phash(phash_); txin.prevout.hash = phash; txin.prevout.n = (unsigned int)in->Get(NanNew("vout"))->Uint32Value(); std::string shash_; Local script_obj = Local::Cast(in->Get(NanNew("scriptSig"))); String::Utf8Value shash__(script_obj->Get(NanNew("hex"))->ToString()); shash_ = *shash__; std::vector shash(shash_.begin(), shash_.end()); CScript scriptSig(shash.begin(), shash.end()); txin.scriptSig = scriptSig; txin.nSequence = (unsigned int)in->Get(NanNew("sequence"))->Uint32Value(); ctx.vin.push_back(txin); } Local vout = Local::Cast(jstx->Get(NanNew("vout"))); for (unsigned int vo = 0; vo < vout->Length(); vo++) { CTxOut txout; Local out = Local::Cast(vout->Get(vo)); int64_t nValue = (int64_t)out->Get(NanNew("value"))->IntegerValue(); txout.nValue = nValue; Local script_obj = Local::Cast(out->Get(NanNew("scriptPubKey"))); String::Utf8Value phash__(script_obj->Get(NanNew("hex"))); std::string phash_ = *phash__; std::vector phash(phash_.begin(), phash_.end()); CScript scriptPubKey(phash.begin(), phash.end()); txout.scriptPubKey = scriptPubKey; ctx.vout.push_back(txout); } ctx.nLockTime = (unsigned int)jstx->Get(NanNew("locktime"))->Uint32Value(); } #if USE_LDB_ADDR /** LevelDB Parser DB: blocks/blk/revXXXXX.dat */ static ctx_list * read_addr(const std::string addr, const int64_t blockheight, const int64_t blocktime) { ctx_list *head = new ctx_list(); ctx_list *cur = NULL; head->err_msg = std::string(""); CScript expectedScriptSig = GetScriptForDestination(CBitcoinAddress(addr).Get()); leveldb::Iterator* pcursor = pblocktree->pdb->NewIterator(pblocktree->iteroptions); pcursor->SeekToFirst(); while (pcursor->Valid()) { boost::this_thread::interruption_point(); try { leveldb::Slice slKey = pcursor->key(); CDataStream ssKey(slKey.data(), slKey.data() + slKey.size(), SER_DISK, CLIENT_VERSION); char type; ssKey >> type; // Blockchain Index Structure: // http://bitcoin.stackexchange.com/questions/28168 // File info record structure // 'f' + 4-byte file number // Number of blocks stored in block file // Size of block file: blocks/blkXXXXX.dat // Size of undo file: blocks/revXXXXX.dat // Low and high heights of blocks stored in file // Low and high timestamps of blocks stored in file if (type == 'f') { goto next; } // Last block file number used structure // 'l' // 4-byte file number if (type == 'l') { goto next; } // Reindexing structure // 'R' // 1-byte Boolean (1 if reindexing) if (type == 'R') { goto next; } // Flags structure // 'F' + 1-byte flag name + flag name string // 1-byte Boolean (key may be `txindex` if transaction index is enabled) if (type == 'F') { goto next; } // Block Structure: // 'b' + 32-byte block hash // The block header // The block height // The number of transactions // The block validation state // The block file and pos // The undo file and pos if (type == 'b') { leveldb::Slice slValue = pcursor->value(); CDataStream ssValue(slValue.data(), slValue.data() + slValue.size(), SER_DISK, CLIENT_VERSION); uint256 blockhash; ssKey >> blockhash; // class CBlockIndex { // const uint256* phashBlock; // CBlockIndex* pprev; // CBlockIndex* pskip; // int nHeight; // int nFile; // unsigned int nDataPos; // unsigned int nUndoPos; // uint256 nChainWork; // unsigned int nTx; // unsigned int nChainTx; // unsigned int nStatus; // int nVersion; // uint256 hashMerkleRoot; // unsigned int nTime; // unsigned int nBits; // unsigned int nNonce; // uint32_t nSequenceId; // }; // class CDiskBlockIndex : public CBlockIndex { // uint256 hashPrev; // }; CDiskBlockIndex index; ssValue >> index; if (blocktime != -1 && index.GetBlockTime() < blocktime) { goto next; } // struct CDiskBlockPos { // int nFile; // unsigned int nPos; // }; CDiskBlockPos blockPos; blockPos.nFile = index.nFile; blockPos.nPos = index.nDataPos; CBlock cblock; if (!ReadBlockFromDisk(cblock, blockPos)) { goto next; } BOOST_FOREACH(const CTransaction& ctx, cblock.vtx) { BOOST_FOREACH(const CTxIn& txin, ctx.vin) { if (txin.scriptSig.ToString() != expectedScriptSig.ToString()) { continue; } if (cur == NULL) { head->ctx = ctx; head->blockhash = blockhash; head->next = NULL; cur = head; } else { ctx_list *item = new ctx_list(); item->ctx = ctx; item->blockhash = blockhash; item->next = NULL; cur->next = item; cur = item; } goto next; } for (unsigned int vo = 0; vo < ctx.vout.size(); vo++) { const CTxOut& txout = ctx.vout[vo]; const CScript& scriptPubKey = txout.scriptPubKey; int nRequired; txnouttype type; vector addresses; if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { continue; } BOOST_FOREACH(const CTxDestination& address, addresses) { if (CBitcoinAddress(address).ToString() != addr) { continue; } if (cur == NULL) { head->ctx = ctx; head->blockhash = blockhash; head->next = NULL; cur = head; } else { ctx_list *item = new ctx_list(); item->ctx = ctx; item->blockhash = blockhash; item->next = NULL; cur->next = item; cur = item; } goto next; } } } } // Transaction Structure: // 't' + 32-byte tx hash // Which block file the tx is stored in // Which offset in the block file the tx resides // The offset from the top of the block containing the tx if (type == 't') { leveldb::Slice slValue = pcursor->value(); CDataStream ssValue(slValue.data(), slValue.data() + slValue.size(), SER_DISK, CLIENT_VERSION); uint256 txid; ssKey >> txid; // struct CDiskBlockPos { // int nFile; // unsigned int nPos; // }; // struct CDiskTxPos : public CDiskBlockPos { // unsigned int nTxOffset; // }; CDiskTxPos txPos; ssValue >> txPos; CTransaction ctx; uint256 blockhash; if (!pblocktree->ReadTxIndex(txid, txPos)) { goto next; } CAutoFile file(OpenBlockFile(txPos, true), SER_DISK, CLIENT_VERSION); CBlockHeader header; try { file >> header; fseek(file.Get(), txPos.nTxOffset, SEEK_CUR); file >> ctx; } catch (std::exception &e) { goto error; } if (ctx.GetHash() != txid) { goto error; } blockhash = header.GetHash(); BOOST_FOREACH(const CTxIn& txin, ctx.vin) { if (txin.scriptSig.ToString() != expectedScriptSig.ToString()) { continue; } if (cur == NULL) { head->ctx = ctx; head->blockhash = blockhash; head->next = NULL; cur = head; } else { ctx_list *item = new ctx_list(); item->ctx = ctx; item->blockhash = blockhash; item->next = NULL; cur->next = item; cur = item; } goto next; } for (unsigned int vo = 0; vo < ctx.vout.size(); vo++) { const CTxOut& txout = ctx.vout[vo]; const CScript& scriptPubKey = txout.scriptPubKey; int nRequired; txnouttype type; vector addresses; if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { continue; } BOOST_FOREACH(const CTxDestination& address, addresses) { if (CBitcoinAddress(address).ToString() != addr) { continue; } if (cur == NULL) { head->ctx = ctx; head->blockhash = blockhash; head->next = NULL; cur = head; } else { ctx_list *item = new ctx_list(); item->ctx = ctx; item->blockhash = blockhash; item->next = NULL; cur->next = item; cur = item; } goto next; } } } next: pcursor->Next(); } catch (std::exception &e) { head->err_msg = std::string(e.what() + std::string(" : Deserialize error. Key: ") + pcursor->key().ToString()); delete pcursor; return head; } } delete pcursor; return head; error: head->err_msg = std::string("Deserialize Error."); delete pcursor; return head; } #endif /** LevelDB Parser DB: blocks/blk/revXXXXX.dat */ #if USE_LDB_TX static bool get_block_by_tx(const std::string itxid, CBlock& rcblock, CBlockIndex **rcblock_index, CTransaction& rctx) { const char *txkey = std::string(std::string("t") + itxid).c_str(); std::string slValue; //leveldb::Slice slValue; pblocktree->pdb->Get(leveldb::ReadOptions(), txkey, &slValue); CDataStream ssValue(slValue.begin(), slValue.end(), SER_DISK, CLIENT_VERSION); //CDataStream ssValue(slValue.data(), slValue.data() + slValue.size(), SER_DISK, CLIENT_VERSION); // Blockchain Index Structure: // http://bitcoin.stackexchange.com/questions/28168 // Transaction Structure: // 't' + 32-byte tx hash // Which block file the tx is stored in // Which offset in the block file the tx resides // The offset from the top of the block containing the tx // struct CDiskBlockPos { // int nFile; // unsigned int nPos; // }; // struct CDiskTxPos : public CDiskBlockPos { // unsigned int nTxOffset; // }; CDiskTxPos txPos; ssValue >> txPos; CTransaction ctx; uint256 blockhash; if (!pblocktree->ReadTxIndex(txid, txPos)) { goto error; } CAutoFile file(OpenBlockFile(txPos, true), SER_DISK, CLIENT_VERSION); CBlockHeader header; try { file >> header; fseek(file.Get(), txPos.nTxOffset, SEEK_CUR); file >> ctx; } catch (std::exception &e) { goto error; } if (ctx.GetHash() != txid) { goto error; } blockhash = header.GetHash(); CBlockIndex* pblockindex = mapBlockIndex[blockhash]; if (ReadBlockFromDisk(rcblock, pblockindex)) { *rcblock_index = pblockindex; rctx = ctx; return true; } return false; } #endif /** * 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 bitcoindjs. */ extern "C" void init(Handle target) { NanScope(); NODE_SET_METHOD(target, "start", StartBitcoind); NODE_SET_METHOD(target, "onBlocksReady", OnBlocksReady); NODE_SET_METHOD(target, "stop", StopBitcoind); NODE_SET_METHOD(target, "stopping", IsStopping); NODE_SET_METHOD(target, "stopped", IsStopped); NODE_SET_METHOD(target, "getBlock", GetBlock); NODE_SET_METHOD(target, "getTransaction", GetTransaction); NODE_SET_METHOD(target, "broadcastTx", BroadcastTx); NODE_SET_METHOD(target, "verifyBlock", VerifyBlock); NODE_SET_METHOD(target, "verifyTransaction", VerifyTransaction); NODE_SET_METHOD(target, "getInfo", GetInfo); NODE_SET_METHOD(target, "getPeerInfo", GetPeerInfo); NODE_SET_METHOD(target, "getAddresses", GetAddresses); NODE_SET_METHOD(target, "getProgress", GetProgress); NODE_SET_METHOD(target, "getMiningInfo", GetMiningInfo); NODE_SET_METHOD(target, "getAddrTransactions", GetAddrTransactions); NODE_SET_METHOD(target, "getBestBlock", GetBestBlock); NODE_SET_METHOD(target, "getChainHeight", GetChainHeight); NODE_SET_METHOD(target, "getBlockByTx", GetBlockByTx); NODE_SET_METHOD(target, "getBlocksByTime", GetBlocksByTime); NODE_SET_METHOD(target, "getFromTx", GetFromTx); NODE_SET_METHOD(target, "getLastFileIndex", GetLastFileIndex); NODE_SET_METHOD(target, "getBlockHex", GetBlockHex); NODE_SET_METHOD(target, "getTxHex", GetTxHex); NODE_SET_METHOD(target, "blockFromHex", BlockFromHex); NODE_SET_METHOD(target, "txFromHex", TxFromHex); NODE_SET_METHOD(target, "hookPackets", HookPackets); } NODE_MODULE(bitcoindjs, init)