/** * bitcoind.js - a binding for node.js which links to libbitcoind.so. * Copyright (c) 2014, BitPay (MIT License) * * bitcoindjs.cc: * A bitcoind node.js binding. */ #include "nan.h" #include "bitcoindjs.h" /** * LevelDB */ #include #include #include #include #include #include #include #include /** * secp256k1 */ #include /** * Bitcoin headers */ #include "config/bitcoin-config.h" #include "addrman.h" #include "alert.h" #include "allocators.h" #include "amount.h" #include "base58.h" #include "bloom.h" #include "bitcoind.h" #include "chain.h" #include "chainparams.h" #include "chainparamsbase.h" #include "checkpoints.h" #include "checkqueue.h" #include "clientversion.h" #include "coincontrol.h" #include "coins.h" #include "compat.h" #include "core/block.h" #include "core/transaction.h" #include "core_io.h" #include "crypter.h" #include "db.h" #include "hash.h" #include "init.h" #include "key.h" #include "keystore.h" #include "leveldbwrapper.h" #include "limitedmap.h" #include "main.h" #include "miner.h" #include "mruset.h" #include "netbase.h" #include "net.h" #include "noui.h" #include "pow.h" #include "protocol.h" #include "random.h" #include "rpcclient.h" #include "rpcprotocol.h" #include "rpcserver.h" #include "rpcwallet.h" #include "script/interpreter.h" #include "script/script.h" #include "script/sigcache.h" #include "script/sign.h" #include "script/standard.h" #include "script/script_error.h" #include "serialize.h" #include "sync.h" #include "threadsafety.h" #include "timedata.h" #include "tinyformat.h" #include "txdb.h" #include "txmempool.h" #include "ui_interface.h" #include "uint256.h" #include "util.h" #include "utilstrencodings.h" #include "utilmoneystr.h" #include "utiltime.h" #include "version.h" #include "wallet.h" #include "wallet_ismine.h" #include "walletdb.h" #include "compat/sanity.h" #include "json/json_spirit.h" #include "json/json_spirit_error_position.h" #include "json/json_spirit_reader.h" #include "json/json_spirit_reader_template.h" #include "json/json_spirit_stream_reader.h" #include "json/json_spirit_utils.h" #include "json/json_spirit_value.h" #include "json/json_spirit_writer.h" #include "json/json_spirit_writer_template.h" #include "crypto/common.h" #include "crypto/hmac_sha512.h" #include "crypto/sha1.h" #include "crypto/sha256.h" #include "crypto/sha512.h" #include "crypto/ripemd160.h" #include "univalue/univalue_escapes.h" #include "univalue/univalue.h" /** * Bitcoin System */ #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace boost; /** * 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; #ifdef ENABLE_WALLET extern std::string strWalletFile; extern CWallet *pwalletMain; #endif extern CFeeRate payTxFee; extern const std::string strMessageMagic; // extern map mapOrphanBlocks; 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); /** * Node.js System */ #include #include #include #include #include #include #include #include #include using namespace node; using namespace v8; // Need this because account names can be an empty string. #define EMPTY ("\\x01") // LevelDB options #define USE_LDB_ADDR 1 /** * Node.js Exposed Function Templates */ NAN_METHOD(StartBitcoind); NAN_METHOD(IsStopping); NAN_METHOD(IsStopped); NAN_METHOD(StopBitcoind); NAN_METHOD(GetBlock); NAN_METHOD(GetTx); NAN_METHOD(BroadcastTx); NAN_METHOD(VerifyBlock); NAN_METHOD(VerifyTransaction); NAN_METHOD(FillTransaction); NAN_METHOD(GetInfo); NAN_METHOD(GetPeerInfo); NAN_METHOD(GetAddresses); NAN_METHOD(GetProgress); NAN_METHOD(SetGenerate); NAN_METHOD(GetGenerate); NAN_METHOD(GetMiningInfo); NAN_METHOD(GetAddrTransactions); NAN_METHOD(GetBestBlock); NAN_METHOD(GetBlockHex); NAN_METHOD(GetTxHex); NAN_METHOD(BlockFromHex); NAN_METHOD(TxFromHex); NAN_METHOD(HookPackets); NAN_METHOD(WalletNewAddress); NAN_METHOD(WalletGetAccountAddress); NAN_METHOD(WalletSetAccount); NAN_METHOD(WalletGetAccount); NAN_METHOD(WalletGetRecipients); NAN_METHOD(WalletSetRecipient); NAN_METHOD(WalletRemoveRecipient); NAN_METHOD(WalletSendTo); NAN_METHOD(WalletSignMessage); NAN_METHOD(WalletVerifyMessage); NAN_METHOD(WalletGetBalance); NAN_METHOD(WalletCreateMultiSigAddress); NAN_METHOD(WalletGetUnconfirmedBalance); NAN_METHOD(WalletSendFrom); NAN_METHOD(WalletMove); NAN_METHOD(WalletListTransactions); NAN_METHOD(WalletReceivedByAddress); NAN_METHOD(WalletListAccounts); NAN_METHOD(WalletGetTransaction); NAN_METHOD(WalletBackup); NAN_METHOD(WalletPassphrase); NAN_METHOD(WalletPassphraseChange); NAN_METHOD(WalletLock); NAN_METHOD(WalletEncrypt); NAN_METHOD(WalletEncrypted); NAN_METHOD(WalletKeyPoolRefill); NAN_METHOD(WalletSetTxFee); NAN_METHOD(WalletDumpKey); NAN_METHOD(WalletImportKey); NAN_METHOD(WalletDumpWallet); NAN_METHOD(WalletImportWallet); NAN_METHOD(WalletChangeLabel); NAN_METHOD(WalletDeleteAccount); NAN_METHOD(WalletIsMine); NAN_METHOD(WalletRescan); /** * 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_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_wallet_sendto(uv_work_t *req); static void async_wallet_sendto_after(uv_work_t *req); static void async_wallet_sendfrom(uv_work_t *req); static void async_wallet_sendfrom_after(uv_work_t *req); static void async_import_key(uv_work_t *req); static void async_import_key_after(uv_work_t *req); static void async_dump_wallet(uv_work_t *req); static void async_dump_wallet_after(uv_work_t *req); static void async_import_wallet(uv_work_t *req); static void async_import_wallet_after(uv_work_t *req); static void async_rescan(uv_work_t *req); static void async_rescan_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 block_hash, 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 void AcentryToJSON_V8(const CAccountingEntry& acentry, const string& strAccount, Local& ret, int *a_count); static void WalletTxToJSON_V8(const CWalletTx& wtx, Local& entry); static void MaybePushAddress_V8(Local& entry, const CTxDestination &dest); static void ListTransactions_V8(const CWalletTx& wtx, const string& strAccount, int nMinDepth, bool fLong, Local ret, const isminefilter& filter, int *a_count); static int64_t SatoshiFromAmount(const CAmount& amount); 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; /** * Private Structs * Used for async functions and necessary linked lists at points. */ #if 0 typedef struct _prev_list { std::string addr; int64_t val; struct _prev_list *next; } prev_list; #endif /** * 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; Persistent callback; }; /** * async_block_data */ struct async_block_data { std::string err_msg; std::string hash; int64_t height; CBlock cblock; CBlockIndex* cblock_index; #if 0 prev_list *inputs; #endif Persistent callback; }; /** * async_tx_data */ struct async_tx_data { std::string err_msg; std::string txHash; std::string blockHash; CTransaction ctx; #if 0 prev_list *inputs; #endif Persistent callback; }; /** * async_addrtx_data */ typedef struct _ctx_list { CTransaction ctx; uint256 blockhash; struct _ctx_list *next; } ctx_list; struct async_addrtx_data { std::string err_msg; std::string addr; ctx_list *ctxs; Persistent callback; }; /** * async_broadcast_tx_data */ struct async_broadcast_tx_data { std::string err_msg; Persistent jstx; CTransaction ctx; std::string tx_hash; bool override_fees; bool own_only; Persistent callback; }; /** * async_wallet_sendto_data */ struct async_wallet_sendto_data { std::string err_msg; std::string tx_hash; std::string address; int64_t nAmount; CWalletTx wtx; Persistent callback; }; /** * async_wallet_sendfrom_data */ struct async_wallet_sendfrom_data { std::string err_msg; std::string tx_hash; std::string address; int64_t nAmount; int nMinDepth; CWalletTx wtx; Persistent callback; }; /** * async_import_key_data */ struct async_import_key_data { std::string err_msg; bool fRescan; Persistent callback; }; /** * async_import_wallet_data */ struct async_import_wallet_data { std::string err_msg; std::string path; Persistent callback; }; /** * async_dump_wallet_data */ struct async_dump_wallet_data { std::string err_msg; std::string path; Persistent callback; }; /** * async_rescan_data */ struct async_rescan_data { std::string err_msg; Persistent callback; }; /** * Read Raw DB */ #if USE_LDB_ADDR static ctx_list * read_addr(const std::string addr); #endif /** * Functions */ /** * StartBitcoind() * bitcoind.start(callback) * Start the bitcoind node with AppInit2() on a separate thread. */ NAN_METHOD(StartBitcoind) { NanScope(); Local callback; std::string datadir = std::string(""); bool rpc = false; bool testnet = 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(); } 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->testnet = testnet; data->rpc = rpc; data->callback = Persistent::New(callback); 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()); } /** * 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; start_node(); data->result = std::string("start_node(): bitcoind opened."); } /** * async_start_node_after() * Execute our callback. */ static void async_start_node_after(uv_work_t *req) { NanScope(); async_node_data *data = static_cast(req->data); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } else { const unsigned argc = 2; Local argv[argc] = { Local::New(Null()), Local::New(NanNew(data->result)) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); delete data; delete req; } /** * start_node(void) * Start AppInit2() on a separate thread, wait for * pwalletMain instantiation (and signal() calls). * 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(); (boost::thread *)new boost::thread(boost::bind(&start_node_thread)); // Wait for wallet to be instantiated. This also avoids // a race condition with signals not being set up. while (!pwalletMain) { useconds_t usec = 100 * 1000; usleep(usec); } // 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 (boost::thread *)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((3 + 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 { fprintf(stderr, "bitcoind.js: Bad -datadir value."); } } if (g_rpc) { argv[argc] = (char *)"-server"; argc++; } if (g_testnet) { argv[argc] = (char *)"-testnet"; argc++; } argv[argc] = NULL; bool fRet = false; try { ParseParameters((const int)argc, (const char **)argv); if (!boost::filesystem::is_directory(GetDataDir(false))) { fprintf(stderr, "bitcoind.js: Specified data directory \"%s\" does not exist.\n", mapArgs["-datadir"].c_str()); return; } try { ReadConfigFile(mapArgs, mapMultiArgs); } catch(std::exception &e) { fprintf(stderr, "bitcoind.js: Error reading configuration file: %s\n", e.what()); return; } // mapArgs["-datadir"] = g_data_dir; // mapArgs["-server"] = g_rpc ? "1" : "0"; // mapArgs["-testnet"] = g_testnet ? "1" : "0"; if (!SelectParamsFromCommandLine()) { fprintf(stderr, "bitcoind.js: Invalid combination of -regtest and -testnet.\n"); return; } // XXX Potentially add an option for this. // This is probably a good idea if people try to start bitcoind while // running a program which links to libbitcoind.so, but disable it for now. CreatePidFile(GetPidFile(), getpid()); detectShutdownThread = new boost::thread( boost::bind(&DetectShutdownThread, &threadGroup)); fRet = AppInit2(threadGroup); } catch (std::exception& e) { fprintf(stderr, "bitcoind.js: AppInit(): std::exception"); } catch (...) { fprintf(stderr, "bitcoind.js: AppInit(): other exception"); } 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) { NanScope(); 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(""); data->callback = Persistent::New(callback); 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()); } /** * 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("stop_node(): bitcoind shutdown."); } /** * async_stop_node_after() * Execute our callback. */ static void async_stop_node_after(uv_work_t *req) { NanScope(); async_node_data* data = static_cast(req->data); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } else { const unsigned argc = 2; Local argv[argc] = { Local::New(Null()), Local::New(NanNew(data->result)) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); 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) { NanScope(); 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]); data->callback = Persistent::New(callback); 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()); } 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("get_block(): failed."); } return; } std::string strHash = data->hash; uint256 hash(strHash); CBlock cblock; CBlockIndex* pblockindex = mapBlockIndex[hash]; if (ReadBlockFromDisk(cblock, pblockindex)) { data->cblock = cblock; data->cblock_index = pblockindex; #if 0 BOOST_FOREACH(const CTransaction& ctx, cblock.vtx) { BOOST_FOREACH(const CTxIn& txin, ctx.vin) { CTransaction prev_tx; if (GetTransaction(txin.prevout.hash, prev_tx, block_hash, true)) { CTxDestination from; CTxOut prev_out = prev_tx.vout[txin.prevout.n]; ExtractDestination(prev_out.scriptPubKey, from); CBitcoinAddress addrFrom(from); std::string addr = addrFrom.ToString(); int64_t val = (int64_t)prev_out.nValue; prev_list *cur = new prev_list(); cur->addr = addr; cur->val = val; if (data->inputs == NULL) { data->inputs = cur; } else { data->inputs->next = cur; data->inputs = cur; } } } } #endif } else { data->err_msg = std::string("get_block(): failed."); } } static void async_get_block_after(uv_work_t *req) { NanScope(); async_block_data* data = static_cast(req->data); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->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); #if 0 prev_list *head = data->inputs; prev_list *cur = data->inputs; prev_list *next; for (int i = 0; i < jsblock->tx->ength(); i++) { for (int j = 0; j < jstx->vin->Length(); j++) { next = cur->next; jsblock. Local jsprev = NanNew(); jsprev->Set(NanNew("address"), NanNew(cur->addr)); jsprev->Set(NanNew("value"), NanNew(cur->val)); jsblock->tx->Get(i)->vin->Get(j)->Set(NanNew("prev"), jsprev); delete cur; cur = next; } } #endif const unsigned argc = 2; Local argv[argc] = { Local::New(Null()), Local::New(jsblock) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); delete data; delete req; } /** * GetTx() * bitcoind.getTx(txHash, [blockHash], callback) * Read any transaction from disk asynchronously. */ NAN_METHOD(GetTx) { NanScope(); if (args.Length() < 3 || !args[0]->IsString() || !args[1]->IsString() || !args[2]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.getTx(txHash, [blockHash], callback)"); } String::Utf8Value txHash_(args[0]->ToString()); String::Utf8Value blockHash_(args[1]->ToString()); Local callback = Local::Cast(args[2]); std::string txHash = std::string(*txHash_); std::string blockHash = std::string(*blockHash_); if (blockHash == "") { blockHash = std::string( "0000000000000000000000000000000000000000000000000000000000000000"); } async_tx_data *data = new async_tx_data(); data->err_msg = std::string(""); data->txHash = txHash; data->blockHash = blockHash; data->callback = Persistent::New(callback); 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()); } static void async_get_tx(uv_work_t *req) { async_tx_data* data = static_cast(req->data); uint256 hash(data->txHash); uint256 block_hash(data->blockHash); CTransaction ctx; if (GetTransaction(hash, ctx, block_hash, true)) { data->ctx = ctx; goto collect_prev; } else { if (data->blockHash != "0000000000000000000000000000000000000000000000000000000000000000") { CBlock block; CBlockIndex* pblockindex = mapBlockIndex[block_hash]; if (ReadBlockFromDisk(block, pblockindex)) { BOOST_FOREACH(const CTransaction &tx, block.vtx) { if (tx.GetHash() == hash) { data->ctx = tx; goto collect_prev; } } } } data->err_msg = std::string("get_tx(): failed."); } return; collect_prev: return; #if 0 BOOST_FOREACH(const CTxIn& txin, ctx.vin) { CTransaction prev_tx; if (GetTransaction(txin.prevout.hash, prev_tx, block_hash, true)) { CTxDestination from; CTxOut prev_out = prev_tx.vout[txin.prevout.n]; ExtractDestination(prev_out.scriptPubKey, from); CBitcoinAddress addrFrom(from); std::string addr = addrFrom.ToString(); int64_t val = (int64_t)prev_out.nValue; prev_list *cur = new prev_list(); cur->addr = addr; cur->val = val; if (data->inputs == NULL) { data->inputs = cur; } else { data->inputs->next = cur; data->inputs = cur; } } } #endif } static void async_get_tx_after(uv_work_t *req) { NanScope(); async_tx_data* data = static_cast(req->data); std::string txHash = data->txHash; std::string blockHash = data->blockHash; CTransaction ctx = data->ctx; uint256 hash(txHash); uint256 block_hash(blockHash); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } else { Local jstx = NanNew(); ctx_to_jstx(ctx, block_hash, jstx); // XXX Do this for GetBlock, and PacketHook #if 0 prev_list *head = data->inputs; prev_list *cur = data->inputs; prev_list *next; for (int i = 0; i < jstx->vin->Length(); i++) { next = cur->next; Local jsprev = NanNew(); jsprev->Set(NanNew("address"), NanNew(cur->addr)); jsprev->Set(NanNew("value"), NanNew(cur->val)); jstx->vin->Get(i)->Set(NanNew("prev"), jsprev); delete cur; cur = next; } #endif const unsigned argc = 2; Local argv[argc] = { Local::New(Null()), Local::New(jstx) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); 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) { NanScope(); 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(""); data->callback = Persistent::New(callback); data->jstx = Persistent::New(jstx); 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()); } 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; } } else { // With v0.9.0 // SyncWithWallets(hashTx, ctx, NULL); } RelayTransaction(ctx); data->tx_hash = hashTx.GetHex(); } static void async_broadcast_tx_after(uv_work_t *req) { NanScope(); async_broadcast_tx_data* data = static_cast(req->data); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } else { const unsigned argc = 3; Local argv[argc] = { Local::New(Null()), Local::New(NanNew(data->tx_hash)), Local::New(data->jstx) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); 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)); } /** * FillTransaction() * bitcoindjs.fillTransaction(tx, options); * This will fill a javascript transaction object with the proper available * unpsent outputs as inputs and sign them using internal bitcoind functions. */ NAN_METHOD(FillTransaction) { NanScope(); if (args.Length() < 2 || !args[0]->IsObject() || !args[1]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.fillTransaction(tx, options)"); } 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); // Get total value of outputs // Get the scriptPubKey of the first output (presumably our destination) int64_t nValue = 0; for (unsigned int vo = 0; vo < ctx.vout.size(); vo++) { const CTxOut& txout = ctx.vout[vo]; int64_t value = txout.nValue; const CScript& scriptPubKey = txout.scriptPubKey; nValue += value; } if (nValue <= 0) { return NanThrowError("Invalid amount"); } // With v0.9.0: // if (nValue + nTransactionFee > pwalletMain->GetBalance()) // if (nValue + payTxFee > pwalletMain->GetBalance()) // return NanThrowError("Insufficient funds"); if (nValue > pwalletMain->GetBalance()) { return NanThrowError("Insufficient funds"); } // With v0.9.0: // int64_t nFeeRet = nTransactionFee; int64_t nFeeRet = 1000; // int64_t nFeeRet = CFeeRate(nAmount, 1000); if (pwalletMain->IsLocked()) { return NanThrowError("Wallet locked, unable to create transaction!"); } CCoinControl* coinControl = new CCoinControl(); int64_t nTotalValue = nValue + nFeeRet; set > setCoins; int64_t nValueIn = 0; if (!pwalletMain->SelectCoins(nTotalValue, setCoins, nValueIn, coinControl)) { return NanThrowError("Insufficient funds"); } // Fill vin BOOST_FOREACH(const PAIRTYPE(const CWalletTx*, unsigned int)& coin, setCoins) { ctx.vin.push_back(CTxIn(coin.first->GetHash(), coin.second)); } // Sign int nIn = 0; BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) { if (!SignSignature( (const CKeyStore&)*pwalletMain, (const CTransaction&)*coin.first, (CMutableTransaction&)ctx, nIn++ )) { return NanThrowError("Signing transaction failed"); } } // Turn our CTransaction into a javascript Transaction Local new_jstx = NanNew(); ctx_to_jstx(ctx, 0, new_jstx); NanReturnValue(new_jstx); } /** * 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)); #ifdef ENABLE_WALLET if (pwalletMain) { obj->Set(NanNew("walletversion"), NanNew(pwalletMain->GetVersion())); obj->Set(NanNew("balance"), NanNew(pwalletMain->GetBalance())); // double } #endif 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")); #ifdef ENABLE_WALLET if (pwalletMain) { obj->Set(NanNew("keypoololdest"), NanNew(pwalletMain->GetOldestKeyPoolTime())); obj->Set(NanNew("keypoolsize"), NanNew((int)pwalletMain->GetKeyPoolSize())->ToInt32()); } if (pwalletMain && pwalletMain->IsCrypted()) { obj->Set(NanNew("unlocked_until"), NanNew(nWalletUnlockTime)); } obj->Set(NanNew("paytxfee"), NanNew(payTxFee.GetFeePerK())); // double #endif 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), "%020lu", (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) { NanScope(); 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; data->callback = Persistent::New(callback); 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()); } static void async_get_progress(uv_work_t *req) { async_get_block(req); } static void async_get_progress_after(uv_work_t *req) { NanScope(); async_block_data* data = static_cast(req->data); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->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)); // result->Set(NanNew("orphans"), // NanNew(mapOrphanBlocks.size())); const unsigned argc = 2; Local argv[argc] = { Local::New(Null()), Local::New(result) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); delete data; delete req; } /** * SetGenerate() * bitcoindjs.setGenerate(options) * Set coin generation / mining */ NAN_METHOD(SetGenerate) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.setGenerate(options)"); } Local options = Local::Cast(args[0]); if (pwalletMain == NULL) { return NanThrowError("Method not found (disabled)"); } bool fGenerate = true; if (options->Get(NanNew("generate"))->IsBoolean()) { fGenerate = options->Get(NanNew("generate"))->ToBoolean()->IsTrue(); } int nGenProcLimit = -1; if (options->Get(NanNew("limit"))->IsNumber()) { nGenProcLimit = (int)options->Get(NanNew("limit"))->IntegerValue(); if (nGenProcLimit == 0) { fGenerate = false; } } // -regtest mode: don't return until nGenProcLimit blocks are generated if (fGenerate && Params().MineBlocksOnDemand()) { int nHeightStart = 0; int nHeightEnd = 0; int nHeight = 0; int nGenerate = (nGenProcLimit > 0 ? nGenProcLimit : 1); { // Don't keep cs_main locked LOCK(cs_main); nHeightStart = chainActive.Height(); nHeight = nHeightStart; nHeightEnd = nHeightStart+nGenerate; } int nHeightLast = -1; while (nHeight < nHeightEnd) { if (nHeightLast != nHeight) { nHeightLast = nHeight; GenerateBitcoins(fGenerate, pwalletMain, 1); } MilliSleep(1); { // Don't keep cs_main locked LOCK(cs_main); nHeight = chainActive.Height(); } } } else { // Not -regtest: start generate thread, return immediately mapArgs["-gen"] = (fGenerate ? "1" : "0"); mapArgs["-genproclimit"] = itostr(nGenProcLimit); GenerateBitcoins(fGenerate, pwalletMain, nGenProcLimit); } NanReturnValue(True()); } /** * GetGenerate() * bitcoindjs.GetGenerate() * Get coin generation / mining */ NAN_METHOD(GetGenerate) { NanScope(); bool generate = GetBoolArg("-gen", false); NanReturnValue(NanNew(generate)); } /** * GetMiningInfo() * bitcoindjs.getMiningInfo() * Get coin generation / mining information */ NAN_METHOD(GetMiningInfo) { NanScope(); Local obj = NanNew(); json_spirit::Array empty_params; // (json_spirit::Value)GetNetworkHashPS(120 /* blocks=-1 */, -1 /* height=x */); // (int64_t)GetNetworkHashPS(120 /* blocks=-1 */, -1 /* height=x */).get_int64(); // (int64_t)getnetworkhashps(empty_params, false).get_int64(); // (json_spirit::Value)getgenerate(empty_params, false); // (bool)getgenerate(empty_params, false).get_bool(); // (bool)GetBoolArg("-gen", false); // (json_spirit::Value)gethashespersec(empty_params, false); // (int64_t)gethashespersec(empty_params, false).get_int64(); // int64_t hashespersec = 0; // if (GetTimeMillis() - nHPSTimerStart > 8000) { // hashespersec = (int64_t)0; // } else { // hashespersec = (int64_t)dHashesPerSec; // } 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))); // If lookup is -1, then use blocks since last difficulty change. // If lookup is larger than chain, then set it to chain length. // ~/bitcoin/src/json/json_spirit_value.h // ~/bitcoin/src/rpcmining.cpp 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())); #ifdef ENABLE_WALLET obj->Set(NanNew("generate"), NanNew( (bool)getgenerate(empty_params, false).get_bool())); obj->Set(NanNew("hashespersec"), NanNew( (int64_t)gethashespersec(empty_params, false).get_int64())); #endif NanReturnValue(obj); } /** * GetAddrTransactions() * bitcoind.getAddrTransactions(addr, callback) * Read any transaction from disk asynchronously. */ NAN_METHOD(GetAddrTransactions) { NanScope(); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.getAddrTransactions(addr, callback)"); } String::Utf8Value addr_(args[0]->ToString()); Local callback = Local::Cast(args[1]); Persistent cb; cb = Persistent::New(callback); std::string addr = std::string(*addr_); async_addrtx_data *data = new async_addrtx_data(); data->err_msg = std::string(""); data->addr = addr; data->ctxs = NULL; data->callback = Persistent::New(callback); 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()); } static void async_get_addrtx(uv_work_t *req) { async_addrtx_data* data = static_cast(req->data); 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; // Check the last 20,000 blocks int64_t i = chainActive.Height() - 20000; if (i < 0) 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) { // 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("get_addrtx(): failed."); break; } } return; #else ctx_list *ctxs = read_addr(data->addr); 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) { NanScope(); async_addrtx_data* data = static_cast(req->data); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->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(Null()), Local::New(result) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); 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())); } /** * 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::AsciiValue 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(); // XXX Possibly pass true into is_new to search for CBlockIndex? 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::AsciiValue 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) { NanScope(); 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("peerId"), NanNew(pfrom->GetId())); o->Set(NanNew("userAgent"), NanNew(pfrom->cleanSubVer)); if (strCommand == "version") { // Each connection can only send one version message if (pfrom->nVersion != 0) { NanReturnValue(Undefined()); } bool fRelayTxes = false; int nStartingHeight = 0; int cleanSubVer = 0; //std::string strSubVer(strdup(pfrom->strSubVer.c_str())); 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()); } 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()); } 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()); } 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), "%020lu", (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()); } 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(); //bool fAlreadyHave = AlreadyHave(inv); // Bad size if (pfrom->nSendSize > (SendBufferSize() * 2)) { NanReturnValue(Undefined()); } Local item = NanNew(); //item->Set(NanNew("have"), NanNew(fAlreadyHave)); 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()); } 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); } 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); // cblock_to_jsblock(block, NULL, o, true); // last_block_hash = block.GetHash(); o->Set(NanNew("block"), jsblock); // XXX Can now directly access the DB: #if 0 leveldb::Iterator* pcursor = pblocktree->pdb->NewIterator(pblocktree->iteroptions); pcursor->SeekToFirst(); while (pcursor->Valid()); #endif } 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), "%020lu", (uint64_t)nonce); assert(written == 20); o->Set(NanNew("nonce"), NanNew(sNonce)); } else { char sNonce[21] = {0}; int written = snprintf(sNonce, sizeof(sNonce), "%020lu", (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), "%020lu", (uint64_t)nonce); assert(written == 20); char sPingNonceSent[21] = {0}; written = snprintf(sPingNonceSent, sizeof(sPingNonceSent), "%020lu", (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(); //std::string svData(filter.vData.begin(), filter.vData.end()); //char *cvData = svData.c_str(); //int vDataHexLen = sizeof(char) * (strlen(cvData) * 2) + 1; //char *vDataHex = (char *)malloc(vDataHexLen); //int written = snprintf(vDataHex, vDataHexLen, "%x", cvData); //uint64_t dataHex; //sscanf(cvData, "%x", &dataHex); //// assert(written == vDataHexLen); //vDataHex[written] = '\0'; //o->Set(NanNew("data"), NanNew(vDataHex)); //free(vDataHex); //o->Set(NanNew("full"), NanNew(filter.isFull)); //o->Set(NanNew("empty"), NanNew(filter.isEmpty)); //o->Set(NanNew("hashFuncs"), NanNew(filter.nHashFuncs)); //o->Set(NanNew("tweaks"), NanNew(filter.nTweak)); //o->Set(NanNew("flags"), NanNew(filter.nFlags)); 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) { //std::string svData(vData.begin(), vData.end()); //char *cvData = svData.c_str(); //int vDataHexLen = sizeof(char) * (strlen(cvData) * 2) + 1; //char *vDataHex = (char *)malloc(vDataHexLen); //int written = snprintf(vDataHex, vDataHexLen, "%x", cvData); //uint64_t dataHex; //sscanf(cvData, "%x", &dataHex); //// assert(written == vDataHexLen); //vDataHex[written] = '\0'; //o->Set(NanNew("data"), NanNew(vDataHex)); //free(vDataHex); 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; // delete cur->pfrom; // cleaned up on disconnect 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; } /** * WalletNewAddress() * bitcoindjs.walletNewAddress(options) * Create a new address in the global pwalletMain. */ NAN_METHOD(WalletNewAddress) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletNewAddress(options)"); } // Parse the account first so we don't generate a key if there's an error Local options = Local::Cast(args[0]); String::Utf8Value name_(options->Get(NanNew("name"))->ToString()); std::string strAccount = std::string(*name_); if (!pwalletMain->IsLocked()) { // XXX Do this asynchronously pwalletMain->TopUpKeyPool(); } // Generate a new key that is added to wallet CPubKey newKey; if (!pwalletMain->GetKeyFromPool(newKey)) { // return NanThrowError("Keypool ran out, please call keypoolrefill first"); // Call to EnsureWalletIsUnlocked() if (pwalletMain->IsLocked()) { return NanThrowError("Please enter the wallet passphrase with walletpassphrase first."); } // XXX Do this asynchronously pwalletMain->TopUpKeyPool(100); if (pwalletMain->GetKeyPoolSize() < 100) { return NanThrowError("Error refreshing keypool."); } } CKeyID keyID = newKey.GetID(); pwalletMain->SetAddressBook(keyID, strAccount, "receive"); NanReturnValue(NanNew(CBitcoinAddress(keyID).ToString())); } // NOTE: This function was ripped out of the bitcoin core source. It needed to // be modified to fit v8's error handling. CBitcoinAddress GetAccountAddress(std::string strAccount, bool bForceNew=false) { CWalletDB walletdb(pwalletMain->strWalletFile); CAccount account; walletdb.ReadAccount(strAccount, account); bool bKeyUsed = false; // Check if the current key has been used if (account.vchPubKey.IsValid()) { CScript scriptPubKey = GetScriptForDestination(account.vchPubKey.GetID()); for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end() && account.vchPubKey.IsValid(); ++it) { const CWalletTx& wtx = (*it).second; BOOST_FOREACH(const CTxOut& txout, wtx.vout) { if (txout.scriptPubKey == scriptPubKey) { bKeyUsed = true; } } } } // Generate a new key if (!account.vchPubKey.IsValid() || bForceNew || bKeyUsed) { if (!pwalletMain->GetKeyFromPool(account.vchPubKey)) { NanThrowError("Keypool ran out, please call keypoolrefill first"); CBitcoinAddress addr; return addr; } pwalletMain->SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive"); walletdb.WriteAccount(strAccount, account); } return CBitcoinAddress(account.vchPubKey.GetID()); } /** * WalletGetAccountAddress() * bitcoindjs.walletGetAccountAddress(options) * Return the address tied to a specific account name. */ NAN_METHOD(WalletGetAccountAddress) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletGetAccountAddress(options)"); } Local options = Local::Cast(args[0]); std::string strAccount = std::string(EMPTY); if (options->Get(NanNew("account"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("account"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("label"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("label"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("name"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("name"))->ToString()); strAccount = std::string(*account_); } if (strAccount == EMPTY) { return NanThrowError("No account name provided."); } std::string ret = GetAccountAddress(strAccount).ToString(); NanReturnValue(NanNew(ret)); } /** * WalletSetAccount() * bitcoindjs.walletSetAccount(options) * Return a new address if the account does not exist, or tie an account to an * address. */ NAN_METHOD(WalletSetAccount) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletSetAccount(options)"); } // Parse the account first so we don't generate a key if there's an error Local options = Local::Cast(args[0]); std::string strAddress = std::string(""); if (options->Get(NanNew("address"))->IsString()) { String::Utf8Value address_(options->Get(NanNew("address"))->ToString()); strAddress = std::string(*address_); } CBitcoinAddress address; if (strAddress != "") { address = CBitcoinAddress(strAddress); if (!address.IsValid()) { return NanThrowError("Invalid Bitcoin address"); } } std::string strAccount = std::string(EMPTY); if (options->Get(NanNew("account"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("account"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("label"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("label"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("name"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("name"))->ToString()); strAccount = std::string(*account_); } if (strAddress != "") { // If it isn't our address, create a recipient: { CTxDestination dest = address.Get(); if (!IsMine(*pwalletMain, dest)) { pwalletMain->SetAddressBook(dest, strAccount, "send"); pwalletMain->SetAddressBook(dest, strAccount, "send"); NanReturnValue(Undefined()); } } // Detect when changing the account of an address that is the 'unused current key' of another account: if (pwalletMain->mapAddressBook.count(address.Get())) { string strOldAccount = pwalletMain->mapAddressBook[address.Get()].name; if (address == GetAccountAddress(strOldAccount)) { GetAccountAddress(strOldAccount, true); } pwalletMain->SetAddressBook(address.Get(), strAccount, "receive"); } } else { // Generate a new key that is added to wallet CPubKey newKey; if (!pwalletMain->GetKeyFromPool(newKey)) { // return NanThrowError("Keypool ran out, please call keypoolrefill first"); // Call to EnsureWalletIsUnlocked() if (pwalletMain->IsLocked()) { return NanThrowError("Please enter the wallet passphrase with walletpassphrase first."); } // XXX Do this asynchronously pwalletMain->TopUpKeyPool(100); if (pwalletMain->GetKeyPoolSize() < 100) { return NanThrowError("Error refreshing keypool."); } } CKeyID keyID = newKey.GetID(); pwalletMain->SetAddressBook(keyID, strAccount, "receive"); } NanReturnValue(Undefined()); } /** * WalletGetAccount() * bitcoindjs.walletGetAccount(options) * Get an account name based on address. */ NAN_METHOD(WalletGetAccount) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletGetAccount(options)"); } Local options = Local::Cast(args[0]); String::Utf8Value address_(options->Get(NanNew("address"))->ToString()); std::string strAddress = std::string(*address_); CBitcoinAddress address(strAddress); if (!address.IsValid()) { return NanThrowError("Invalid Bitcoin address"); } std::string strAccount; map::iterator mi = pwalletMain->mapAddressBook.find(address.Get()); if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.name.empty()) { strAccount = (*mi).second.name; } NanReturnValue(NanNew(strAccount)); } /** * WalletGetRecipients() * bitcoindjs.walletGetRecipients() * Get all recipients */ NAN_METHOD(WalletGetRecipients) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletGetRecipients(options)"); } Local options = Local::Cast(args[0]); Local array = NanNew(); int i = 0; BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook) { const CBitcoinAddress& address = item.first; const string& strName = item.second.name; if (item.second.purpose == "send" && address.IsValid()) { Local recipient = NanNew(); recipient->Set(NanNew("label"), NanNew(strName)); recipient->Set(NanNew("account"), NanNew(strName)); recipient->Set(NanNew("name"), NanNew(strName)); recipient->Set(NanNew("address"), NanNew(address.ToString())); array->Set(i, recipient); i++; if (options->Get(NanNew("_label"))->IsString()) { break; } } } if (options->Get(NanNew("_label"))->IsString()) { NanReturnValue(array->Get(0)); } NanReturnValue(array); } /** * WalletSetRecipient() * bitcoindjs.walletSetRecipient() * Set a recipient */ NAN_METHOD(WalletSetRecipient) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletSetRecipient(options)"); } Local options = Local::Cast(args[0]); String::Utf8Value addr_(options->Get(NanNew("address"))->ToString()); std::string addr = std::string(*addr_); std::string strAccount = std::string(EMPTY); if (options->Get(NanNew("account"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("account"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("label"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("label"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("name"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("name"))->ToString()); strAccount = std::string(*account_); } if (strAccount == EMPTY) { return NanThrowError("No account name provided."); } CTxDestination address = CBitcoinAddress(addr).Get(); pwalletMain->SetAddressBook(address, strAccount, "send"); pwalletMain->SetAddressBook(address, strAccount, "send"); NanReturnValue(True()); } /** * WalletRemoveRecipient() * bitcoindjs.walletRemoveRecipient() * Remove a recipient */ NAN_METHOD(WalletRemoveRecipient) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletRemoveRecipient(options)"); } Local options = Local::Cast(args[0]); String::Utf8Value addr_(options->Get(NanNew("address"))->ToString()); std::string addr = std::string(*addr_); CTxDestination address = CBitcoinAddress(addr).Get(); pwalletMain->DelAddressBook(address); NanReturnValue(True()); } /** * WalletSendTo() * bitcoindjs.walletSendTo(options, callback) * Send bitcoin to an address, automatically creating the transaction based on * availing unspent outputs. */ NAN_METHOD(WalletSendTo) { NanScope(); if (args.Length() < 2 || !args[0]->IsObject() || !args[1]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.walletSendTo(options, callback)"); } Local options = Local::Cast(args[0]); Local callback = Local::Cast(args[1]); async_wallet_sendto_data *data = new async_wallet_sendto_data(); data->err_msg = std::string(""); data->callback = Persistent::New(callback); String::Utf8Value addr_(options->Get(NanNew("address"))->ToString()); std::string addr = std::string(*addr_); data->address = addr; // Amount int64_t nAmount = options->Get(NanNew("amount"))->IntegerValue(); data->nAmount = nAmount; // Wallet comments CWalletTx wtx; if (options->Get(NanNew("comment"))->IsString()) { String::Utf8Value comment_(options->Get(NanNew("comment"))->ToString()); std::string comment = std::string(*comment_); wtx.mapValue["comment"] = comment; } if (options->Get(NanNew("to"))->IsString()) { String::Utf8Value to_(options->Get(NanNew("to"))->ToString()); std::string to = std::string(*to_); wtx.mapValue["to"] = to; } data->wtx = wtx; uv_work_t *req = new uv_work_t(); req->data = data; int status = uv_queue_work(uv_default_loop(), req, async_wallet_sendto, (uv_after_work_cb)async_wallet_sendto_after); assert(status == 0); NanReturnValue(Undefined()); } static void async_wallet_sendto(uv_work_t *req) { async_wallet_sendto_data* data = static_cast(req->data); CBitcoinAddress address(data->address); if (!address.IsValid()) { data->err_msg = std::string("Invalid Bitcoin address"); return; } // Amount int64_t nAmount = data->nAmount; // Wallet Transaction CWalletTx wtx = data->wtx; // Call to EnsureWalletIsUnlocked() if (pwalletMain->IsLocked()) { data->err_msg = std::string("Please enter the wallet passphrase with walletpassphrase first."); return; } std::string strError = pwalletMain->SendMoney(address.Get(), nAmount, wtx); if (strError != "") { data->err_msg = strError; return; } data->tx_hash = wtx.GetHash().GetHex(); } static void async_wallet_sendto_after(uv_work_t *req) { NanScope(); async_wallet_sendto_data* data = static_cast(req->data); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } else { const unsigned argc = 2; Local argv[argc] = { Local::New(Null()), Local::New(NanNew(data->tx_hash)) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); delete data; delete req; } /** * WalletSendFrom() * bitcoindjs.walletSendFrom(options, callback) * Send bitcoin to a particular address from a particular owned account name. * This once again automatically creates and signs a transaction based on any * unspent outputs available. */ NAN_METHOD(WalletSendFrom) { NanScope(); if (args.Length() < 2 || !args[0]->IsObject() || !args[1]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.walletSendFrom(options, callback)"); } Local options = Local::Cast(args[0]); Local callback = Local::Cast(args[1]); async_wallet_sendfrom_data *data = new async_wallet_sendfrom_data(); data->err_msg = std::string(""); data->callback = Persistent::New(callback); String::Utf8Value addr_(options->Get(NanNew("address"))->ToString()); std::string addr = std::string(*addr_); data->address = addr; String::Utf8Value from_(options->Get(NanNew("from"))->ToString()); std::string from = std::string(*from_); std::string strAccount = from; int64_t nAmount = options->Get(NanNew("amount"))->IntegerValue(); data->nAmount = nAmount; int nMinDepth = 1; if (options->Get(NanNew("confirmations"))->IsNumber()) { nMinDepth = options->Get(NanNew("confirmations"))->IntegerValue(); } data->nMinDepth = nMinDepth; CWalletTx wtx; wtx.strFromAccount = strAccount; if (options->Get(NanNew("comment"))->IsString()) { String::Utf8Value comment_(options->Get(NanNew("comment"))->ToString()); std::string comment = std::string(*comment_); wtx.mapValue["comment"] = comment; } if (options->Get(NanNew("to"))->IsString()) { String::Utf8Value to_(options->Get(NanNew("to"))->ToString()); std::string to = std::string(*to_); wtx.mapValue["to"] = to; } data->wtx = wtx; uv_work_t *req = new uv_work_t(); req->data = data; int status = uv_queue_work(uv_default_loop(), req, async_wallet_sendfrom, (uv_after_work_cb)async_wallet_sendfrom_after); assert(status == 0); NanReturnValue(Undefined()); } static void async_wallet_sendfrom(uv_work_t *req) { async_wallet_sendfrom_data* data = static_cast(req->data); CBitcoinAddress address(data->address); if (!address.IsValid()) { data->err_msg = std::string("Invalid Bitcoin address"); return; } int64_t nAmount = data->nAmount; int nMinDepth = data->nMinDepth; CWalletTx wtx = data->wtx; std::string strAccount = data->wtx.strFromAccount; // Call to: EnsureWalletIsUnlocked() if (pwalletMain->IsLocked()) { data->err_msg = std::string("Please enter the wallet passphrase with walletpassphrase first."); return; } // Check funds double nBalance = (double)GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE); if (((double)(nAmount * 1.0) / 100000000) > nBalance) { data->err_msg = std::string("Account has insufficient funds"); return; } // Send std::string strError = pwalletMain->SendMoney(address.Get(), nAmount, wtx); if (strError != "") { data->err_msg = strError; return; } data->tx_hash = wtx.GetHash().GetHex(); } static void async_wallet_sendfrom_after(uv_work_t *req) { NanScope(); async_wallet_sendfrom_data* data = static_cast(req->data); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } else { const unsigned argc = 2; Local argv[argc] = { Local::New(Null()), Local::New(NanNew(data->tx_hash)) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); delete data; delete req; } /** * WalletMove() * bitcoindjs.walletMove(options) * Move BTC from one account to another */ NAN_METHOD(WalletMove) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletMove(options)"); } Local options = Local::Cast(args[0]); std::string strFrom; if (options->Get(NanNew("from"))->IsString()) { String::Utf8Value s_(options->Get(NanNew("from"))->ToString()); strFrom = std::string(*s_); } std::string strTo; if (options->Get(NanNew("to"))->IsString()) { String::Utf8Value s_(options->Get(NanNew("to"))->ToString()); strTo = std::string(*s_); } CAmount nAmount; if (options->Get(NanNew("amount"))->IsNumber()) { nAmount = (CAmount)options->Get(NanNew("amount"))->IntegerValue(); } else { return NanThrowError("No amount specified."); } // DEPRECATED // int nMinDepth = 1; // if (options->Get(NanNew("confirmations"))->IsNumber()) { // nMinDepth = options->Get(NanNew("confirmations"))->IntegerValue(); // } std::string strComment; if (options->Get(NanNew("comment"))->IsString()) { String::Utf8Value s_(options->Get(NanNew("comment"))->ToString()); strComment = std::string(*s_); } CWalletDB walletdb(pwalletMain->strWalletFile); if (!walletdb.TxnBegin()) { return NanThrowError("database error"); } int64_t nNow = GetAdjustedTime(); // Debit CAccountingEntry debit; debit.nOrderPos = pwalletMain->IncOrderPosNext(&walletdb); debit.strAccount = strFrom; debit.nCreditDebit = -nAmount; debit.nTime = nNow; debit.strOtherAccount = strTo; debit.strComment = strComment; walletdb.WriteAccountingEntry(debit); // Credit CAccountingEntry credit; credit.nOrderPos = pwalletMain->IncOrderPosNext(&walletdb); credit.strAccount = strTo; credit.nCreditDebit = nAmount; credit.nTime = nNow; credit.strOtherAccount = strFrom; credit.strComment = strComment; walletdb.WriteAccountingEntry(credit); if (!walletdb.TxnCommit()) { return NanThrowError("database error"); } NanReturnValue(Undefined()); } /** * WalletSignMessage() * bitcoindjs.walletSignMessage(options) * Sign any piece of text using a private key tied to an address. */ NAN_METHOD(WalletSignMessage) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletSignMessage(options)"); } Local options = Local::Cast(args[0]); String::Utf8Value strAddress_(options->Get(NanNew("address"))->ToString()); std::string strAddress = std::string(*strAddress_); String::Utf8Value strMessage_(options->Get(NanNew("message"))->ToString()); std::string strMessage = std::string(*strMessage_); // Call to EnsureWalletIsUnlocked() if (pwalletMain->IsLocked()) { return NanThrowError("Please enter the wallet passphrase with walletpassphrase first."); } CBitcoinAddress addr(strAddress); if (!addr.IsValid()) { return NanThrowError("Invalid address"); } CKeyID keyID; if (!addr.GetKeyID(keyID)) { return NanThrowError("Address does not refer to key"); } CKey key; if (!pwalletMain->GetKey(keyID, key)) { return NanThrowError("Private key not available"); } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << strMessage; vector vchSig; if (!key.SignCompact(ss.GetHash(), vchSig)) { return NanThrowError("Sign failed"); } std::string result = EncodeBase64(&vchSig[0], vchSig.size()); NanReturnValue(NanNew(result)); } /** * WalletVerifyMessage() * bitcoindjs.walletVerifyMessage(options) * Verify a signed message using any address' public key. */ NAN_METHOD(WalletVerifyMessage) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletVerifyMessage(options)"); } Local options = Local::Cast(args[0]); String::Utf8Value strAddress_(options->Get(NanNew("address"))->ToString()); std::string strAddress = std::string(*strAddress_); String::Utf8Value strSign_(options->Get(NanNew("signature"))->ToString()); std::string strSign = std::string(*strSign_); String::Utf8Value strMessage_(options->Get(NanNew("message"))->ToString()); std::string strMessage = std::string(*strMessage_); CBitcoinAddress addr(strAddress); if (!addr.IsValid()) { return NanThrowError( "Invalid address"); } CKeyID keyID; if (!addr.GetKeyID(keyID)) { return NanThrowError( "Address does not refer to key"); } bool fInvalid = false; vector vchSig = DecodeBase64(strSign.c_str(), &fInvalid); if (fInvalid) { return NanThrowError( "Malformed base64 encoding"); } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << strMessage; CPubKey pubkey; if (!pubkey.RecoverCompact(ss.GetHash(), vchSig)) { NanReturnValue(NanNew(false)); } NanReturnValue(NanNew(pubkey.GetID() == keyID)); } /** * WalletCreateMultiSigAddress() * bitcoindjs.walletCreateMultiSigAddress(options) * Create a multisig address for the global wallet. */ CScript _createmultisig_redeemScript(int nRequired, Local keys) { // Gather public keys if (nRequired < 1) { throw runtime_error("a multisignature address must require at least one key to redeem"); } if ((int)keys->Length() < nRequired) { NanThrowError("not enough keys supplied"); CScript s; return s; } std::vector pubkeys; pubkeys.resize(keys->Length()); for (unsigned int i = 0; i < keys->Length(); i++) { String::Utf8Value key_(keys->Get(i)->ToString()); const std::string& ks = std::string(*key_); #ifdef ENABLE_WALLET // Case 1: Bitcoin address and we have full public key: CBitcoinAddress address(ks); if (pwalletMain && address.IsValid()) { CKeyID keyID; if (!address.GetKeyID(keyID)) { NanThrowError("does not refer to a key"); CScript s; return s; } CPubKey vchPubKey; if (!pwalletMain->GetPubKey(keyID, vchPubKey)) { NanThrowError("no full public key for address"); CScript s; return s; } if (!vchPubKey.IsFullyValid()) { NanThrowError("Invalid public key"); CScript s; return s; } pubkeys[i] = vchPubKey; } // Case 2: hex public key else #endif if (IsHex(ks)) { CPubKey vchPubKey(ParseHex(ks)); if (!vchPubKey.IsFullyValid()) { NanThrowError("Invalid public key"); CScript s; return s; } pubkeys[i] = vchPubKey; } else { NanThrowError("Invalid public key"); CScript s; return s; } } CScript result = GetScriptForMultisig(nRequired, pubkeys); if (result.size() > MAX_SCRIPT_ELEMENT_SIZE) { NanThrowError("redeemScript exceeds size limit"); CScript s; return s; } return result; } NAN_METHOD(WalletCreateMultiSigAddress) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletCreateMultiSigAddress(options)"); } Local options = Local::Cast(args[0]); int nRequired = options->Get(NanNew("nRequired"))->IntegerValue(); Local keys = Local::Cast(options->Get(NanNew("keys"))); // Gather public keys if (nRequired < 1) { return NanThrowError( "a multisignature address must require at least one key to redeem"); } if ((int)keys->Length() < nRequired) { char s[150] = {0}; snprintf(s, sizeof(s), "not enough keys supplied (got %u keys, but need at least %u to redeem)", keys->Length(), nRequired); NanThrowError(s); NanReturnValue(Undefined()); } std::string strAccount = ""; if (options->Get(NanNew("account"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("account"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("label"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("label"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("name"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("name"))->ToString()); strAccount = std::string(*account_); } // Construct using pay-to-script-hash: CScript inner = _createmultisig_redeemScript(nRequired, keys); CScriptID innerID(inner); pwalletMain->AddCScript(inner); pwalletMain->SetAddressBook(innerID, strAccount, "send"); CBitcoinAddress address(innerID); Local result = NanNew(); result->Set(NanNew("address"), NanNew(address.ToString())); result->Set(NanNew("redeemScript"), NanNew(HexStr(inner.begin(), inner.end()))); NanReturnValue(result); } /** * WalletGetBalance() * bitcoindjs.walletGetBalance(options) * Get total balance of global wallet in satoshies in a javascript Number (up * to 64 bits, only 32 if bitwise ops or floating point are used unfortunately. * Obviously floating point is not necessary for satoshies). */ NAN_METHOD(WalletGetBalance) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletGetBalance(options)"); } Local options = Local::Cast(args[0]); std::string strAccount = "*"; int nMinDepth = 1; if (options->Get(NanNew("account"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("account"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("label"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("label"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("name"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("name"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("confirmations"))->IsNumber()) { nMinDepth = options->Get(NanNew("confirmations"))->IntegerValue(); } isminefilter filter = ISMINE_SPENDABLE; if (strAccount == "*") { // Calculate total balance a different way from GetBalance() // (GetBalance() sums up all unspent TxOuts) // getbalance and getbalance '*' 0 should return the same number CAmount nBalance = 0; for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { const CWalletTx& wtx = (*it).second; if (!wtx.IsTrusted() || wtx.GetBlocksToMaturity() > 0) { continue; } CAmount allFee; string strSentAccount; list listReceived; list listSent; wtx.GetAmounts(listReceived, listSent, allFee, strSentAccount, filter); if (wtx.GetDepthInMainChain() >= nMinDepth) { BOOST_FOREACH(const COutputEntry& r, listReceived) { nBalance += r.amount; } } BOOST_FOREACH(const COutputEntry& s, listSent) { nBalance -= s.amount; } nBalance -= allFee; } NanReturnValue(NanNew(SatoshiFromAmount(nBalance))); } CAmount nBalance = GetAccountBalance(strAccount, nMinDepth, filter); NanReturnValue(NanNew(SatoshiFromAmount(nBalance))); } /** * WalletGetUnconfirmedBalance() * bitcoindjs.walletGetUnconfirmedBalance(options) * Returns the unconfirmed balance in satoshies (including the transactions * that have not yet been included in any block). */ NAN_METHOD(WalletGetUnconfirmedBalance) { NanScope(); NanReturnValue(NanNew(pwalletMain->GetUnconfirmedBalance())); } /** * WalletListTransactions() * bitcoindjs.walletListTransactions(options) * List all transactions pertaining to any owned addreses. NOT YET IMPLEMENTED> */ NAN_METHOD(WalletListTransactions) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletListTransactions(options)"); } Local options = Local::Cast(args[0]); std::string strAccount = "*"; if (options->Get(NanNew("account"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("account"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("label"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("label"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("name"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("name"))->ToString()); strAccount = std::string(*account_); } int nCount = 10; if (options->Get(NanNew("count"))->IsNumber()) { nCount = (int)options->Get(NanNew("count"))->IntegerValue(); } int nFrom = 0; if (options->Get(NanNew("from"))->IsNumber()) { nFrom = (int)options->Get(NanNew("from"))->IntegerValue(); } isminefilter filter = ISMINE_SPENDABLE; if (options->Get(NanNew("spendable"))->IsBoolean()) { if (options->Get(NanNew("spendable"))->ToBoolean()->IsTrue()) { filter = filter | ISMINE_WATCH_ONLY; } } if (nCount < 0) { return NanThrowError("Negative count"); } if (nFrom < 0) { return NanThrowError("Negative from"); } Local ret = NanNew(); std::list acentries; CWallet::TxItems txOrdered = pwalletMain->OrderedTxItems(acentries, strAccount); // iterate backwards until we have nCount items to return: int a_count = 0; for (CWallet::TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = (*it).second.first; if (pwtx != 0) { ListTransactions_V8(*pwtx, strAccount, 0, true, ret, filter, &a_count); } CAccountingEntry *const pacentry = (*it).second.second; if (pacentry != 0) { AcentryToJSON_V8(*pacentry, strAccount, ret, &a_count); } if ((int)ret->Length() >= (nCount+nFrom)) { break; } } // ret is newest to oldest if (nFrom > (int)ret->Length()) { nFrom = ret->Length(); } if ((nFrom + nCount) > (int)ret->Length()) { nCount = ret->Length() - nFrom; } NanReturnValue(ret); } static void AcentryToJSON_V8(const CAccountingEntry& acentry, const string& strAccount, Local& ret, int *a_count) { bool fAllAccounts = (strAccount == string("*")); int i = *a_count; if (fAllAccounts || acentry.strAccount == strAccount) { Local entry = NanNew(); entry->Set(NanNew("account"), NanNew(acentry.strAccount)); entry->Set(NanNew("category"), NanNew("move")); entry->Set(NanNew("time"), NanNew(acentry.nTime)); entry->Set(NanNew("amount"), NanNew(acentry.nCreditDebit)); entry->Set(NanNew("otheraccount"), NanNew(acentry.strOtherAccount)); entry->Set(NanNew("comment"), NanNew(acentry.strComment)); ret->Set(i, entry); i++; } *a_count = i; } static void WalletTxToJSON_V8(const CWalletTx& wtx, Local& entry) { int confirms = wtx.GetDepthInMainChain(); entry->Set(NanNew("confirmations"), NanNew(confirms)); if (wtx.IsCoinBase()) { entry->Set(NanNew("generated"), NanNew(true)); } if (confirms > 0) { entry->Set(NanNew("blockhash"), NanNew(wtx.hashBlock.GetHex())); entry->Set(NanNew("blockindex"), NanNew(wtx.nIndex)); entry->Set(NanNew("blocktime"), NanNew(mapBlockIndex[wtx.hashBlock]->GetBlockTime())); } uint256 hash = wtx.GetHash(); entry->Set(NanNew("txid"), NanNew(hash.GetHex())); Local conflicts = NanNew(); int i = 0; BOOST_FOREACH(const uint256& conflict, wtx.GetConflicts()) { conflicts->Set(i, NanNew(conflict.GetHex())); i++; } entry->Set(NanNew("walletconflicts"), conflicts); entry->Set(NanNew("time"), NanNew(wtx.GetTxTime())); entry->Set(NanNew("timereceived"), NanNew((int64_t)wtx.nTimeReceived)); BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue) { entry->Set(NanNew(item.first), NanNew(item.second)); } std::string strHex = EncodeHexTx(static_cast(wtx)); entry->Set(NanNew("hex"), NanNew(strHex)); } static void MaybePushAddress_V8(Local& entry, const CTxDestination &dest) { CBitcoinAddress addr; if (addr.Set(dest)) { entry->Set(NanNew("address"), NanNew(addr.ToString())); } } static void ListTransactions_V8(const CWalletTx& wtx, const string& strAccount, int nMinDepth, bool fLong, Local ret, const isminefilter& filter, int *a_count) { CAmount nFee; string strSentAccount; list listReceived; list listSent; wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, filter); bool fAllAccounts = (strAccount == string("*")); bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); int i = *a_count; // Sent if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount)) { BOOST_FOREACH(const COutputEntry& s, listSent) { Local entry = NanNew(); if (involvesWatchonly || (::IsMine(*pwalletMain, s.destination) & ISMINE_WATCH_ONLY)) { entry->Set(NanNew("involvesWatchonly"), NanNew(true)); } entry->Set(NanNew("account"), NanNew(strSentAccount)); MaybePushAddress_V8(entry, s.destination); entry->Set(NanNew("category"), NanNew("send")); entry->Set(NanNew("amount"), NanNew(-s.amount)); entry->Set(NanNew("vout"), NanNew(s.vout)); entry->Set(NanNew("fee"), NanNew(-nFee)); if (fLong) { WalletTxToJSON_V8(wtx, entry); } else { std::string strHex = EncodeHexTx(static_cast(wtx)); entry->Set(NanNew("hex"), NanNew(strHex)); } ret->Set(i, entry); i++; } } // Received if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) { BOOST_FOREACH(const COutputEntry& r, listReceived) { string account; if (pwalletMain->mapAddressBook.count(r.destination)) { account = pwalletMain->mapAddressBook[r.destination].name; } if (fAllAccounts || (account == strAccount)) { Local entry = NanNew(); if(involvesWatchonly || (::IsMine(*pwalletMain, r.destination) & ISMINE_WATCH_ONLY)) { entry->Set(NanNew("involvesWatchonly"), NanNew(true)); } entry->Set(NanNew("account"), NanNew(account)); MaybePushAddress_V8(entry, r.destination); if (wtx.IsCoinBase()) { if (wtx.GetDepthInMainChain() < 1) { entry->Set(NanNew("category"), NanNew("orphan")); } else if (wtx.GetBlocksToMaturity() > 0) { entry->Set(NanNew("category"), NanNew("immature")); } else { entry->Set(NanNew("category"), NanNew("generate")); } } else { entry->Set(NanNew("category"), NanNew("receive")); } entry->Set(NanNew("amount"), NanNew(r.amount)); // XXX What is COutputEntry::vout? // entry->Set(NanNew("vout"), NanNew(r.vout)); if (fLong) { WalletTxToJSON_V8(wtx, entry); } else { std::string strHex = EncodeHexTx(static_cast(wtx)); entry->Set(NanNew("hex"), NanNew(strHex)); } ret->Set(i, entry); i++; } } } *a_count = i; } /** * WalletReceivedByAddress() * bitcoindjs.walletReceivedByAddress(options) * List all transactions pertaining to any owned addreses. NOT YET IMPLEMENTED> */ NAN_METHOD(WalletReceivedByAddress) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletReceivedByAddress(options)"); } Local options = Local::Cast(args[0]); String::Utf8Value addr_(options->Get(NanNew("address"))->ToString()); std::string addr = std::string(*addr_); // Bitcoin address CBitcoinAddress address = CBitcoinAddress(addr); if (!address.IsValid()) { return NanThrowError("Invalid Bitcoin address"); } CScript scriptPubKey = GetScriptForDestination(address.Get()); if (!IsMine(*pwalletMain, scriptPubKey)) { NanReturnValue(NanNew((double)0.0)); } // Minimum confirmations int nMinDepth = 1; if (options->Get(NanNew("confirmations"))->IsNumber()) { nMinDepth = (int)options->Get(NanNew("confirmations"))->IntegerValue(); } // Tally CAmount nAmount = 0; for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { const CWalletTx& wtx = (*it).second; if (wtx.IsCoinBase() || !IsFinalTx(wtx)) { continue; } BOOST_FOREACH(const CTxOut& txout, wtx.vout) { if (txout.scriptPubKey == scriptPubKey) { if (wtx.GetDepthInMainChain() >= nMinDepth) { nAmount += txout.nValue; } } } } NanReturnValue(NanNew((int64_t)nAmount)); } /** * WalletListAccounts() * bitcoindjs.walletListAccounts(options) * This will list all accounts, addresses, balanced, private keys, public keys, * and whether these keys are in compressed format. */ NAN_METHOD(WalletListAccounts) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletListAccounts(options)"); } Local options = Local::Cast(args[0]); int nMinDepth = 1; if (options->Get(NanNew("confirmations"))->IsNumber()) { nMinDepth = options->Get(NanNew("confirmations"))->IntegerValue(); } isminefilter includeWatchonly = ISMINE_SPENDABLE; map mapAccountBalances; BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& entry, pwalletMain->mapAddressBook) { if (IsMine(*pwalletMain, entry.first) & includeWatchonly) { // This address belongs to me mapAccountBalances[entry.second.name] = 0; } } for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { const CWalletTx& wtx = (*it).second; CAmount nFee; std::string strSentAccount; list listReceived; list listSent; int nDepth = wtx.GetDepthInMainChain(); if (wtx.GetBlocksToMaturity() > 0 || nDepth < 0) { continue; } wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, includeWatchonly); mapAccountBalances[strSentAccount] -= nFee; BOOST_FOREACH(const COutputEntry& s, listSent) { mapAccountBalances[strSentAccount] -= s.amount; } if (nDepth >= nMinDepth) { BOOST_FOREACH(const COutputEntry& r, listReceived) { if (pwalletMain->mapAddressBook.count(r.destination)) { mapAccountBalances[pwalletMain->mapAddressBook[r.destination].name] += r.amount; } else { mapAccountBalances[""] += r.amount; } } } } list acentries; CWalletDB(pwalletMain->strWalletFile).ListAccountCreditDebit("*", acentries); BOOST_FOREACH(const CAccountingEntry& entry, acentries) { mapAccountBalances[entry.strAccount] += entry.nCreditDebit; } Local obj = NanNew(); BOOST_FOREACH(const PAIRTYPE(string, int64_t)& accountBalance, mapAccountBalances) { Local entry = NanNew(); entry->Set(NanNew("balance"), NanNew(accountBalance.second)); Local addr = NanNew(); int i = 0; BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook) { const CBitcoinAddress& address = item.first; const std::string& strName = item.second.name; if (strName == accountBalance.first && item.second.purpose != "send") { Local a = NanNew(); a->Set(NanNew("address"), NanNew(address.ToString())); CKeyID keyID; if (!address.GetKeyID(keyID)) { return NanThrowError("Address does not refer to a key"); } CKey vchSecret; if (!pwalletMain->GetKey(keyID, vchSecret)) { return NanThrowError("Private key for address is not known"); } if (!pwalletMain->IsLocked()) { std::string priv = CBitcoinSecret(vchSecret).ToString(); a->Set(NanNew("privkeycompressed"), NanNew(vchSecret.IsCompressed())); a->Set(NanNew("privkey"), NanNew(priv)); } CPubKey vchPubKey; pwalletMain->GetPubKey(keyID, vchPubKey); a->Set(NanNew("pubkeycompressed"), NanNew(vchPubKey.IsCompressed())); a->Set(NanNew("pubkey"), NanNew(HexStr(vchPubKey))); addr->Set(i, a); i++; } } entry->Set(NanNew("addresses"), addr); entry->Set(NanNew("account"), NanNew(accountBalance.first)); entry->Set(NanNew("name"), NanNew(accountBalance.first)); entry->Set(NanNew("label"), NanNew(accountBalance.first)); obj->Set(NanNew(accountBalance.first), entry); } NanReturnValue(obj); } /** * WalletGetTransaction() * bitcoindjs.walletGetTransaction(options) * Get any transaction pertaining to any owned addresses. */ NAN_METHOD(WalletGetTransaction) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletGetTransaction(options)"); } Local options = Local::Cast(args[0]); std::string txid; if (options->Get(NanNew("txid"))->IsString()) { String::Utf8Value txid_(options->Get(NanNew("txid"))->ToString()); txid = std::string(*txid_); } else { return NanThrowError("txid not specified."); } uint256 hash; hash.SetHex(txid); isminefilter filter = ISMINE_SPENDABLE; if (options->Get(NanNew("watch"))->IsBoolean() && options->Get(NanNew("watch"))->ToBoolean()->IsTrue()) { filter = filter | ISMINE_WATCH_ONLY; } Local entry = NanNew(); if (!pwalletMain->mapWallet.count(hash)) { return NanThrowError("Invalid or non-wallet transaction id"); } const CWalletTx& wtx = pwalletMain->mapWallet[hash]; CAmount nCredit = wtx.GetCredit(filter != 0); CAmount nDebit = wtx.GetDebit(filter); CAmount nNet = nCredit - nDebit; CAmount nFee = (wtx.IsFromMe(filter) ? wtx.GetValueOut() - nDebit : 0); entry->Set(NanNew("amount"), NanNew(SatoshiFromAmount(nNet - nFee))); if (wtx.IsFromMe(filter)) { entry->Set(NanNew("fee"), NanNew(SatoshiFromAmount(nFee))); } WalletTxToJSON_V8(wtx, entry); Local details = NanNew(); int a_count = 0; // NOTE: fLong is set to false in rpcwallet.cpp ListTransactions_V8(wtx, "*", 0, /*fLong=*/ true, details, filter, &a_count); entry->Set(NanNew("details"), details); //std::string strHex = EncodeHexTx(static_cast(wtx)); //entry->Set(NanNew("hex"), NanNew(strHex)); NanReturnValue(entry); } /** * WalletBackup() * bitcoindjs.walletBackup(options) * Backup the bdb wallet.dat to a particular location on filesystem. */ NAN_METHOD(WalletBackup) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletBackup(options)"); } Local options = Local::Cast(args[0]); String::Utf8Value path_(options->Get(NanNew("path"))->ToString()); std::string strDest = std::string(*path_); if (!BackupWallet(*pwalletMain, strDest)) { return NanThrowError("Wallet backup failed!"); } NanReturnValue(Undefined()); } /** * WalletPassphrase() * bitcoindjs.walletPassphrase(options) * Unlock wallet if encrypted already. */ NAN_METHOD(WalletPassphrase) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletPassphrase(options)"); } Local options = Local::Cast(args[0]); String::Utf8Value passphrase_(options->Get(NanNew("passphrase"))->ToString()); std::string strPassphrase = std::string(*passphrase_); if (!pwalletMain->IsCrypted()) { return NanThrowError("Running with an unencrypted wallet, but walletpassphrase was called."); } SecureString strWalletPass; strWalletPass.reserve(100); strWalletPass = strPassphrase.c_str(); if (strWalletPass.length() > 0) { if (!pwalletMain->Unlock(strWalletPass)) { return NanThrowError("The wallet passphrase entered was incorrect."); } } else { return NanThrowError("No wallet passphrase provided."); } // XXX Do this asynchronously pwalletMain->TopUpKeyPool(); NanReturnValue(Undefined()); } /** * WalletPassphraseChange() * bitcoindjs.walletPassphraseChange(options) * Change the current passphrase for the encrypted wallet. */ NAN_METHOD(WalletPassphraseChange) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletPassphraseChange(options)"); } Local options = Local::Cast(args[0]); String::Utf8Value oldPass_(options->Get(NanNew("oldPass"))->ToString()); std::string oldPass = std::string(*oldPass_); String::Utf8Value newPass_(options->Get(NanNew("newPass"))->ToString()); std::string newPass = std::string(*newPass_); if (!pwalletMain->IsCrypted()) { return NanThrowError("Running with an unencrypted wallet, but walletpassphrasechange was called."); } SecureString strOldWalletPass; strOldWalletPass.reserve(100); strOldWalletPass = oldPass.c_str(); SecureString strNewWalletPass; strNewWalletPass.reserve(100); strNewWalletPass = newPass.c_str(); if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1) { return NanThrowError("Passphrases not provided."); } if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) { return NanThrowError("The wallet passphrase entered was incorrect."); } NanReturnValue(Undefined()); } /** * WalletLock() * bitcoindjs.walletLock(options) * Forget the encrypted wallet passphrase and lock the wallet once again. */ NAN_METHOD(WalletLock) { NanScope(); if (args.Length() < 0) { return NanThrowError( "Usage: bitcoindjs.walletLock([options])"); } if (!pwalletMain->IsCrypted()) { return NanThrowError("Running with an unencrypted wallet, but walletlock was called."); } pwalletMain->Lock(); NanReturnValue(Undefined()); } /** * WalletEncrypt() * bitcoindjs.walletEncrypt(options) * Encrypt the global wallet with a particular passphrase. Requires restarted * because Berkeley DB is bad. */ NAN_METHOD(WalletEncrypt) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletEncrypt(options)"); } Local options = Local::Cast(args[0]); String::Utf8Value passphrase_(options->Get(NanNew("passphrase"))->ToString()); std::string strPass = std::string(*passphrase_); if (pwalletMain->IsCrypted()) { return NanThrowError("Running with an encrypted wallet, but encryptwallet was called."); } SecureString strWalletPass; strWalletPass.reserve(100); strWalletPass = strPass.c_str(); if (strWalletPass.length() < 1) { return NanThrowError("No wallet passphrase provided."); } if (!pwalletMain->EncryptWallet(strWalletPass)) { return NanThrowError("Failed to encrypt the wallet."); } // BDB seems to have a bad habit of writing old data into // slack space in .dat files; that is bad if the old data is // unencrypted private keys. So: StartShutdown(); printf( "bitcoind.js:" " wallet encrypted; bitcoind.js stopping," " restart to run with encrypted wallet." " The keypool has been flushed, you need" " to make a new backup.\n" ); NanReturnValue(Undefined()); } /** * WalletEncrypted() * bitcoindjs.walletEncrypted() * Check whether the wallet is encrypted. */ NAN_METHOD(WalletEncrypted) { NanScope(); if (args.Length() > 0) { return NanThrowError( "Usage: bitcoindjs.walletEncrypted()"); } bool isLocked = false; bool isEncrypted = false; isEncrypted = pwalletMain->IsCrypted(); if (isEncrypted) { isLocked = pwalletMain->IsLocked(); } Local result = NanNew(); result->Set(NanNew("locked"), NanNew(isLocked)); result->Set(NanNew("encrypted"), NanNew(isEncrypted)); NanReturnValue(result); } /** * WalletKeyPoolRefill() * bitcoindjs.walletKeyPoolRefill(options) * Refill key pool */ NAN_METHOD(WalletKeyPoolRefill) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletKeyPoolRefill(options)"); } Local options = Local::Cast(args[0]); // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool unsigned int kpSize = 0; if (options->Get(NanNew("size"))->IsNumber()) { kpSize = (unsigned int)options->Get(NanNew("size"))->IntegerValue(); } // EnsureWalletIsUnlocked(); if (pwalletMain->IsLocked()) { return NanThrowError("Please enter the wallet passphrase with walletpassphrase first."); } // XXX Do this asynchronously pwalletMain->TopUpKeyPool(kpSize); if (pwalletMain->GetKeyPoolSize() < kpSize) { return NanThrowError("Error refreshing keypool."); } NanReturnValue(True()); } /** * WalletSetTxFee() * bitcoindjs.walletSetTxFee(options) * Set default global wallet transaction fee internally. */ NAN_METHOD(WalletSetTxFee) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletSetTxFee(options)"); } Local options = Local::Cast(args[0]); int64_t fee = options->Get(NanNew("fee"))->IntegerValue(); // Amount CAmount nAmount = 0; if (fee != 0.0) { nAmount = fee; } payTxFee = CFeeRate(nAmount, 1000); NanReturnValue(True()); } /** * WalletDumpKey() * bitcoindjs.walletDumpKey(options) * Dump private key */ NAN_METHOD(WalletDumpKey) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletDumpKey(options)"); } Local options = Local::Cast(args[0]); String::Utf8Value addr_(options->Get(NanNew("address"))->ToString()); std::string addr = std::string(*addr_); CBitcoinAddress address(addr); Local obj = NanNew(); obj->Set(NanNew("address"), NanNew(address.ToString())); CKeyID keyID; if (!address.GetKeyID(keyID)) { return NanThrowError("Address does not refer to a key"); } CKey vchSecret; if (!pwalletMain->GetKey(keyID, vchSecret)) { return NanThrowError("Private key for address is not known"); } if (!pwalletMain->IsCrypted()) { std::string priv = CBitcoinSecret(vchSecret).ToString(); obj->Set(NanNew("privkeycompressed"), NanNew(vchSecret.IsCompressed())); obj->Set(NanNew("privkey"), NanNew(priv)); } CPubKey vchPubKey; pwalletMain->GetPubKey(keyID, vchPubKey); obj->Set(NanNew("pubkeycompressed"), NanNew(vchPubKey.IsCompressed())); obj->Set(NanNew("pubkey"), NanNew(HexStr(vchPubKey))); NanReturnValue(obj); } /** * WalletImportKey() * bitcoindjs.walletImportKey(options) * Import private key into global wallet using standard compressed bitcoind * format. */ NAN_METHOD(WalletImportKey) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletImportKey(options, callback)"); } async_import_key_data *data = new async_import_key_data(); data->err_msg = std::string(""); data->fRescan = false; Local options = Local::Cast(args[0]); Local callback; if (args.Length() > 1 && args[1]->IsFunction()) { callback = Local::Cast(args[1]); data->callback = Persistent::New(callback); } std::string strSecret = ""; std::string strAccount = std::string(EMPTY); String::Utf8Value key_(options->Get(NanNew("key"))->ToString()); strSecret = std::string(*key_); if (options->Get(NanNew("account"))->IsString()) { String::Utf8Value label_(options->Get(NanNew("account"))->ToString()); strAccount = std::string(*label_); } if (options->Get(NanNew("label"))->IsString()) { String::Utf8Value label_(options->Get(NanNew("label"))->ToString()); strAccount = std::string(*label_); } if (options->Get(NanNew("name"))->IsString()) { String::Utf8Value label_(options->Get(NanNew("name"))->ToString()); strAccount = std::string(*label_); } rescan: if (data->fRescan) { uv_work_t *req = new uv_work_t(); req->data = data; int status = uv_queue_work(uv_default_loop(), req, async_import_key, (uv_after_work_cb)async_import_key_after); assert(status == 0); NanReturnValue(Undefined()); } // Whether to perform rescan after import data->fRescan = args.Length() > 1 && args[1]->IsFunction() ? true : false; if (strAccount == EMPTY) { if (data->fRescan) { data->err_msg = std::string("No account name provided."); goto rescan; } else { return NanThrowError("No account name provided."); } } // Call to: EnsureWalletIsUnlocked() if (pwalletMain->IsLocked()) { if (data->fRescan) { data->err_msg = std::string("Please enter the wallet passphrase with walletpassphrase first."); goto rescan; } else { return NanThrowError("Please enter the wallet passphrase with walletpassphrase first."); } } CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(strSecret); if (!fGood) { if (data->fRescan) { data->err_msg = std::string("Invalid private key encoding"); goto rescan; } else { return NanThrowError("Invalid private key encoding"); } } CKey key = vchSecret.GetKey(); if (!key.IsValid()) { if (data->fRescan) { data->err_msg = std::string("Private key outside allowed range"); goto rescan; } else { return NanThrowError("Private key outside allowed range"); } } CPubKey pubkey = key.GetPubKey(); CKeyID vchAddress = pubkey.GetID(); { LOCK2(cs_main, pwalletMain->cs_wallet); pwalletMain->MarkDirty(); pwalletMain->SetAddressBook(vchAddress, strAccount, "receive"); // Don't throw error in case a key is already there if (pwalletMain->HaveKey(vchAddress)) { NanReturnValue(Undefined()); } pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = 1; if (!pwalletMain->AddKeyPubKey(key, pubkey)) { if (data->fRescan) { data->err_msg = std::string("Error adding key to wallet"); goto rescan; } else { return NanThrowError("Error adding key to wallet"); } } // whenever a key is imported, we need to scan the whole chain pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value' } if (data->fRescan) { goto rescan; } NanReturnValue(Undefined()); } static void async_import_key(uv_work_t *req) { async_import_key_data* data = static_cast(req->data); if (data->err_msg != "") { return; } if (data->fRescan) { // This may take a long time, do it on the libuv thread pool: pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); } } static void async_import_key_after(uv_work_t *req) { NanScope(); async_import_key_data* data = static_cast(req->data); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } else { const unsigned argc = 2; Local argv[argc] = { Local::New(Null()), Local::New(True()) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); delete data; delete req; } /** * WalletDumpWallet() * bitcoindjs.walletDumpWallet(options, callback) * Dump wallet to bitcoind plaintext format. */ NAN_METHOD(WalletDumpWallet) { NanScope(); if (args.Length() < 2 || !args[0]->IsObject() || !args[1]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.walletDumpWallet(options, callback)"); } async_dump_wallet_data *data = new async_dump_wallet_data(); data->err_msg = std::string(""); Local options = Local::Cast(args[0]); Local callback = Local::Cast(args[1]); String::Utf8Value path_(options->Get(NanNew("path"))->ToString()); std::string path = std::string(*path_); // Call to: EnsureWalletIsUnlocked() if (pwalletMain->IsLocked()) { data->err_msg = std::string("Please enter the wallet passphrase with walletpassphrase first."); } data->path = path; data->callback = Persistent::New(callback); uv_work_t *req = new uv_work_t(); req->data = data; int status = uv_queue_work(uv_default_loop(), req, async_dump_wallet, (uv_after_work_cb)async_dump_wallet_after); assert(status == 0); NanReturnValue(Undefined()); } static void async_dump_wallet(uv_work_t *req) { async_dump_wallet_data* data = static_cast(req->data); if (data->err_msg != "") { return; } std::string path = data->path; ofstream file; file.open(path.c_str()); if (!file.is_open()) { data->err_msg = std::string("Cannot open wallet dump file"); } std::map mapKeyBirth; std::set setKeyPool; pwalletMain->GetKeyBirthTimes(mapKeyBirth); pwalletMain->GetAllReserveKeys(setKeyPool); // sort time/key pairs std::vector > vKeyBirth; for (std::map::const_iterator it = mapKeyBirth.begin(); it != mapKeyBirth.end(); it++) { vKeyBirth.push_back(std::make_pair(it->second, it->first)); } mapKeyBirth.clear(); std::sort(vKeyBirth.begin(), vKeyBirth.end()); // produce output file << strprintf("# Wallet dump created by bitcoind.js %s (%s)\n", CLIENT_BUILD, CLIENT_DATE); file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime())); file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString()); file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime())); file << "\n"; for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { const CKeyID &keyid = it->second; std::string strTime = EncodeDumpTime(it->first); std::string strAddr = CBitcoinAddress(keyid).ToString(); CKey key; if (pwalletMain->GetKey(keyid, key)) { if (pwalletMain->mapAddressBook.count(keyid)) { file << strprintf("%s %s label=%s # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, EncodeDumpString(pwalletMain->mapAddressBook[keyid].name), strAddr); } else if (setKeyPool.count(keyid)) { file << strprintf("%s %s reserve=1 # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, strAddr); } else { file << strprintf("%s %s change=1 # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, strAddr); } } } file << "\n"; file << "# End of dump\n"; file.close(); } static void async_dump_wallet_after(uv_work_t *req) { NanScope(); async_dump_wallet_data* data = static_cast(req->data); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } else { const unsigned argc = 2; Local argv[argc] = { Local::New(Null()), Local::New(NanNew(data->path)) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); delete data; delete req; } /** * WalletImportWallet() * bitcoindjs.walletImportWallet(options, callback) * Import bitcoind wallet from plaintext format. */ NAN_METHOD(WalletImportWallet) { NanScope(); if (args.Length() < 2 || !args[0]->IsObject() || !args[1]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.walletImportWallet(options, callback)"); } async_import_wallet_data *data = new async_import_wallet_data(); data->err_msg = std::string(""); Local options = Local::Cast(args[0]); Local callback = Local::Cast(args[1]); String::Utf8Value path_(options->Get(NanNew("path"))->ToString()); std::string path = std::string(*path_); // Call to: EnsureWalletIsUnlocked() if (pwalletMain->IsLocked()) { data->err_msg = std::string("Please enter the wallet passphrase with walletpassphrase first."); } data->path = path; data->callback = Persistent::New(callback); uv_work_t *req = new uv_work_t(); req->data = data; int status = uv_queue_work(uv_default_loop(), req, async_import_wallet, (uv_after_work_cb)async_import_wallet_after); assert(status == 0); NanReturnValue(Undefined()); } static void async_import_wallet(uv_work_t *req) { async_import_wallet_data* data = static_cast(req->data); std::string path = data->path; ifstream file; file.open(path.c_str(), std::ios::in | std::ios::ate); if (!file.is_open()) { data->err_msg = std::string("Cannot open wallet dump file"); } int64_t nTimeBegin = chainActive.Tip()->GetBlockTime(); bool fGood = true; int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); file.seekg(0, file.beg); pwalletMain->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI while (file.good()) { pwalletMain->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))) ); std::string line; std::getline(file, line); if (line.empty() || line[0] == '#') { continue; } std::vector vstr; boost::split(vstr, line, boost::is_any_of(" ")); if (vstr.size() < 2) { continue; } CBitcoinSecret vchSecret; if (!vchSecret.SetString(vstr[0])) { continue; } CKey key = vchSecret.GetKey(); CPubKey pubkey = key.GetPubKey(); CKeyID keyid = pubkey.GetID(); if (pwalletMain->HaveKey(keyid)) { // LogPrintf("Skipping import of %s (key already present)\n", CBitcoinAddress(keyid).ToString()); continue; } int64_t nTime = DecodeDumpTime(vstr[1]); std::string strLabel; bool fLabel = true; for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { if (boost::algorithm::starts_with(vstr[nStr], "#")) { break; } if (vstr[nStr] == "change=1") { fLabel = false; } if (vstr[nStr] == "reserve=1") { fLabel = false; } if (boost::algorithm::starts_with(vstr[nStr], "label=")) { strLabel = DecodeDumpString(vstr[nStr].substr(6)); fLabel = true; } } // LogPrintf("Importing %s...\n", CBitcoinAddress(keyid).ToString()); if (!pwalletMain->AddKeyPubKey(key, pubkey)) { fGood = false; continue; } pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime; if (fLabel) { pwalletMain->SetAddressBook(keyid, strLabel, "receive"); } nTimeBegin = std::min(nTimeBegin, nTime); } file.close(); pwalletMain->ShowProgress("", 100); // hide progress dialog in GUI CBlockIndex *pindex = chainActive.Tip(); while (pindex && pindex->pprev && pindex->GetBlockTime() > nTimeBegin - 7200) { pindex = pindex->pprev; } if (!pwalletMain->nTimeFirstKey || nTimeBegin < pwalletMain->nTimeFirstKey) { pwalletMain->nTimeFirstKey = nTimeBegin; } // LogPrintf("Rescanning last %i blocks\n", chainActive.Height() - pindex->nHeight + 1); pwalletMain->ScanForWalletTransactions(pindex); pwalletMain->MarkDirty(); if (!fGood) { data->err_msg = std::string("Cannot open wallet dump file"); } } static void async_import_wallet_after(uv_work_t *req) { NanScope(); async_import_wallet_data* data = static_cast(req->data); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } else { const unsigned argc = 2; Local argv[argc] = { Local::New(Null()), Local::New(NanNew(data->path)) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); delete data; delete req; } /** * WalletChangeLabel() * bitcoindjs.walletChangeLabel(options) * Change account label */ NAN_METHOD(WalletChangeLabel) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletChangeLabel(options)"); } Local options = Local::Cast(args[0]); std::string strAccount = std::string(EMPTY); if (options->Get(NanNew("account"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("account"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("label"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("label"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("name"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("name"))->ToString()); strAccount = std::string(*account_); } std::string addr = std::string(""); if (options->Get(NanNew("address"))->IsString()) { String::Utf8Value addr_(options->Get(NanNew("address"))->ToString()); addr = std::string(*addr_); } if (strAccount == EMPTY && addr == "") { return NanThrowError("No address or account name entered."); } if (strAccount == EMPTY && addr != "") { BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook) { const CBitcoinAddress& address = item.first; const string& strName = item.second.name; if (address.ToString() == addr) { strAccount = strName; break; } } } if (addr == "" && strAccount != EMPTY) { BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook) { const CBitcoinAddress& address = item.first; const string& strName = item.second.name; if (strName == strAccount) { addr = address.ToString(); break; } } } // If it isn't our address, create a recipient: CTxDestination dest = CBitcoinAddress(addr).Get(); if (!IsMine(*pwalletMain, dest)) { pwalletMain->SetAddressBook(dest, strAccount, "send"); pwalletMain->SetAddressBook(dest, strAccount, "send"); NanReturnValue(True()); } // Rename our address: pwalletMain->SetAddressBook(dest, strAccount, "receive"); NanReturnValue(True()); } /** * WalletDeleteAccount() * bitcoindjs.walletDeleteAccount(options) * Delete account and all associated addresses */ NAN_METHOD(WalletDeleteAccount) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletDeleteAccount(options)"); } Local options = Local::Cast(args[0]); std::string strAccount = std::string(EMPTY); if (options->Get(NanNew("account"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("account"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("label"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("label"))->ToString()); strAccount = std::string(*account_); } if (options->Get(NanNew("name"))->IsString()) { String::Utf8Value account_(options->Get(NanNew("name"))->ToString()); strAccount = std::string(*account_); } std::string addr = std::string(""); if (options->Get(NanNew("address"))->IsString()) { String::Utf8Value addr_(options->Get(NanNew("address"))->ToString()); addr = std::string(*addr_); } // LOCK2(cs_main, pwalletMain->cs_wallet); CWalletDB walletdb(pwalletMain->strWalletFile); CAccount account; walletdb.ReadAccount(strAccount, account); if (strAccount == EMPTY) { BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook) { const CBitcoinAddress& address = item.first; const string& strName = item.second.name; if (address.ToString() == addr) { strAccount = strName; break; } } } if (strAccount == EMPTY) { if (addr == "") { return NanThrowError("No account name specified."); } else { return NanThrowError("No account tied to specified address."); } } // Find all addresses that have the given account BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook) { const CBitcoinAddress& address = item.first; const string& strName = item.second.name; if (strName == strAccount) { walletdb.EraseName(address.ToString()); walletdb.ErasePurpose(address.ToString()); } } NanReturnValue(True()); } /** * WalletIsMine() * bitcoindjs.walletIsMine(options) * Check whether address or scriptPubKey is owned by wallet. */ NAN_METHOD(WalletIsMine) { NanScope(); if (args.Length() < 1 || !args[0]->IsObject()) { return NanThrowError( "Usage: bitcoindjs.walletIsMine(options)"); } Local options = Local::Cast(args[0]); std::string addr = std::string(""); std::string spk = std::string(""); if (options->Get(NanNew("address"))->IsString()) { String::Utf8Value s_(options->Get(NanNew("address"))->ToString()); addr = std::string(*s_); } if (options->Get(NanNew("scriptPubKey"))->IsString()) { String::Utf8Value s_(options->Get(NanNew("scriptPubKey"))->ToString()); spk = std::string(*s_); } // Bitcoin address CScript scriptPubKey; if (addr != "") { CBitcoinAddress address = CBitcoinAddress(addr); if (!address.IsValid()) { return NanThrowError("Invalid Bitcoin address"); } scriptPubKey = GetScriptForDestination(address.Get()); } else { scriptPubKey << ParseHex(spk); } bool is_mine = IsMine(*pwalletMain, scriptPubKey); NanReturnValue(NanNew(is_mine)); } /** * WalletRescan() * bitcoindjs.walletRescan(options, callback) * Rescan blockchain */ NAN_METHOD(WalletRescan) { NanScope(); if (args.Length() < 2 || !args[0]->IsObject() || !args[1]->IsFunction()) { return NanThrowError( "Usage: bitcoindjs.walletRescan(options, callback)"); } async_rescan_data *data = new async_rescan_data(); //Local options = Local::Cast(args[0]); Local callback = Local::Cast(args[1]); data->err_msg = std::string(""); data->callback = Persistent::New(callback); uv_work_t *req = new uv_work_t(); req->data = data; int status = uv_queue_work(uv_default_loop(), req, async_rescan, (uv_after_work_cb)async_rescan_after); assert(status == 0); NanReturnValue(Undefined()); } static void async_rescan(uv_work_t *req) { // async_rescan_data* data = static_cast(req->data); // This may take a long time, do it on the libuv thread pool: pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); } static void async_rescan_after(uv_work_t *req) { NanScope(); async_rescan_data* data = static_cast(req->data); if (data->err_msg != "") { Local err = Exception::Error(NanNew(data->err_msg)); const unsigned argc = 1; Local argv[argc] = { err }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } else { const unsigned argc = 2; Local argv[argc] = { Local::New(Null()), Local::New(True()) }; TryCatch try_catch; data->callback->Call(Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { node::FatalException(try_catch); } } data->callback.Dispose(); delete data; delete req; } /** * Conversions * cblock_to_jsblock(cblock, cblock_index, jsblock, is_new) * ctx_to_jstx(ctx, block_hash, 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 block_hash = cblock.GetHash(); jsblock->Set(NanNew("hash"), NanNew(block_hash.GetHex())); CMerkleTx txGen(cblock.vtx[0]); txGen.SetMerkleBranch(cblock); jsblock->Set(NanNew("confirmations"), NanNew((int)txGen.GetDepthInMainChain())->ToInt32()); 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, block_hash, 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 inline void ctx_to_jstx(const CTransaction& ctx, uint256 block_hash, Local jstx) { // With v0.9.0 // jstx->Set(NanNew("mintxfee"), NanNew((int64_t)ctx.nMinTxFee)->ToInteger()); // jstx->Set(NanNew("minrelaytxfee"), NanNew((int64_t)ctx.nMinRelayTxFee)->ToInteger()); 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 (GetTransaction(txin.prevout.hash, prev_tx, block_hash, true)) { 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 { 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); CWalletTx cwtx(pwalletMain, ctx); // XXX Determine wether this is our transaction bool is_mine = cwtx.hashBlock != 0; jstx->Set(NanNew("ismine"), NanNew(is_mine)); // Find block hash if it's in our wallet if (block_hash == 0 && is_mine) { block_hash = cwtx.hashBlock; } if (block_hash != 0) { jstx->Set(NanNew("blockhash"), NanNew(block_hash.GetHex())); if (ctx.IsCoinBase()) { jstx->Set(NanNew("generated"), NanNew(true)); } if (mapBlockIndex.count(block_hash) > 0) { CBlockIndex* pindex = mapBlockIndex[block_hash]; jstx->Set(NanNew("confirmations"), NanNew(pindex->nHeight)); jstx->Set(NanNew("blockindex"), 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)); jstx->Set(NanNew("blockindex"), NanNew(-1)); jstx->Set(NanNew("blocktime"), NanNew(0)); jstx->Set(NanNew("time"), NanNew(0)); jstx->Set(NanNew("timereceived"), NanNew(0)); } if (!is_mine) { jstx->Set(NanNew("walletconflicts"), NanNew()); } else { // XXX If the tx is ours int confirms = cwtx.GetDepthInMainChain(); jstx->Set(NanNew("confirmations"), NanNew(confirms)); Local conflicts = NanNew(); int co = 0; BOOST_FOREACH(const uint256& conflict, cwtx.GetConflicts()) { conflicts->Set(co++, NanNew(conflict.GetHex())); } jstx->Set(NanNew("walletconflicts"), conflicts); jstx->Set(NanNew("time"), NanNew(cwtx.GetTxTime())); jstx->Set(NanNew("timereceived"), NanNew((int64_t)cwtx.nTimeReceived)); } } else { jstx->Set(NanNew("blockhash"), NanNew(uint256(0).GetHex())); jstx->Set(NanNew("confirmations"), NanNew(-1)); jstx->Set(NanNew("generated"), NanNew(false)); jstx->Set(NanNew("blockhash"), NanNew(uint256(0).GetHex())); jstx->Set(NanNew("blockindex"), 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::AsciiValue 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::AsciiValue 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::AsciiValue 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)); //if (ctx.IsCoinBase()) { // txin.prevout.hash = uint256(0); // txin.prevout.n = (unsigned int)0; //} else { String::AsciiValue 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::AsciiValue 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::AsciiValue 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 static ctx_list * read_addr(const std::string addr) { ctx_list *head = new ctx_list(); ctx_list *cur = NULL; 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(); const char *k = slKey.ToString().c_str(); if (k[0] == 'b') { char *blockhash_ = strdup(k); blockhash_++; std::string sblockhash = std::string(blockhash_); uint256 blockhash(sblockhash); leveldb::Slice slValue = pcursor->value(); CDataStream ssValue(slValue.data(), slValue.data() + slValue.size(), SER_DISK, CLIENT_VERSION); CBlock cblock; ssValue >> cblock; 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 found; } 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 found; } } } } found: pcursor->Next(); } catch (std::exception &e) { // error("%s : Deserialize or I/O error - %s", __func__, e.what()); delete pcursor; return head; } } delete pcursor; return head; } #endif static int64_t SatoshiFromAmount(const CAmount& amount) { // ./core.h : static const int64_t COIN = 100000000; // ValueFromAmount: // return (double)amount / (double)COIN; return (int64_t)amount; } /** * 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, "stop", StopBitcoind); NODE_SET_METHOD(target, "stopping", IsStopping); NODE_SET_METHOD(target, "stopped", IsStopped); NODE_SET_METHOD(target, "getBlock", GetBlock); NODE_SET_METHOD(target, "getTx", GetTx); NODE_SET_METHOD(target, "broadcastTx", BroadcastTx); NODE_SET_METHOD(target, "verifyBlock", VerifyBlock); NODE_SET_METHOD(target, "verifyTransaction", VerifyTransaction); NODE_SET_METHOD(target, "fillTransaction", FillTransaction); NODE_SET_METHOD(target, "getInfo", GetInfo); NODE_SET_METHOD(target, "getPeerInfo", GetPeerInfo); NODE_SET_METHOD(target, "getAddresses", GetAddresses); NODE_SET_METHOD(target, "walletGetRecipients", WalletGetRecipients); NODE_SET_METHOD(target, "walletSetRecipient", WalletSetRecipient); NODE_SET_METHOD(target, "walletRemoveRecipient", WalletRemoveRecipient); NODE_SET_METHOD(target, "getProgress", GetProgress); NODE_SET_METHOD(target, "setGenerate", SetGenerate); NODE_SET_METHOD(target, "getGenerate", GetGenerate); NODE_SET_METHOD(target, "getMiningInfo", GetMiningInfo); NODE_SET_METHOD(target, "getAddrTransactions", GetAddrTransactions); NODE_SET_METHOD(target, "getBestBlock", GetBestBlock); 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_SET_METHOD(target, "walletNewAddress", WalletNewAddress); NODE_SET_METHOD(target, "walletGetAccountAddress", WalletGetAccountAddress); NODE_SET_METHOD(target, "walletSetAccount", WalletSetAccount); NODE_SET_METHOD(target, "walletGetAccount", WalletGetAccount); NODE_SET_METHOD(target, "walletSendTo", WalletSendTo); NODE_SET_METHOD(target, "walletSignMessage", WalletSignMessage); NODE_SET_METHOD(target, "walletVerifyMessage", WalletVerifyMessage); NODE_SET_METHOD(target, "walletGetBalance", WalletGetBalance); NODE_SET_METHOD(target, "walletCreateMultiSigAddress", WalletCreateMultiSigAddress); NODE_SET_METHOD(target, "walletGetUnconfirmedBalance", WalletGetUnconfirmedBalance); NODE_SET_METHOD(target, "walletSendFrom", WalletSendFrom); NODE_SET_METHOD(target, "walletMove", WalletMove); NODE_SET_METHOD(target, "walletListTransactions", WalletListTransactions); NODE_SET_METHOD(target, "walletReceivedByAddress", WalletReceivedByAddress); NODE_SET_METHOD(target, "walletListAccounts", WalletListAccounts); NODE_SET_METHOD(target, "walletGetTransaction", WalletGetTransaction); NODE_SET_METHOD(target, "walletBackup", WalletBackup); NODE_SET_METHOD(target, "walletPassphrase", WalletPassphrase); NODE_SET_METHOD(target, "walletPassphraseChange", WalletPassphraseChange); NODE_SET_METHOD(target, "walletLock", WalletLock); NODE_SET_METHOD(target, "walletEncrypt", WalletEncrypt); NODE_SET_METHOD(target, "walletEncrypted", WalletEncrypted); NODE_SET_METHOD(target, "walletDumpKey", WalletDumpKey); NODE_SET_METHOD(target, "walletKeyPoolRefill", WalletKeyPoolRefill); NODE_SET_METHOD(target, "walletSetTxFee", WalletSetTxFee); NODE_SET_METHOD(target, "walletImportKey", WalletImportKey); NODE_SET_METHOD(target, "walletDumpWallet", WalletDumpWallet); NODE_SET_METHOD(target, "walletImportWallet", WalletImportWallet); NODE_SET_METHOD(target, "walletChangeLabel", WalletChangeLabel); NODE_SET_METHOD(target, "walletDeleteAccount", WalletDeleteAccount); NODE_SET_METHOD(target, "walletIsMine", WalletIsMine); NODE_SET_METHOD(target, "walletRescan", WalletRescan); } NODE_MODULE(bitcoindjs, init)