diff --git a/lib/bitcoind.js b/lib/bitcoind.js index 7dfa3c29..16beb73c 100644 --- a/lib/bitcoind.js +++ b/lib/bitcoind.js @@ -751,6 +751,14 @@ Wallet.prototype.importKey = function(options) { return bitcoindjs.walletImportKey(options || {}); }; +Wallet.prototype.dump = function(options, callback) { + return bitcoindjs.walletDumpWallet(options || {}, callback); +}; + +Wallet.prototype.import = function(options, callback) { + return bitcoindjs.walletImportWallet(options || {}, callback); +}; + Wallet = new Wallet; /** diff --git a/src/bitcoindjs.cc b/src/bitcoindjs.cc index 7fc0c386..f5c7e6c0 100644 --- a/src/bitcoindjs.cc +++ b/src/bitcoindjs.cc @@ -198,6 +198,8 @@ NAN_METHOD(WalletDumpPrivKey); NAN_METHOD(WalletKeyPoolRefill); NAN_METHOD(WalletSetTxFee); NAN_METHOD(WalletImportKey); +NAN_METHOD(WalletDumpWallet); +NAN_METHOD(WalletImportWallet); /** * Node.js Internal Function Templates @@ -406,6 +408,26 @@ struct async_import_key_data { 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; +}; + /** * Functions */ @@ -3574,7 +3596,321 @@ async_import_key_after(uv_work_t *req) { const unsigned argc = 2; Local argv[argc] = { Local::New(Null()), - 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(); + + 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()) { + return NanThrowError("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); + + 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 Bitcoin %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.empty()) { + Local err = Exception::Error(String::New(data->err_msg.c_str())); + 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(); + + 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()) { + return NanThrowError("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()) { + return NanThrowError("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.empty()) { + Local err = Exception::Error(String::New(data->err_msg.c_str())); + 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); @@ -3973,6 +4309,8 @@ init(Handle target) { 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_MODULE(bitcoindjs, init)