diff --git a/qa/rpc-tests/importmulti.py b/qa/rpc-tests/importmulti.py index 960cb63d7..5c536f2f4 100755 --- a/qa/rpc-tests/importmulti.py +++ b/qa/rpc-tests/importmulti.py @@ -1,293 +1,360 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2016 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * class ImportMultiTest (BitcoinTestFramework): def __init__(self): super().__init__() self.num_nodes = 2 self.setup_clean_chain = True def setup_network(self, split=False): self.nodes = start_nodes(2, self.options.tmpdir) self.is_network_split=False def run_test (self): print ("Mining blocks...") self.nodes[0].generate(1) self.nodes[1].generate(1) # keyword definition PRIV_KEY = 'privkey' PUB_KEY = 'pubkey' ADDRESS_KEY = 'address' SCRIPT_KEY = 'script' node0_address1 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) node0_address2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) node0_address3 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) #Check only one address assert_equal(node0_address1['ismine'], True) #Node 1 sync test assert_equal(self.nodes[1].getblockcount(),1) #Address Test - before import address_info = self.nodes[1].validateaddress(node0_address1['address']) assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], False) # RPC importmulti ----------------------------------------------- # Bitcoin Address print("Should import an address") address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) result = self.nodes[1].importmulti([{ "scriptPubKey": { "address": address['address'] } }]) assert_equal(result[0]['success'], True) address_assert = self.nodes[1].validateaddress(address['address']) assert_equal(address_assert['iswatchonly'], True) assert_equal(address_assert['ismine'], False) # ScriptPubKey + internal print("Should import a scriptPubKey with internal flag") address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) result = self.nodes[1].importmulti([{ "scriptPubKey": address['scriptPubKey'], "internal": True }]) assert_equal(result[0]['success'], True) address_assert = self.nodes[1].validateaddress(address['address']) assert_equal(address_assert['iswatchonly'], True) assert_equal(address_assert['ismine'], False) # ScriptPubKey + !internal print("Should not import a scriptPubKey without internal flag") address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) result = self.nodes[1].importmulti([{ "scriptPubKey": address['scriptPubKey'] }]) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -8) assert_equal(result[0]['error']['message'], 'Internal must be set for hex scriptPubKey') address_assert = self.nodes[1].validateaddress(address['address']) assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['ismine'], False) # Address + Public key + !Internal print("Should import an address with public key") address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) result = self.nodes[1].importmulti([{ "scriptPubKey": { "address": address['address'] }, "pubkeys": [ address['pubkey'] ] }]) assert_equal(result[0]['success'], True) address_assert = self.nodes[1].validateaddress(address['address']) assert_equal(address_assert['iswatchonly'], True) assert_equal(address_assert['ismine'], False) # ScriptPubKey + Public key + internal print("Should import a scriptPubKey with internal and with public key") address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) request = [{ "scriptPubKey": address['scriptPubKey'], "pubkeys": [ address['pubkey'] ], "internal": True }]; result = self.nodes[1].importmulti(request) assert_equal(result[0]['success'], True) address_assert = self.nodes[1].validateaddress(address['address']) assert_equal(address_assert['iswatchonly'], True) assert_equal(address_assert['ismine'], False) # ScriptPubKey + Public key + !internal print("Should not import a scriptPubKey without internal and with public key") address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) request = [{ "scriptPubKey": address['scriptPubKey'], "pubkeys": [ address['pubkey'] ] }]; result = self.nodes[1].importmulti(request) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -8) assert_equal(result[0]['error']['message'], 'Internal must be set for hex scriptPubKey') address_assert = self.nodes[1].validateaddress(address['address']) assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['ismine'], False) # Address + Private key + !watchonly print("Should import an address with private key") address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) result = self.nodes[1].importmulti([{ "scriptPubKey": { "address": address['address'] }, "keys": [ self.nodes[0].dumpprivkey(address['address']) ] }]) assert_equal(result[0]['success'], True) address_assert = self.nodes[1].validateaddress(address['address']) assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['ismine'], True) # Address + Private key + watchonly print("Should not import an address with private key and with watchonly") address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) result = self.nodes[1].importmulti([{ "scriptPubKey": { "address": address['address'] }, "keys": [ self.nodes[0].dumpprivkey(address['address']) ], "watchonly": True }]) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -8) assert_equal(result[0]['error']['message'], 'Incompatibility found between watchonly and keys') address_assert = self.nodes[1].validateaddress(address['address']) assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['ismine'], False) # ScriptPubKey + Private key + internal print("Should import a scriptPubKey with internal and with private key") address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) result = self.nodes[1].importmulti([{ "scriptPubKey": address['scriptPubKey'], "keys": [ self.nodes[0].dumpprivkey(address['address']) ], "internal": True }]) assert_equal(result[0]['success'], True) address_assert = self.nodes[1].validateaddress(address['address']) assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['ismine'], True) # ScriptPubKey + Private key + !internal print("Should not import a scriptPubKey without internal and with private key") address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) result = self.nodes[1].importmulti([{ "scriptPubKey": address['scriptPubKey'], "keys": [ self.nodes[0].dumpprivkey(address['address']) ] }]) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -8) assert_equal(result[0]['error']['message'], 'Internal must be set for hex scriptPubKey') address_assert = self.nodes[1].validateaddress(address['address']) assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['ismine'], False) # P2SH address sig_address_1 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) sig_address_2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) sig_address_3 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['address'], sig_address_2['address'], sig_address_3['pubkey']]) self.nodes[1].generate(100) transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) self.nodes[1].generate(1) transaction = self.nodes[1].gettransaction(transactionid); print("Should import a p2sh") result = self.nodes[1].importmulti([{ "scriptPubKey": { "address": multi_sig_script['address'] } }]) assert_equal(result[0]['success'], True) address_assert = self.nodes[1].validateaddress(multi_sig_script['address']) assert_equal(address_assert['isscript'], True) assert_equal(address_assert['iswatchonly'], True) p2shunspent = self.nodes[1].listunspent(0,999999, [multi_sig_script['address']])[0] assert_equal(p2shunspent['spendable'], False) assert_equal(p2shunspent['solvable'], False) # P2SH + Redeem script sig_address_1 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) sig_address_2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) sig_address_3 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['address'], sig_address_2['address'], sig_address_3['pubkey']]) self.nodes[1].generate(100) transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) self.nodes[1].generate(1) transaction = self.nodes[1].gettransaction(transactionid); print("Should import a p2sh with respective redeem script") result = self.nodes[1].importmulti([{ "scriptPubKey": { "address": multi_sig_script['address'] }, "redeemscript": multi_sig_script['redeemScript'] }]) assert_equal(result[0]['success'], True) p2shunspent = self.nodes[1].listunspent(0,999999, [multi_sig_script['address']])[0] assert_equal(p2shunspent['spendable'], False) assert_equal(p2shunspent['solvable'], True) # P2SH + Redeem script + Private Keys + !Watchonly sig_address_1 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) sig_address_2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) sig_address_3 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['address'], sig_address_2['address'], sig_address_3['pubkey']]) self.nodes[1].generate(100) transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) self.nodes[1].generate(1) transaction = self.nodes[1].gettransaction(transactionid); print("Should import a p2sh with respective redeem script and private keys") result = self.nodes[1].importmulti([{ "scriptPubKey": { "address": multi_sig_script['address'] }, "redeemscript": multi_sig_script['redeemScript'], "keys": [ self.nodes[0].dumpprivkey(sig_address_1['address']), self.nodes[0].dumpprivkey(sig_address_2['address'])] }]) assert_equal(result[0]['success'], True) p2shunspent = self.nodes[1].listunspent(0,999999, [multi_sig_script['address']])[0] assert_equal(p2shunspent['spendable'], False) assert_equal(p2shunspent['solvable'], True) # P2SH + Redeem script + Private Keys + Watchonly sig_address_1 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) sig_address_2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) sig_address_3 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['address'], sig_address_2['address'], sig_address_3['pubkey']]) self.nodes[1].generate(100) transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) self.nodes[1].generate(1) transaction = self.nodes[1].gettransaction(transactionid); print("Should import a p2sh with respective redeem script and private keys") result = self.nodes[1].importmulti([{ "scriptPubKey": { "address": multi_sig_script['address'] }, "redeemscript": multi_sig_script['redeemScript'], "keys": [ self.nodes[0].dumpprivkey(sig_address_1['address']), self.nodes[0].dumpprivkey(sig_address_2['address'])], "watchonly": True }]) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -8) assert_equal(result[0]['error']['message'], 'Incompatibility found between watchonly and keys') - # TODO Consistency tests? + # Address + Public key + !Internal + Wrong pubkey + print("Should not import an address with a wrong public key") + address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) + address2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) + result = self.nodes[1].importmulti([{ + "scriptPubKey": { + "address": address['address'] + }, + "pubkeys": [ address2['pubkey'] ] + }]) + assert_equal(result[0]['success'], False) + assert_equal(result[0]['error']['code'], -5) + assert_equal(result[0]['error']['message'], 'Consistency check failed') + address_assert = self.nodes[1].validateaddress(address['address']) + assert_equal(address_assert['iswatchonly'], False) + assert_equal(address_assert['ismine'], False) + + + # ScriptPubKey + Public key + internal + Wrong pubkey + print("Should not import a scriptPubKey with internal and with a wrong public key") + address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) + address2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) + request = [{ + "scriptPubKey": address['scriptPubKey'], + "pubkeys": [ address2['pubkey'] ], + "internal": True + }]; + result = self.nodes[1].importmulti(request) + assert_equal(result[0]['success'], False) + assert_equal(result[0]['error']['code'], -5) + assert_equal(result[0]['error']['message'], 'Consistency check failed') + address_assert = self.nodes[1].validateaddress(address['address']) + assert_equal(address_assert['iswatchonly'], False) + assert_equal(address_assert['ismine'], False) + # Address + Private key + !watchonly + Wrong private key + print("Should not import an address with a wrong private key") + address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) + address2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) + result = self.nodes[1].importmulti([{ + "scriptPubKey": { + "address": address['address'] + }, + "keys": [ self.nodes[0].dumpprivkey(address2['address']) ] + }]) + assert_equal(result[0]['success'], False) + assert_equal(result[0]['error']['code'], -5) + assert_equal(result[0]['error']['message'], 'Consistency check failed') + address_assert = self.nodes[1].validateaddress(address['address']) + assert_equal(address_assert['iswatchonly'], False) + assert_equal(address_assert['ismine'], False) + + + # ScriptPubKey + Private key + internal + Wrong private key + print("Should not import a scriptPubKey with internal and with a wrong private key") + address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) + address2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress()) + result = self.nodes[1].importmulti([{ + "scriptPubKey": address['scriptPubKey'], + "keys": [ self.nodes[0].dumpprivkey(address2['address']) ], + "internal": True + }]) + assert_equal(result[0]['success'], False) + assert_equal(result[0]['error']['code'], -5) + assert_equal(result[0]['error']['message'], 'Consistency check failed') + address_assert = self.nodes[1].validateaddress(address['address']) + assert_equal(address_assert['iswatchonly'], False) + assert_equal(address_assert['ismine'], False) + if __name__ == '__main__': ImportMultiTest ().main () diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 0297337c2..7b16b4adf 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1,1029 +1,1061 @@ // Copyright (c) 2009-2015 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "base58.h" #include "chain.h" #include "rpc/server.h" #include "init.h" #include "main.h" #include "script/script.h" #include "script/standard.h" #include "sync.h" #include "util.h" #include "utiltime.h" #include "wallet.h" #include "merkleblock.h" #include "core_io.h" #include <fstream> #include <stdint.h> #include <boost/algorithm/string.hpp> #include <boost/date_time/posix_time/posix_time.hpp> #include <univalue.h> #include <boost/assign/list_of.hpp> #include <boost/foreach.hpp> using namespace std; void EnsureWalletIsUnlocked(); bool EnsureWalletIsAvailable(bool avoidException); std::string static EncodeDumpTime(int64_t nTime) { return DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", nTime); } int64_t static DecodeDumpTime(const std::string &str) { static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); static const std::locale loc(std::locale::classic(), new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ")); std::istringstream iss(str); iss.imbue(loc); boost::posix_time::ptime ptime(boost::date_time::not_a_date_time); iss >> ptime; if (ptime.is_not_a_date_time()) return 0; return (ptime - epoch).total_seconds(); } std::string static EncodeDumpString(const std::string &str) { std::stringstream ret; BOOST_FOREACH(unsigned char c, str) { if (c <= 32 || c >= 128 || c == '%') { ret << '%' << HexStr(&c, &c + 1); } else { ret << c; } } return ret.str(); } std::string DecodeDumpString(const std::string &str) { std::stringstream ret; for (unsigned int pos = 0; pos < str.length(); pos++) { unsigned char c = str[pos]; if (c == '%' && pos+2 < str.length()) { c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) | ((str[pos+2]>>6)*9+((str[pos+2]-'0')&15)); pos += 2; } ret << c; } return ret.str(); } UniValue importprivkey(const JSONRPCRequest& request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw runtime_error( "importprivkey \"bitcoinprivkey\" ( \"label\" rescan )\n" "\nAdds a private key (as returned by dumpprivkey) to your wallet.\n" "\nArguments:\n" "1. \"bitcoinprivkey\" (string, required) The private key (see dumpprivkey)\n" "2. \"label\" (string, optional, default=\"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" "\nNote: This call can take minutes to complete if rescan is true.\n" "\nExamples:\n" "\nDump a private key\n" + HelpExampleCli("dumpprivkey", "\"myaddress\"") + "\nImport the private key with rescan\n" + HelpExampleCli("importprivkey", "\"mykey\"") + "\nImport using a label and without rescan\n" + HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false") ); LOCK2(cs_main, pwalletMain->cs_wallet); EnsureWalletIsUnlocked(); string strSecret = request.params[0].get_str(); string strLabel = ""; if (request.params.size() > 1) strLabel = request.params[1].get_str(); // Whether to perform rescan after import bool fRescan = true; if (request.params.size() > 2) fRescan = request.params[2].get_bool(); if (fRescan && fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(strSecret); if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); CKey key = vchSecret.GetKey(); if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); CKeyID vchAddress = pubkey.GetID(); { pwalletMain->MarkDirty(); pwalletMain->SetAddressBook(vchAddress, strLabel, "receive"); // Don't throw error in case a key is already there if (pwalletMain->HaveKey(vchAddress)) return NullUniValue; pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = 1; if (!pwalletMain->AddKeyPubKey(key, pubkey)) throw JSONRPCError(RPC_WALLET_ERROR, "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 (fRescan) { pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); } } return NullUniValue; } void ImportAddress(const CBitcoinAddress& address, const string& strLabel); void ImportScript(const CScript& script, const string& strLabel, bool isRedeemScript) { if (!isRedeemScript && ::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE) throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); pwalletMain->MarkDirty(); if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script)) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); if (isRedeemScript) { if (!pwalletMain->HaveCScript(script) && !pwalletMain->AddCScript(script)) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); ImportAddress(CBitcoinAddress(CScriptID(script)), strLabel); } else { CTxDestination destination; if (ExtractDestination(script, destination)) { pwalletMain->SetAddressBook(destination, strLabel, "receive"); } } } void ImportAddress(const CBitcoinAddress& address, const string& strLabel) { CScript script = GetScriptForDestination(address.Get()); ImportScript(script, strLabel, false); // add to address book or update label if (address.IsValid()) pwalletMain->SetAddressBook(address.Get(), strLabel, "receive"); } UniValue importaddress(const JSONRPCRequest& request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) throw runtime_error( "importaddress \"address\" ( \"label\" rescan p2sh )\n" "\nAdds a script (in hex) or address that can be watched as if it were in your wallet but cannot be used to spend.\n" "\nArguments:\n" "1. \"script\" (string, required) The hex-encoded script (or address)\n" "2. \"label\" (string, optional, default=\"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" "4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n" "\nNote: This call can take minutes to complete if rescan is true.\n" "If you have the full public key, you should call importpubkey instead of this.\n" "\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n" "as change, and not show up in many RPCs.\n" "\nExamples:\n" "\nImport a script with rescan\n" + HelpExampleCli("importaddress", "\"myscript\"") + "\nImport using a label without rescan\n" + HelpExampleCli("importaddress", "\"myscript\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importaddress", "\"myscript\", \"testing\", false") ); string strLabel = ""; if (request.params.size() > 1) strLabel = request.params[1].get_str(); // Whether to perform rescan after import bool fRescan = true; if (request.params.size() > 2) fRescan = request.params[2].get_bool(); if (fRescan && fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); // Whether to import a p2sh version, too bool fP2SH = false; if (request.params.size() > 3) fP2SH = request.params[3].get_bool(); LOCK2(cs_main, pwalletMain->cs_wallet); CBitcoinAddress address(request.params[0].get_str()); if (address.IsValid()) { if (fP2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); ImportAddress(address, strLabel); } else if (IsHex(request.params[0].get_str())) { std::vector<unsigned char> data(ParseHex(request.params[0].get_str())); ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH); } else { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); } if (fRescan) { pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); pwalletMain->ReacceptWalletTransactions(); } return NullUniValue; } UniValue importprunedfunds(const JSONRPCRequest& request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() != 2) throw runtime_error( "importprunedfunds\n" "\nImports funds without rescan. Corresponding address or script must previously be included in wallet. Aimed towards pruned wallets. The end-user is responsible to import additional transactions that subsequently spend the imported outputs or rescan after the point in the blockchain the transaction is included.\n" "\nArguments:\n" "1. \"rawtransaction\" (string, required) A raw transaction in hex funding an already-existing address in wallet\n" "2. \"txoutproof\" (string, required) The hex output from gettxoutproof that contains the transaction\n" ); CTransaction tx; if (!DecodeHexTx(tx, request.params[0].get_str())) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); uint256 hashTx = tx.GetHash(); CWalletTx wtx(pwalletMain,tx); CDataStream ssMB(ParseHexV(request.params[1], "proof"), SER_NETWORK, PROTOCOL_VERSION); CMerkleBlock merkleBlock; ssMB >> merkleBlock; //Search partial merkle tree in proof for our transaction and index in valid block vector<uint256> vMatch; vector<unsigned int> vIndex; unsigned int txnIndex = 0; if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) { LOCK(cs_main); if (!mapBlockIndex.count(merkleBlock.header.GetHash()) || !chainActive.Contains(mapBlockIndex[merkleBlock.header.GetHash()])) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); vector<uint256>::const_iterator it; if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx))==vMatch.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof"); } txnIndex = vIndex[it - vMatch.begin()]; } else { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); } wtx.nIndex = txnIndex; wtx.hashBlock = merkleBlock.header.GetHash(); LOCK2(cs_main, pwalletMain->cs_wallet); if (pwalletMain->IsMine(tx)) { pwalletMain->AddToWallet(wtx, false); return NullUniValue; } throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction"); } UniValue removeprunedfunds(const JSONRPCRequest& request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() != 1) throw runtime_error( "removeprunedfunds \"txid\"\n" "\nDeletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will effect wallet balances.\n" "\nArguments:\n" "1. \"txid\" (string, required) The hex-encoded id of the transaction you are deleting\n" "\nExamples:\n" + HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("removprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") ); LOCK2(cs_main, pwalletMain->cs_wallet); uint256 hash; hash.SetHex(request.params[0].get_str()); vector<uint256> vHash; vHash.push_back(hash); vector<uint256> vHashOut; if(pwalletMain->ZapSelectTx(vHash, vHashOut) != DB_LOAD_OK) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Could not properly delete the transaction."); } if(vHashOut.empty()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction does not exist in wallet."); } return NullUniValue; } UniValue importpubkey(const JSONRPCRequest& request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) throw runtime_error( "importpubkey \"pubkey\" ( \"label\" rescan )\n" "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend.\n" "\nArguments:\n" "1. \"pubkey\" (string, required) The hex-encoded public key\n" "2. \"label\" (string, optional, default=\"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" "\nNote: This call can take minutes to complete if rescan is true.\n" "\nExamples:\n" "\nImport a public key with rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\"") + "\nImport using a label without rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false") ); string strLabel = ""; if (request.params.size() > 1) strLabel = request.params[1].get_str(); // Whether to perform rescan after import bool fRescan = true; if (request.params.size() > 2) fRescan = request.params[2].get_bool(); if (fRescan && fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); if (!IsHex(request.params[0].get_str())) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); std::vector<unsigned char> data(ParseHex(request.params[0].get_str())); CPubKey pubKey(data.begin(), data.end()); if (!pubKey.IsFullyValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); LOCK2(cs_main, pwalletMain->cs_wallet); ImportAddress(CBitcoinAddress(pubKey.GetID()), strLabel); ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false); if (fRescan) { pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); pwalletMain->ReacceptWalletTransactions(); } return NullUniValue; } UniValue importwallet(const JSONRPCRequest& request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() != 1) throw runtime_error( "importwallet \"filename\"\n" "\nImports keys from a wallet dump file (see dumpwallet).\n" "\nArguments:\n" "1. \"filename\" (string, required) The wallet file\n" "\nExamples:\n" "\nDump the wallet\n" + HelpExampleCli("dumpwallet", "\"test\"") + "\nImport the wallet\n" + HelpExampleCli("importwallet", "\"test\"") + "\nImport using the json rpc call\n" + HelpExampleRpc("importwallet", "\"test\"") ); if (fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode"); LOCK2(cs_main, pwalletMain->cs_wallet); EnsureWalletIsUnlocked(); ifstream file; file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate); if (!file.is_open()) throw JSONRPCError(RPC_INVALID_PARAMETER, "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<std::string> 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(); assert(key.VerifyPubKey(pubkey)); 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) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet"); return NullUniValue; } UniValue dumpprivkey(const JSONRPCRequest& request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() != 1) throw runtime_error( "dumpprivkey \"bitcoinaddress\"\n" "\nReveals the private key corresponding to 'bitcoinaddress'.\n" "Then the importprivkey can be used with this output\n" "\nArguments:\n" "1. \"bitcoinaddress\" (string, required) The bitcoin address for the private key\n" "\nResult:\n" "\"key\" (string) The private key\n" "\nExamples:\n" + HelpExampleCli("dumpprivkey", "\"myaddress\"") + HelpExampleCli("importprivkey", "\"mykey\"") + HelpExampleRpc("dumpprivkey", "\"myaddress\"") ); LOCK2(cs_main, pwalletMain->cs_wallet); EnsureWalletIsUnlocked(); string strAddress = request.params[0].get_str(); CBitcoinAddress address; if (!address.SetString(strAddress)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); CKeyID keyID; if (!address.GetKeyID(keyID)) throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); CKey vchSecret; if (!pwalletMain->GetKey(keyID, vchSecret)) throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); return CBitcoinSecret(vchSecret).ToString(); } UniValue dumpwallet(const JSONRPCRequest& request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() != 1) throw runtime_error( "dumpwallet \"filename\"\n" "\nDumps all wallet keys in a human-readable format.\n" "\nArguments:\n" "1. \"filename\" (string, required) The filename\n" "\nExamples:\n" + HelpExampleCli("dumpwallet", "\"test\"") + HelpExampleRpc("dumpwallet", "\"test\"") ); LOCK2(cs_main, pwalletMain->cs_wallet); EnsureWalletIsUnlocked(); ofstream file; file.open(request.params[0].get_str().c_str()); if (!file.is_open()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); std::map<CKeyID, int64_t> mapKeyBirth; std::set<CKeyID> setKeyPool; pwalletMain->GetKeyBirthTimes(mapKeyBirth); pwalletMain->GetAllReserveKeys(setKeyPool); // sort time/key pairs std::vector<std::pair<int64_t, CKeyID> > vKeyBirth; for (std::map<CKeyID, int64_t>::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\n", CLIENT_BUILD); 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"; // add the base58check encoded extended master if the wallet uses HD CKeyID masterKeyID = pwalletMain->GetHDChain().masterKeyID; if (!masterKeyID.IsNull()) { CKey key; if (pwalletMain->GetKey(masterKeyID, key)) { CExtKey masterKey; masterKey.SetMaster(key.begin(), key.size()); CBitcoinExtKey b58extkey; b58extkey.SetKey(masterKey); file << "# extended private masterkey: " << b58extkey.ToString() << "\n\n"; } } for (std::vector<std::pair<int64_t, CKeyID> >::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)) { file << strprintf("%s %s ", CBitcoinSecret(key).ToString(), strTime); if (pwalletMain->mapAddressBook.count(keyid)) { file << strprintf("label=%s", EncodeDumpString(pwalletMain->mapAddressBook[keyid].name)); } else if (keyid == masterKeyID) { file << "hdmaster=1"; } else if (setKeyPool.count(keyid)) { file << "reserve=1"; } else if (pwalletMain->mapKeyMetadata[keyid].hdKeypath == "m") { file << "inactivehdmaster=1"; } else { file << "change=1"; } file << strprintf(" # addr=%s%s\n", strAddr, (pwalletMain->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath="+pwalletMain->mapKeyMetadata[keyid].hdKeypath : "")); } } file << "\n"; file << "# End of dump\n"; file.close(); return NullUniValue; } UniValue processImport(const UniValue& data) { - // TODO List: - // - Check consistency between pubkeys/privkeys and scriptPubKey/redeemScript. - try { bool success = false; // Required fields. const UniValue& scriptPubKey = data["scriptPubKey"]; // Should have script or JSON with "address". if (!(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address")) && !(scriptPubKey.getType() == UniValue::VSTR)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scriptPubKey"); } // Optional fields. const string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); const bool& internal = data.exists("internal") ? data["internal"].get_bool() : false; const bool& watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false; const string& label = data.exists("label") && !internal ? data["label"].get_str() : ""; const int64_t& timestamp = data.exists("timestamp") && data["timestamp"].get_int64() > 1 ? data["timestamp"].get_int64() : 1; bool isScript = scriptPubKey.getType() == UniValue::VSTR; bool isP2SH = strRedeemScript.length() > 0; const string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); // Parse the output. CScript script; CBitcoinAddress address; if (!isScript) { address = CBitcoinAddress(output); script = GetScriptForDestination(address.Get()); } else { if (!IsHex(output)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey"); } std::vector<unsigned char> vData(ParseHex(output)); script = CScript(vData.begin(), vData.end()); } // Watchonly and private keys if (watchOnly && keys.size()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between watchonly and keys"); } // Internal + Label if (internal && data.exists("label")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between internal and label"); } // Not having Internal + Script if (!internal && isScript) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set for hex scriptPubKey"); } // Keys / PubKeys size check. if (!isP2SH && (keys.size() > 1 || pubKeys.size() > 1)) { // Address / scriptPubKey throw JSONRPCError(RPC_INVALID_PARAMETER, "More than private key given for one address"); } // Invalid P2SH redeemScript if (isP2SH && !IsHex(strRedeemScript)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script"); } // Process. // // P2SH if (isP2SH) { - // TODO: check consistency between private keys and p2sh redeemscript + p2sh address - // Import redeem script. std::vector<unsigned char> vData(ParseHex(strRedeemScript)); CScript redeemScript = CScript(vData.begin(), vData.end()); // Invalid P2SH address if (!script.IsPayToScriptHash()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid P2SH address / script"); } pwalletMain->MarkDirty(); if (!pwalletMain->HaveWatchOnly(redeemScript) && !pwalletMain->AddWatchOnly(redeemScript)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } if (!pwalletMain->HaveCScript(redeemScript) && !pwalletMain->AddCScript(redeemScript)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); } CBitcoinAddress redeemAddress = CBitcoinAddress(CScriptID(redeemScript)); CScript redeemDestination = GetScriptForDestination(redeemAddress.Get()); if (::IsMine(*pwalletMain, redeemDestination) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); } pwalletMain->MarkDirty(); if (!pwalletMain->HaveWatchOnly(redeemDestination) && !pwalletMain->AddWatchOnly(redeemDestination)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } // add to address book or update label if (address.IsValid()) { pwalletMain->SetAddressBook(address.Get(), label, "receive"); } // Import private keys. if (keys.size()) { for (size_t i = 0; i < keys.size(); i++) { const string& privkey = keys[i].get_str(); CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(privkey); if (!fGood) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } CKey key = vchSecret.GetKey(); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); } CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); CKeyID vchAddress = pubkey.GetID(); pwalletMain->MarkDirty(); pwalletMain->SetAddressBook(vchAddress, label, "receive"); if (pwalletMain->HaveKey(vchAddress)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key"); } pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = timestamp; if (!pwalletMain->AddKeyPubKey(key, pubkey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } if (timestamp < pwalletMain->nTimeFirstKey) { pwalletMain->nTimeFirstKey = timestamp; } } } success = true; } else { - // TODO: check consistency between private/public keys and scriptPubKey / address - // Import public keys. if (pubKeys.size() && keys.size() == 0) { const string& strPubKey = pubKeys[0].get_str(); if (!IsHex(strPubKey)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); } std::vector<unsigned char> data(ParseHex(strPubKey)); CPubKey pubKey(data.begin(), data.end()); if (!pubKey.IsFullyValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); } CBitcoinAddress pubKeyAddress = CBitcoinAddress(pubKey.GetID()); + + // Consistency check. + if (!isScript && pubKeyAddress.Get() != address.Get()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + + // Consistency check. + if (isScript) { + CBitcoinAddress scriptAddress; + CTxDestination destination; + + if (ExtractDestination(script, destination)) { + scriptAddress = CBitcoinAddress(destination); + if (scriptAddress.Get() != pubKeyAddress.Get()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + } + } + CScript pubKeyScript = GetScriptForDestination(pubKeyAddress.Get()); if (::IsMine(*pwalletMain, pubKeyScript) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); } pwalletMain->MarkDirty(); if (!pwalletMain->HaveWatchOnly(pubKeyScript) && !pwalletMain->AddWatchOnly(pubKeyScript)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } // add to address book or update label if (pubKeyAddress.IsValid()) { pwalletMain->SetAddressBook(pubKeyAddress.Get(), label, "receive"); } // TODO Is this necessary? CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey); if (::IsMine(*pwalletMain, scriptRawPubKey) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); } pwalletMain->MarkDirty(); if (!pwalletMain->HaveWatchOnly(scriptRawPubKey) && !pwalletMain->AddWatchOnly(scriptRawPubKey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } success = true; } // Import private keys. if (keys.size()) { const string& strPrivkey = keys[0].get_str(); // Checks. CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(strPrivkey); if (!fGood) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } CKey key = vchSecret.GetKey(); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); } CPubKey pubKey = key.GetPubKey(); assert(key.VerifyPubKey(pubKey)); - CKeyID vchAddress = pubkey.GetID(); + CBitcoinAddress pubKeyAddress = CBitcoinAddress(pubKey.GetID()); + + // Consistency check. + if (!isScript && pubKeyAddress.Get() != address.Get()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + + // Consistency check. + if (isScript) { + CBitcoinAddress scriptAddress; + CTxDestination destination; + + if (ExtractDestination(script, destination)) { + scriptAddress = CBitcoinAddress(destination); + if (scriptAddress.Get() != pubKeyAddress.Get()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + } + } + + CKeyID vchAddress = pubKey.GetID(); pwalletMain->MarkDirty(); pwalletMain->SetAddressBook(vchAddress, label, "receive"); if (pwalletMain->HaveKey(vchAddress)) { return false; } pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = timestamp; if (!pwalletMain->AddKeyPubKey(key, pubKey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } if (timestamp < pwalletMain->nTimeFirstKey) { pwalletMain->nTimeFirstKey = timestamp; } success = true; } // Import scriptPubKey only. if (pubKeys.size() == 0 && keys.size() == 0) { if (::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); } pwalletMain->MarkDirty(); if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } if (scriptPubKey.getType() == UniValue::VOBJ) { // add to address book or update label if (address.IsValid()) { pwalletMain->SetAddressBook(address.Get(), label, "receive"); } } success = true; } } UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(success)); return result; } catch (const UniValue& e) { UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); result.pushKV("error", e); return result; } catch (...) { UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); return result; } } UniValue importmulti(const JSONRPCRequest& mainRequest) { // clang-format off if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) throw runtime_error( "importmulti '[<json import requests>]' '<json options>' \n\n" "Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options).\n\n" "Arguments:\n" "1. request array (array, required) Data to be imported\n" " [ (array of json objects)\n" " {\n" " \"scriptPubKey\": \"<script>\" | { \"address\":\"<address>\" }, (string / json, required) Type of scriptPubKey (string for script, json for address)\n" " \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey\n" " \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n" " \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n" " \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be be treated as not incoming payments\n" " \"watchonly\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty\n" " \"label\": <label> , (string, optional, default: '') Label to assign to the address (aka account name, for now), only allowed with internal=false\n" " \"timestamp\": 1454686740, (integer, optional, default now) Timestamp\n" " }\n" " ,...\n" " ]\n" "2. json options (json, optional)\n" " {\n" " \"rescan\": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\n" " }\n" "\nExamples:\n" + HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, " "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") + "\nResponse is an array with the same size as the input that has the execution result :\n" " [{ \"success\": true } , { \"success\": false, \"error\": { \"code\": -1, \"message\": \"Internal Server Error\"} }, ... ]\n"); // clang-format on if (!EnsureWalletIsAvailable(mainRequest.fHelp)) { return NullUniValue; } RPCTypeCheck(mainRequest.params, boost::assign::list_of(UniValue::VARR)(UniValue::VOBJ)); const UniValue& requests = mainRequest.params[0]; //Default options bool fRescan = true; if (mainRequest.params.size() > 1) { const UniValue& options = mainRequest.params[1]; if (options.exists("rescan")) { fRescan = options["rescan"].get_bool(); } } LOCK2(cs_main, pwalletMain->cs_wallet); EnsureWalletIsUnlocked(); bool fRunScan = false; const int64_t minimumTimestamp = 1; int64_t nLowestTimestamp; if (fRescan && chainActive.Tip()) { nLowestTimestamp = chainActive.Tip()->GetBlockTime(); } else { fRescan = false; } UniValue response(UniValue::VARR); BOOST_FOREACH (const UniValue& data, requests.getValues()) { const UniValue result = processImport(data); response.push_back(result); if (!fRescan) { continue; } // If at least one request was successful then allow rescan. if (result["success"].get_bool()) { fRunScan = true; } // Get the lowest timestamp. const int64_t& timestamp = data.exists("timestamp") && data["timestamp"].get_int64() > minimumTimestamp ? data["timestamp"].get_int64() : minimumTimestamp; if (timestamp < nLowestTimestamp) { nLowestTimestamp = timestamp; } } if (fRescan && fRunScan && requests.size() && nLowestTimestamp <= chainActive.Tip()->GetBlockTime()) { CBlockIndex* pindex = nLowestTimestamp > minimumTimestamp ? chainActive.FindLatestBefore(nLowestTimestamp) : chainActive.Genesis(); if (pindex) { pwalletMain->ScanForWalletTransactions(pindex, true); pwalletMain->ReacceptWalletTransactions(); } } return response; }