aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/coincontrol.h79
-rw-r--r--src/wallet/crypter.cpp14
-rw-r--r--src/wallet/crypter.h19
-rw-r--r--src/wallet/rpcdump.cpp422
-rw-r--r--src/wallet/rpcwallet.cpp21
-rw-r--r--src/wallet/test/crypto_tests.cpp12
-rw-r--r--src/wallet/wallet.cpp96
-rw-r--r--src/wallet/wallet.h4
-rw-r--r--src/wallet/walletdb.cpp78
-rw-r--r--src/wallet/walletdb.h3
10 files changed, 625 insertions, 123 deletions
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h
new file mode 100644
index 0000000000..08d23688ff
--- /dev/null
+++ b/src/wallet/coincontrol.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2011-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.
+
+#ifndef BITCOIN_WALLET_COINCONTROL_H
+#define BITCOIN_WALLET_COINCONTROL_H
+
+#include "primitives/transaction.h"
+
+/** Coin Control Features. */
+class CCoinControl
+{
+public:
+ CTxDestination destChange;
+ //! If false, allows unselected inputs, but requires all selected inputs be used
+ bool fAllowOtherInputs;
+ //! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria
+ bool fAllowWatchOnly;
+ //! Minimum absolute fee (not per kilobyte)
+ CAmount nMinimumTotalFee;
+ //! Override estimated feerate
+ bool fOverrideFeeRate;
+ //! Feerate to use if overrideFeeRate is true
+ CFeeRate nFeeRate;
+ //! Override the default confirmation target, 0 = use default
+ int nConfirmTarget;
+
+ CCoinControl()
+ {
+ SetNull();
+ }
+
+ void SetNull()
+ {
+ destChange = CNoDestination();
+ fAllowOtherInputs = false;
+ fAllowWatchOnly = false;
+ setSelected.clear();
+ nMinimumTotalFee = 0;
+ nFeeRate = CFeeRate(0);
+ fOverrideFeeRate = false;
+ nConfirmTarget = 0;
+ }
+
+ bool HasSelected() const
+ {
+ return (setSelected.size() > 0);
+ }
+
+ bool IsSelected(const COutPoint& output) const
+ {
+ return (setSelected.count(output) > 0);
+ }
+
+ void Select(const COutPoint& output)
+ {
+ setSelected.insert(output);
+ }
+
+ void UnSelect(const COutPoint& output)
+ {
+ setSelected.erase(output);
+ }
+
+ void UnSelectAll()
+ {
+ setSelected.clear();
+ }
+
+ void ListSelected(std::vector<COutPoint>& vOutpoints) const
+ {
+ vOutpoints.assign(setSelected.begin(), setSelected.end());
+ }
+
+private:
+ std::set<COutPoint> setSelected;
+};
+
+#endif // BITCOIN_WALLET_COINCONTROL_H
diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp
index 190f8ecf2a..31ee060677 100644
--- a/src/wallet/crypter.cpp
+++ b/src/wallet/crypter.cpp
@@ -48,12 +48,12 @@ bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::v
int i = 0;
if (nDerivationMethod == 0)
- i = BytesToKeySHA512AES(chSalt, strKeyData, nRounds, chKey, chIV);
+ i = BytesToKeySHA512AES(chSalt, strKeyData, nRounds, vchKey.data(), vchIV.data());
if (i != (int)WALLET_CRYPTO_KEY_SIZE)
{
- memory_cleanse(chKey, sizeof(chKey));
- memory_cleanse(chIV, sizeof(chIV));
+ memory_cleanse(vchKey.data(), vchKey.size());
+ memory_cleanse(vchIV.data(), vchIV.size());
return false;
}
@@ -66,8 +66,8 @@ bool CCrypter::SetKey(const CKeyingMaterial& chNewKey, const std::vector<unsigne
if (chNewKey.size() != WALLET_CRYPTO_KEY_SIZE || chNewIV.size() != WALLET_CRYPTO_IV_SIZE)
return false;
- memcpy(&chKey[0], &chNewKey[0], sizeof chKey);
- memcpy(&chIV[0], &chNewIV[0], sizeof chIV);
+ memcpy(vchKey.data(), chNewKey.data(), chNewKey.size());
+ memcpy(vchIV.data(), chNewIV.data(), chNewIV.size());
fKeySet = true;
return true;
@@ -82,7 +82,7 @@ bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned
// n + AES_BLOCKSIZE bytes
vchCiphertext.resize(vchPlaintext.size() + AES_BLOCKSIZE);
- AES256CBCEncrypt enc(chKey, chIV, true);
+ AES256CBCEncrypt enc(vchKey.data(), vchIV.data(), true);
size_t nLen = enc.Encrypt(&vchPlaintext[0], vchPlaintext.size(), &vchCiphertext[0]);
if(nLen < vchPlaintext.size())
return false;
@@ -101,7 +101,7 @@ bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingM
vchPlaintext.resize(nLen);
- AES256CBCDecrypt dec(chKey, chIV, true);
+ AES256CBCDecrypt dec(vchKey.data(), vchIV.data(), true);
nLen = dec.Decrypt(&vchCiphertext[0], vchCiphertext.size(), &vchPlaintext[0]);
if(nLen == 0)
return false;
diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h
index 5d0a4a3305..f00f7fa731 100644
--- a/src/wallet/crypter.h
+++ b/src/wallet/crypter.h
@@ -77,8 +77,8 @@ class CCrypter
{
friend class wallet_crypto::TestCrypter; // for test access to chKey/chIV
private:
- unsigned char chKey[WALLET_CRYPTO_KEY_SIZE];
- unsigned char chIV[WALLET_CRYPTO_IV_SIZE];
+ std::vector<unsigned char, secure_allocator<unsigned char>> vchKey;
+ std::vector<unsigned char, secure_allocator<unsigned char>> vchIV;
bool fKeySet;
int BytesToKeySHA512AES(const std::vector<unsigned char>& chSalt, const SecureString& strKeyData, int count, unsigned char *key,unsigned char *iv) const;
@@ -91,28 +91,21 @@ public:
void CleanKey()
{
- memory_cleanse(chKey, sizeof(chKey));
- memory_cleanse(chIV, sizeof(chIV));
+ memory_cleanse(vchKey.data(), vchKey.size());
+ memory_cleanse(vchIV.data(), vchIV.size());
fKeySet = false;
}
CCrypter()
{
fKeySet = false;
-
- // Try to keep the key data out of swap (and be a bit over-careful to keep the IV that we don't even use out of swap)
- // Note that this does nothing about suspend-to-disk (which will put all our key data on disk)
- // Note as well that at no point in this program is any attempt made to prevent stealing of keys by reading the memory of the running process.
- LockedPageManager::Instance().LockRange(&chKey[0], sizeof chKey);
- LockedPageManager::Instance().LockRange(&chIV[0], sizeof chIV);
+ vchKey.resize(WALLET_CRYPTO_KEY_SIZE);
+ vchIV.resize(WALLET_CRYPTO_IV_SIZE);
}
~CCrypter()
{
CleanKey();
-
- LockedPageManager::Instance().UnlockRange(&chKey[0], sizeof chKey);
- LockedPageManager::Instance().UnlockRange(&chIV[0], sizeof chIV);
}
};
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 8a1bbd5684..b638810e9d 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -24,6 +24,7 @@
#include <univalue.h>
+#include <boost/assign/list_of.hpp>
#include <boost/foreach.hpp>
using namespace std;
@@ -637,3 +638,424 @@ UniValue dumpwallet(const JSONRPCRequest& request)
file.close();
return NullUniValue;
}
+
+
+UniValue processImport(const UniValue& data) {
+ 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) {
+ // 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 {
+ // 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));
+
+ 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;
+}
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 33620aa6ff..5a22e0278d 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -6,6 +6,7 @@
#include "amount.h"
#include "base58.h"
#include "chain.h"
+#include "consensus/validation.h"
#include "core_io.h"
#include "init.h"
#include "main.h"
@@ -362,11 +363,14 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtr
vecSend.push_back(recipient);
if (!pwalletMain->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) {
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > pwalletMain->GetBalance())
- strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired));
+ strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired));
+ throw JSONRPCError(RPC_WALLET_ERROR, strError);
+ }
+ CValidationState state;
+ if (!pwalletMain->CommitTransaction(wtxNew, reservekey, g_connman.get(), state)) {
+ strError = strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason());
throw JSONRPCError(RPC_WALLET_ERROR, strError);
}
- if (!pwalletMain->CommitTransaction(wtxNew, reservekey, g_connman.get()))
- throw JSONRPCError(RPC_WALLET_ERROR, "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of the wallet and coins were spent in the copy but not marked as spent here.");
}
UniValue sendtoaddress(const JSONRPCRequest& request)
@@ -959,8 +963,11 @@ UniValue sendmany(const JSONRPCRequest& request)
bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason);
if (!fCreated)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
- if (!pwalletMain->CommitTransaction(wtx, keyChange, g_connman.get()))
- throw JSONRPCError(RPC_WALLET_ERROR, "Transaction commit failed");
+ CValidationState state;
+ if (!pwalletMain->CommitTransaction(wtx, keyChange, g_connman.get(), state)) {
+ strFailReason = strprintf("Transaction commit failed:: %s", state.GetRejectReason());
+ throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
+ }
return wtx.GetHash().GetHex();
}
@@ -2279,7 +2286,7 @@ UniValue getwalletinfo(const JSONRPCRequest& request)
" \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
" \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
" \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
- " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool\n"
+ " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
" \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n"
" \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
" \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
@@ -2583,6 +2590,7 @@ extern UniValue dumpwallet(const JSONRPCRequest& request);
extern UniValue importwallet(const JSONRPCRequest& request);
extern UniValue importprunedfunds(const JSONRPCRequest& request);
extern UniValue removeprunedfunds(const JSONRPCRequest& request);
+extern UniValue importmulti(const JSONRPCRequest& request);
static const CRPCCommand commands[] =
{ // category name actor (function) okSafeMode
@@ -2607,6 +2615,7 @@ static const CRPCCommand commands[] =
{ "wallet", "gettransaction", &gettransaction, false },
{ "wallet", "getunconfirmedbalance", &getunconfirmedbalance, false },
{ "wallet", "getwalletinfo", &getwalletinfo, false },
+ { "wallet", "importmulti", &importmulti, true },
{ "wallet", "importprivkey", &importprivkey, true },
{ "wallet", "importwallet", &importwallet, true },
{ "wallet", "importaddress", &importaddress, true },
diff --git a/src/wallet/test/crypto_tests.cpp b/src/wallet/test/crypto_tests.cpp
index c5f55ef5f0..ce35c53c48 100644
--- a/src/wallet/test/crypto_tests.cpp
+++ b/src/wallet/test/crypto_tests.cpp
@@ -97,10 +97,10 @@ static void TestPassphraseSingle(const std::vector<unsigned char>& vchSalt, cons
OldSetKeyFromPassphrase(passphrase, vchSalt, rounds, 0, chKey, chIV);
- BOOST_CHECK_MESSAGE(memcmp(chKey, crypt.chKey, sizeof(chKey)) == 0, \
- HexStr(chKey, chKey+sizeof(chKey)) + std::string(" != ") + HexStr(crypt.chKey, crypt.chKey + (sizeof crypt.chKey)));
- BOOST_CHECK_MESSAGE(memcmp(chIV, crypt.chIV, sizeof(chIV)) == 0, \
- HexStr(chIV, chIV+sizeof(chIV)) + std::string(" != ") + HexStr(crypt.chIV, crypt.chIV + (sizeof crypt.chIV)));
+ BOOST_CHECK_MESSAGE(memcmp(chKey, crypt.vchKey.data(), crypt.vchKey.size()) == 0, \
+ HexStr(chKey, chKey+sizeof(chKey)) + std::string(" != ") + HexStr(crypt.vchKey));
+ BOOST_CHECK_MESSAGE(memcmp(chIV, crypt.vchIV.data(), crypt.vchIV.size()) == 0, \
+ HexStr(chIV, chIV+sizeof(chIV)) + std::string(" != ") + HexStr(crypt.vchIV));
if(!correctKey.empty())
BOOST_CHECK_MESSAGE(memcmp(chKey, &correctKey[0], sizeof(chKey)) == 0, \
@@ -127,7 +127,7 @@ static void TestDecrypt(const CCrypter& crypt, const std::vector<unsigned char>&
CKeyingMaterial vchDecrypted2;
int result1, result2;
result1 = crypt.Decrypt(vchCiphertext, vchDecrypted1);
- result2 = OldDecrypt(vchCiphertext, vchDecrypted2, crypt.chKey, crypt.chIV);
+ result2 = OldDecrypt(vchCiphertext, vchDecrypted2, crypt.vchKey.data(), crypt.vchIV.data());
BOOST_CHECK(result1 == result2);
// These two should be equal. However, OpenSSL 1.0.1j introduced a change
@@ -152,7 +152,7 @@ static void TestEncryptSingle(const CCrypter& crypt, const CKeyingMaterial& vchP
std::vector<unsigned char> vchCiphertext2;
int result1 = crypt.Encrypt(vchPlaintext, vchCiphertext1);
- int result2 = OldEncrypt(vchPlaintext, vchCiphertext2, crypt.chKey, crypt.chIV);
+ int result2 = OldEncrypt(vchPlaintext, vchCiphertext2, crypt.vchKey.data(), crypt.vchIV.data());
BOOST_CHECK(result1 == result2);
BOOST_CHECK(vchCiphertext1 == vchCiphertext2);
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 66f92d887b..c2bac6e330 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -8,7 +8,7 @@
#include "base58.h"
#include "checkpoints.h"
#include "chain.h"
-#include "coincontrol.h"
+#include "wallet/coincontrol.h"
#include "consensus/consensus.h"
#include "consensus/validation.h"
#include "key.h"
@@ -658,8 +658,79 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
DBErrors CWallet::ReorderTransactions()
{
+ LOCK(cs_wallet);
CWalletDB walletdb(strWalletFile);
- return walletdb.ReorderTransactions(this);
+
+ // Old wallets didn't have any defined order for transactions
+ // Probably a bad idea to change the output of this
+
+ // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap.
+ typedef pair<CWalletTx*, CAccountingEntry*> TxPair;
+ typedef multimap<int64_t, TxPair > TxItems;
+ TxItems txByTime;
+
+ for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ {
+ CWalletTx* wtx = &((*it).second);
+ txByTime.insert(make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0)));
+ }
+ list<CAccountingEntry> acentries;
+ walletdb.ListAccountCreditDebit("", acentries);
+ BOOST_FOREACH(CAccountingEntry& entry, acentries)
+ {
+ txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry)));
+ }
+
+ nOrderPosNext = 0;
+ std::vector<int64_t> nOrderPosOffsets;
+ for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it)
+ {
+ CWalletTx *const pwtx = (*it).second.first;
+ CAccountingEntry *const pacentry = (*it).second.second;
+ int64_t& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos;
+
+ if (nOrderPos == -1)
+ {
+ nOrderPos = nOrderPosNext++;
+ nOrderPosOffsets.push_back(nOrderPos);
+
+ if (pwtx)
+ {
+ if (!walletdb.WriteTx(*pwtx))
+ return DB_LOAD_FAIL;
+ }
+ else
+ if (!walletdb.WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
+ return DB_LOAD_FAIL;
+ }
+ else
+ {
+ int64_t nOrderPosOff = 0;
+ BOOST_FOREACH(const int64_t& nOffsetStart, nOrderPosOffsets)
+ {
+ if (nOrderPos >= nOffsetStart)
+ ++nOrderPosOff;
+ }
+ nOrderPos += nOrderPosOff;
+ nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
+
+ if (!nOrderPosOff)
+ continue;
+
+ // Since we're changing the order, write it back
+ if (pwtx)
+ {
+ if (!walletdb.WriteTx(*pwtx))
+ return DB_LOAD_FAIL;
+ }
+ else
+ if (!walletdb.WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
+ return DB_LOAD_FAIL;
+ }
+ }
+ walletdb.WriteOrderPosNext(nOrderPosNext);
+
+ return DB_LOAD_OK;
}
int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb)
@@ -1463,7 +1534,8 @@ void CWallet::ReacceptWalletTransactions()
CWalletTx& wtx = *(item.second);
LOCK(mempool.cs);
- wtx.AcceptToMemoryPool(maxTxFee);
+ CValidationState state;
+ wtx.AcceptToMemoryPool(maxTxFee, state);
}
}
@@ -2433,17 +2505,22 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
dPriority = wtxNew.ComputePriority(dPriority, nBytes);
+ // Allow to override the default confirmation target over the CoinControl instance
+ int currentConfirmationTarget = nTxConfirmTarget;
+ if (coinControl && coinControl->nConfirmTarget > 0)
+ currentConfirmationTarget = coinControl->nConfirmTarget;
+
// Can we complete this as a free transaction?
if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE)
{
// Not enough fee: enough priority?
- double dPriorityNeeded = mempool.estimateSmartPriority(nTxConfirmTarget);
+ double dPriorityNeeded = mempool.estimateSmartPriority(currentConfirmationTarget);
// Require at least hard-coded AllowFree.
if (dPriority >= dPriorityNeeded && AllowFree(dPriority))
break;
}
- CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
+ CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, mempool);
if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) {
nFeeNeeded = coinControl->nMinimumTotalFee;
}
@@ -2474,7 +2551,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
/**
* Call after CreateTransaction unless you want to abort
*/
-bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman)
+bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state)
{
{
LOCK2(cs_main, cs_wallet);
@@ -2502,9 +2579,9 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CCon
if (fBroadcastTransactions)
{
// Broadcast
- if (!wtxNew.AcceptToMemoryPool(maxTxFee)) {
+ if (!wtxNew.AcceptToMemoryPool(maxTxFee, state)) {
// This must not fail. The transaction has already been signed and recorded.
- LogPrintf("CommitTransaction(): Error: Transaction not valid\n");
+ LogPrintf("CommitTransaction(): Error: Transaction not valid, %s\n", state.GetRejectReason());
return false;
}
wtxNew.RelayWalletTransaction(connman);
@@ -3659,8 +3736,7 @@ int CMerkleTx::GetBlocksToMaturity() const
}
-bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee)
+bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state)
{
- CValidationState state;
return ::AcceptToMemoryPool(mempool, state, *this, true, NULL, false, nAbsurdFee);
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 5e50eaedc3..d7d1f5513a 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -216,7 +216,7 @@ public:
bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; }
int GetBlocksToMaturity() const;
/** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */
- bool AcceptToMemoryPool(const CAmount& nAbsurdFee);
+ bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state);
bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); }
bool isAbandoned() const { return (hashBlock == ABANDON_HASH); }
void setAbandoned() { hashBlock = ABANDON_HASH; }
@@ -775,7 +775,7 @@ public:
*/
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut,
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
- bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman);
+ bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state);
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries);
bool AddAccountingEntry(const CAccountingEntry&);
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 80bfe8255d..43fd6a20ad 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -251,82 +251,6 @@ void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountin
pcursor->close();
}
-DBErrors CWalletDB::ReorderTransactions(CWallet* pwallet)
-{
- LOCK(pwallet->cs_wallet);
- // Old wallets didn't have any defined order for transactions
- // Probably a bad idea to change the output of this
-
- // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap.
- typedef pair<CWalletTx*, CAccountingEntry*> TxPair;
- typedef multimap<int64_t, TxPair > TxItems;
- TxItems txByTime;
-
- for (map<uint256, CWalletTx>::iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it)
- {
- CWalletTx* wtx = &((*it).second);
- txByTime.insert(make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0)));
- }
- list<CAccountingEntry> acentries;
- ListAccountCreditDebit("", acentries);
- BOOST_FOREACH(CAccountingEntry& entry, acentries)
- {
- txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry)));
- }
-
- int64_t& nOrderPosNext = pwallet->nOrderPosNext;
- nOrderPosNext = 0;
- std::vector<int64_t> nOrderPosOffsets;
- for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it)
- {
- CWalletTx *const pwtx = (*it).second.first;
- CAccountingEntry *const pacentry = (*it).second.second;
- int64_t& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos;
-
- if (nOrderPos == -1)
- {
- nOrderPos = nOrderPosNext++;
- nOrderPosOffsets.push_back(nOrderPos);
-
- if (pwtx)
- {
- if (!WriteTx(*pwtx))
- return DB_LOAD_FAIL;
- }
- else
- if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
- return DB_LOAD_FAIL;
- }
- else
- {
- int64_t nOrderPosOff = 0;
- BOOST_FOREACH(const int64_t& nOffsetStart, nOrderPosOffsets)
- {
- if (nOrderPos >= nOffsetStart)
- ++nOrderPosOff;
- }
- nOrderPos += nOrderPosOff;
- nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
-
- if (!nOrderPosOff)
- continue;
-
- // Since we're changing the order, write it back
- if (pwtx)
- {
- if (!WriteTx(*pwtx))
- return DB_LOAD_FAIL;
- }
- else
- if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
- return DB_LOAD_FAIL;
- }
- }
- WriteOrderPosNext(nOrderPosNext);
-
- return DB_LOAD_OK;
-}
-
class CWalletScanState {
public:
unsigned int nKeys;
@@ -711,7 +635,7 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
WriteVersion(CLIENT_VERSION);
if (wss.fAnyUnordered)
- result = ReorderTransactions(pwallet);
+ result = pwallet->ReorderTransactions();
pwallet->laccentries.clear();
ListAccountCreditDebit("*", pwallet->laccentries);
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 9c9d4922a4..a0525bd9a7 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -153,6 +153,7 @@ public:
/// This writes directly to the database, and will not update the CWallet's cached accounting entries!
/// Use wallet.AddAccountingEntry instead, to write *and* update its caches.
+ bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry);
bool WriteAccountingEntry_Backend(const CAccountingEntry& acentry);
bool ReadAccount(const std::string& strAccount, CAccount& account);
bool WriteAccount(const std::string& strAccount, const CAccount& account);
@@ -165,7 +166,6 @@ public:
CAmount GetAccountCreditDebit(const std::string& strAccount);
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries);
- DBErrors ReorderTransactions(CWallet* pwallet);
DBErrors LoadWallet(CWallet* pwallet);
DBErrors FindWalletTx(CWallet* pwallet, std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx);
DBErrors ZapWalletTx(CWallet* pwallet, std::vector<CWalletTx>& vWtx);
@@ -180,7 +180,6 @@ private:
CWalletDB(const CWalletDB&);
void operator=(const CWalletDB&);
- bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry);
};
void ThreadFlushWalletDB(const std::string& strFile);