From 7283d1bc22e96d5a98bbf3d9d7a05c02e2310e4c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 26 Sep 2014 15:43:46 -0700 Subject: [PATCH] wallet work. listaccounts is now extremely useful. --- example/index.js | 4 + lib/bitcoind.js | 52 ++++++++----- src/bitcoindjs.cc | 194 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 212 insertions(+), 38 deletions(-) diff --git a/example/index.js b/example/index.js index 5073070c..7e027d64 100755 --- a/example/index.js +++ b/example/index.js @@ -25,6 +25,10 @@ bitcoind.on('error', function(err) { bitcoind.on('open', function(status) { print('status="%s"', status); + setTimeout(function() { + print(bitcoind.wallet.listAccounts()); + }, 2000); + if (argv.blocks) { getBlocks(bitcoind); } diff --git a/lib/bitcoind.js b/lib/bitcoind.js index 8edac06b..6c6279fe 100644 --- a/lib/bitcoind.js +++ b/lib/bitcoind.js @@ -32,6 +32,7 @@ function Bitcoin(options) { EventEmitter.call(this); this.options = options; + this.wallet = Wallet; Bitcoin.global = this; @@ -653,87 +654,95 @@ script.encode = function encode(s) { /** * Wallet + * Singleton */ -function Wallet() {} +function Wallet() { + var obj = function() { + return obj; + }; + Object.keys(Wallet.prototype).forEach(function(key) { + obj[key] = Wallet.prototype[key]; + }); + return obj; +} Wallet.prototype.createAddress = function(name) { return bitcoindjs.walletNewAddress({ name: name }); }; Wallet.prototype.getAccountAddress = function(options) { - return bitcoindjs.getAccountAddress(options); + return bitcoindjs.getAccountAddress(options || {}); }; Wallet.prototype.setAccount = function(options) { - return bitcoindjs.setAccount(options); + return bitcoindjs.setAccount(options || {}); }; Wallet.prototype.getAccount = function(options) { - return bitcoindjs.getAccount(options); + return bitcoindjs.getAccount(options || {}); }; Wallet.prototype.sendToAddress = function(options) { - return bitcoindjs.sendToAddress(options); + return bitcoindjs.sendToAddress(options || {}); }; Wallet.prototype.signMessage = function(options) { - return bitcoindjs.signMessage(options); + return bitcoindjs.signMessage(options || {}); }; Wallet.prototype.verifyMessage = function(options) { - return bitcoindjs.verifyMessage(options); + return bitcoindjs.verifyMessage(options || {}); }; Wallet.prototype.getBalance = function(options) { - return bitcoindjs.getBalance(options); + return bitcoindjs.getBalance(options || {}); }; Wallet.prototype.getUnconfirmedBalance = function(options) { - return bitcoindjs.getUnconfirmedBalance(options); + return bitcoindjs.getUnconfirmedBalance(options || {}); }; Wallet.prototype.sendFrom = function(options) { - return bitcoindjs.sendFrom(options); + return bitcoindjs.sendFrom(options || {}); }; Wallet.prototype.listTransactions = function(options) { - return bitcoindjs.listTransactions(options); + return bitcoindjs.listTransactions(options || {}); }; Wallet.prototype.listAccounts = function(options) { - return bitcoindjs.listAccounts(options); + return bitcoindjs.listAccounts(options || {}); }; Wallet.prototype.getTransaction = function(options) { - return bitcoindjs.getTransaction(options); + return bitcoindjs.getTransaction(options || {}); }; Wallet.prototype.backupWallet = function(options) { - return bitcoindjs.backupWallet(options); + return bitcoindjs.backupWallet(options || {}); }; Wallet.prototype.walletPassphrase = function(options) { - return bitcoindjs.walletPassphrase(options); + return bitcoindjs.walletPassphrase(options || {}); }; Wallet.prototype.walletPassphraseChange = function(options) { - return bitcoindjs.walletPassphraseChange(options); + return bitcoindjs.walletPassphraseChange(options || {}); }; Wallet.prototype.walletLock = function(options) { - return bitcoindjs.walletLock(options); + return bitcoindjs.walletLock(options || {}); }; Wallet.prototype.encryptWallet = function(options) { - return bitcoindjs.encryptWallet(options); + return bitcoindjs.encryptWallet(options || {}); }; Wallet.prototype.setTxFee = function(options) { - return bitcoindjs.setTxFee(options); + return bitcoindjs.setTxFee(options || {}); }; -// singleton Wallet = new Wallet; /** @@ -877,6 +886,9 @@ exports.Transaction = Transaction; exports.transaction = Transaction; exports.tx = Transaction; +exports.Wallet = Wallet; +exports.wallet = Wallet; + exports.script = script; exports.utils = utils; diff --git a/src/bitcoindjs.cc b/src/bitcoindjs.cc index f207a888..4a59ba78 100644 --- a/src/bitcoindjs.cc +++ b/src/bitcoindjs.cc @@ -97,6 +97,13 @@ using namespace std; using namespace boost; extern void DetectShutdownThread(boost::thread_group*); +extern int nScriptCheckThreads; +extern bool fDaemon; +extern std::map mapArgs; +#ifdef ENABLE_WALLET +extern std::string strWalletFile; +extern CWallet *pwalletMain; +#endif /** * Node and Templates @@ -342,6 +349,10 @@ static void async_start_node_work(uv_work_t *req) { async_node_data *node_data = static_cast(req->data); start_node(); + while (!pwalletMain) { + useconds_t usec = 100 * 1000; + usleep(usec); + } node_data->result = (char *)strdup("start_node(): bitcoind opened."); } @@ -1167,6 +1178,39 @@ NAN_METHOD(VerifyTransaction) { * Wallet */ +int64_t +GetAccountBalance(CWalletDB& walletdb, const string& strAccount, int nMinDepth) { + int64_t nBalance = 0; + + // Tally wallet transactions + for (map::iterator it = pwalletMain->mapWallet.begin(); + it != pwalletMain->mapWallet.end(); ++it) { + const CWalletTx& wtx = (*it).second; + if (!IsFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0) { + continue; + } + + int64_t nReceived, nSent, nFee; + wtx.GetAccountAmounts(strAccount, nReceived, nSent, nFee); + + if (nReceived != 0 && wtx.GetDepthInMainChain() >= nMinDepth) { + nBalance += nReceived; + } + nBalance -= nSent + nFee; + } + + // Tally internal accounting entries + nBalance += walletdb.GetAccountCreditDebit(strAccount); + + return nBalance; +} + +int64_t +GetAccountBalance(const string& strAccount, int nMinDepth) { + CWalletDB walletdb(pwalletMain->strWalletFile); + return GetAccountBalance(walletdb, strAccount, nMinDepth); +} + NAN_METHOD(WalletNewAddress) { NanScope(); @@ -1189,7 +1233,10 @@ NAN_METHOD(WalletNewAddress) { if (!pwalletMain->GetKeyFromPool(newKey)) { // return NanThrowError("Keypool ran out, please call keypoolrefill first"); - EnsureWalletIsUnlocked(); + // EnsureWalletIsUnlocked(); + if (pwalletMain->IsLocked()) { + return NanThrowError("Please enter the wallet passphrase with walletpassphrase first."); + } pwalletMain->TopUpKeyPool(100); if (pwalletMain->GetKeyPoolSize() < 100) { return NanThrowError("Error refreshing keypool."); @@ -1264,7 +1311,6 @@ NAN_METHOD(SendToAddress) { String::Utf8Value addr_(options->Get(NanNew("address"))->ToString()); std::string addr = std::string(*addr_); - string strAccount = from; CBitcoinAddress address(addr); if (!address.IsValid()) { @@ -1272,27 +1318,29 @@ NAN_METHOD(SendToAddress) { } // Amount - int64_t nAmount = options->Get(NanNew("amount"))->IntegerValue() + int64_t nAmount = options->Get(NanNew("amount"))->IntegerValue(); // Wallet comments CWalletTx wtx; - wtx.strFromAccount = strAccount; - if (options->Get(NanNew("comment"))) { + 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"))) { + if (options->Get(NanNew("to"))->IsString()) { String::Utf8Value to_(options->Get(NanNew("to"))->ToString()); std::string to = std::string(*to_); wtx.mapValue["to"] = to; } - EnsureWalletIsUnlocked(); + // EnsureWalletIsUnlocked(); + if (pwalletMain->IsLocked()) { + return NanThrowError("Please enter the wallet passphrase with walletpassphrase first."); + } string strError = pwalletMain->SendMoneyToDestination(address.Get(), nAmount, wtx); if (strError != "") { - return NanThrowError(strError); + return NanThrowError(strError.c_str()); } std::string tx_hash = wtx.GetHash().GetHex(); @@ -1386,26 +1434,29 @@ NAN_METHOD(SendFrom) { return NanThrowError("Invalid Bitcoin address"); } - int64_t nAmount = options->Get(NanNew("amount"))->IntegerValue() + int64_t nAmount = options->Get(NanNew("amount"))->IntegerValue(); int nMinDepth = 1; - if (options->Get(NanNew("minDepth"))) { + if (options->Get(NanNew("minDepth"))->IsNumber()) { nMinDepth = options->Get(NanNew("minDepth"))->IntegerValue(); } CWalletTx wtx; wtx.strFromAccount = strAccount; - if (options->Get(NanNew("comment"))) { + 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"))) { + if (options->Get(NanNew("to"))->IsString()) { String::Utf8Value to_(options->Get(NanNew("to"))->ToString()); std::string to = std::string(*to_); wtx.mapValue["to"] = to; } - EnsureWalletIsUnlocked(); + // EnsureWalletIsUnlocked(); + if (pwalletMain->IsLocked()) { + return NanThrowError("Please enter the wallet passphrase with walletpassphrase first."); + } // Check funds int64_t nBalance = GetAccountBalance(strAccount, nMinDepth); @@ -1416,7 +1467,7 @@ NAN_METHOD(SendFrom) { // Send string strError = pwalletMain->SendMoneyToDestination(address.Get(), nAmount, wtx); if (strError != "") { - return NanThrowError(strError); + return NanThrowError(strError.c_str()); } std::string tx_hash = wtx.GetHash().GetHex(); @@ -1448,12 +1499,119 @@ NAN_METHOD(ListAccounts) { "Usage: bitcoindjs.listAccounts(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_); - NanReturnValue(Undefined()); + int nMinDepth = 1; + if (options->Get(NanNew("minDepth"))->IsNumber()) { + nMinDepth = options->Get(NanNew("minDepth"))->IntegerValue(); + } + + map mapAccountBalances; + BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& entry, pwalletMain->mapAddressBook) { + if (IsMine(*pwalletMain, entry.first)) { // 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; + int64_t nFee; + string strSentAccount; + list > listReceived; + list > listSent; + int nDepth = wtx.GetDepthInMainChain(); + if (wtx.GetBlocksToMaturity() > 0 || nDepth < 0) { + continue; + } + wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount); + mapAccountBalances[strSentAccount] -= nFee; + BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64_t)& s, listSent) { + mapAccountBalances[strSentAccount] -= s.second; + } + if (nDepth >= nMinDepth) { + BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64_t)& r, listReceived) { + if (pwalletMain->mapAddressBook.count(r.first)) { + mapAccountBalances[pwalletMain->mapAddressBook[r.first].name] += r.second; + } else { + mapAccountBalances[""] += r.second; + } + } + } + } + + 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) { + // Canonical method: +#if 0 + obj->Set(NanNew(accountBalance.first.c_str()), NanNew(accountBalance.second)); +#endif + + // Newer method - include addresses: +#if 0 + 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 string& strName = item.second.name; + if (strName == accountBalance.first) { + addr->Set(i, NanNew(address.ToString())); + i++; + } + } + entry->Set(NanNew("addresses"), addr); + obj->Set(NanNew(accountBalance.first), entry); +#endif + + // Newest method: Include pub and priv key. + 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 string& strName = item.second.name; + if (strName == accountBalance.first) { + Local a = NanNew(); + a->Set(NanNew("address"), NanNew(address.ToString())); + + // if (!address.SetString(address.ToString())) { + // return NanThrowError("Invalid Bitcoin address"); + // } + 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"); + } + 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); + obj->Set(NanNew(accountBalance.first), entry); + } + + NanReturnValue(obj); } NAN_METHOD(GetTransaction) {