diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/coincontrol.h | 91 | ||||
-rw-r--r-- | src/wallet/crypter.cpp | 327 | ||||
-rw-r--r-- | src/wallet/crypter.h | 160 | ||||
-rw-r--r-- | src/wallet/db.cpp | 735 | ||||
-rw-r--r-- | src/wallet/db.h | 371 | ||||
-rw-r--r-- | src/wallet/feebumper.cpp | 300 | ||||
-rw-r--r-- | src/wallet/feebumper.h | 58 | ||||
-rw-r--r-- | src/wallet/fees.cpp | 89 | ||||
-rw-r--r-- | src/wallet/fees.h | 34 | ||||
-rw-r--r-- | src/wallet/init.cpp | 316 | ||||
-rw-r--r-- | src/wallet/init.h | 43 | ||||
-rw-r--r-- | src/wallet/rpcdump.cpp | 1275 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 3592 | ||||
-rw-r--r-- | src/wallet/rpcwallet.h | 28 | ||||
-rw-r--r-- | src/wallet/test/accounting_tests.cpp | 136 | ||||
-rw-r--r-- | src/wallet/test/crypto_tests.cpp | 126 | ||||
-rw-r--r-- | src/wallet/test/wallet_test_fixture.cpp | 32 | ||||
-rw-r--r-- | src/wallet/test/wallet_test_fixture.h | 22 | ||||
-rw-r--r-- | src/wallet/test/wallet_tests.cpp | 705 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 4299 | ||||
-rw-r--r-- | src/wallet/wallet.h | 1307 | ||||
-rw-r--r-- | src/wallet/walletdb.cpp | 858 | ||||
-rw-r--r-- | src/wallet/walletdb.h | 254 | ||||
-rw-r--r-- | src/wallet/walletutil.cpp | 27 | ||||
-rw-r--r-- | src/wallet/walletutil.h | 14 |
25 files changed, 15199 insertions, 0 deletions
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h new file mode 100644 index 0000000000..458e770e03 --- /dev/null +++ b/src/wallet/coincontrol.h @@ -0,0 +1,91 @@ +// Copyright (c) 2011-2017 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 <policy/feerate.h> +#include <policy/fees.h> +#include <primitives/transaction.h> +#include <wallet/wallet.h> + +#include <boost/optional.hpp> + +/** Coin Control Features. */ +class CCoinControl +{ +public: + //! Custom change destination, if not set an address is generated + CTxDestination destChange; + //! Custom change type, ignored if destChange is set, defaults to g_change_type + OutputType change_type; + //! 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; + //! Override automatic min/max checks on fee, m_feerate must be set if true + bool fOverrideFeeRate; + //! Override the default payTxFee if set + boost::optional<CFeeRate> m_feerate; + //! Override the default confirmation target if set + boost::optional<unsigned int> m_confirm_target; + //! Signal BIP-125 replace by fee. + bool signalRbf; + //! Fee estimation mode to control arguments to estimateSmartFee + FeeEstimateMode m_fee_mode; + + CCoinControl() + { + SetNull(); + } + + void SetNull() + { + destChange = CNoDestination(); + change_type = g_change_type; + fAllowOtherInputs = false; + fAllowWatchOnly = false; + setSelected.clear(); + m_feerate.reset(); + fOverrideFeeRate = false; + m_confirm_target.reset(); + signalRbf = fWalletRbf; + m_fee_mode = FeeEstimateMode::UNSET; + } + + 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 new file mode 100644 index 0000000000..6ad18721fd --- /dev/null +++ b/src/wallet/crypter.cpp @@ -0,0 +1,327 @@ +// Copyright (c) 2009-2017 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 <wallet/crypter.h> + +#include <crypto/aes.h> +#include <crypto/sha512.h> +#include <script/script.h> +#include <script/standard.h> +#include <util.h> + +#include <string> +#include <vector> + +int CCrypter::BytesToKeySHA512AES(const std::vector<unsigned char>& chSalt, const SecureString& strKeyData, int count, unsigned char *key,unsigned char *iv) const +{ + // This mimics the behavior of openssl's EVP_BytesToKey with an aes256cbc + // cipher and sha512 message digest. Because sha512's output size (64b) is + // greater than the aes256 block size (16b) + aes256 key size (32b), + // there's no need to process more than once (D_0). + + if(!count || !key || !iv) + return 0; + + unsigned char buf[CSHA512::OUTPUT_SIZE]; + CSHA512 di; + + di.Write((const unsigned char*)strKeyData.c_str(), strKeyData.size()); + di.Write(chSalt.data(), chSalt.size()); + di.Finalize(buf); + + for(int i = 0; i != count - 1; i++) + di.Reset().Write(buf, sizeof(buf)).Finalize(buf); + + memcpy(key, buf, WALLET_CRYPTO_KEY_SIZE); + memcpy(iv, buf + WALLET_CRYPTO_KEY_SIZE, WALLET_CRYPTO_IV_SIZE); + memory_cleanse(buf, sizeof(buf)); + return WALLET_CRYPTO_KEY_SIZE; +} + +bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::vector<unsigned char>& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod) +{ + if (nRounds < 1 || chSalt.size() != WALLET_CRYPTO_SALT_SIZE) + return false; + + int i = 0; + if (nDerivationMethod == 0) + i = BytesToKeySHA512AES(chSalt, strKeyData, nRounds, vchKey.data(), vchIV.data()); + + if (i != (int)WALLET_CRYPTO_KEY_SIZE) + { + memory_cleanse(vchKey.data(), vchKey.size()); + memory_cleanse(vchIV.data(), vchIV.size()); + return false; + } + + fKeySet = true; + return true; +} + +bool CCrypter::SetKey(const CKeyingMaterial& chNewKey, const std::vector<unsigned char>& chNewIV) +{ + if (chNewKey.size() != WALLET_CRYPTO_KEY_SIZE || chNewIV.size() != WALLET_CRYPTO_IV_SIZE) + return false; + + memcpy(vchKey.data(), chNewKey.data(), chNewKey.size()); + memcpy(vchIV.data(), chNewIV.data(), chNewIV.size()); + + fKeySet = true; + return true; +} + +bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char> &vchCiphertext) const +{ + if (!fKeySet) + return false; + + // max ciphertext len for a n bytes of plaintext is + // n + AES_BLOCKSIZE bytes + vchCiphertext.resize(vchPlaintext.size() + AES_BLOCKSIZE); + + AES256CBCEncrypt enc(vchKey.data(), vchIV.data(), true); + size_t nLen = enc.Encrypt(&vchPlaintext[0], vchPlaintext.size(), vchCiphertext.data()); + if(nLen < vchPlaintext.size()) + return false; + vchCiphertext.resize(nLen); + + return true; +} + +bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext) const +{ + if (!fKeySet) + return false; + + // plaintext will always be equal to or lesser than length of ciphertext + int nLen = vchCiphertext.size(); + + vchPlaintext.resize(nLen); + + AES256CBCDecrypt dec(vchKey.data(), vchIV.data(), true); + nLen = dec.Decrypt(vchCiphertext.data(), vchCiphertext.size(), &vchPlaintext[0]); + if(nLen == 0) + return false; + vchPlaintext.resize(nLen); + return true; +} + + +static bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector<unsigned char> &vchCiphertext) +{ + CCrypter cKeyCrypter; + std::vector<unsigned char> chIV(WALLET_CRYPTO_IV_SIZE); + memcpy(chIV.data(), &nIV, WALLET_CRYPTO_IV_SIZE); + if(!cKeyCrypter.SetKey(vMasterKey, chIV)) + return false; + return cKeyCrypter.Encrypt(*((const CKeyingMaterial*)&vchPlaintext), vchCiphertext); +} + +static bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext) +{ + CCrypter cKeyCrypter; + std::vector<unsigned char> chIV(WALLET_CRYPTO_IV_SIZE); + memcpy(chIV.data(), &nIV, WALLET_CRYPTO_IV_SIZE); + if(!cKeyCrypter.SetKey(vMasterKey, chIV)) + return false; + return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext)); +} + +static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key) +{ + CKeyingMaterial vchSecret; + if(!DecryptSecret(vMasterKey, vchCryptedSecret, vchPubKey.GetHash(), vchSecret)) + return false; + + if (vchSecret.size() != 32) + return false; + + key.Set(vchSecret.begin(), vchSecret.end(), vchPubKey.IsCompressed()); + return key.VerifyPubKey(vchPubKey); +} + +bool CCryptoKeyStore::SetCrypted() +{ + LOCK(cs_KeyStore); + if (fUseCrypto) + return true; + if (!mapKeys.empty()) + return false; + fUseCrypto = true; + return true; +} + +bool CCryptoKeyStore::IsLocked() const +{ + if (!IsCrypted()) { + return false; + } + LOCK(cs_KeyStore); + return vMasterKey.empty(); +} + +bool CCryptoKeyStore::Lock() +{ + if (!SetCrypted()) + return false; + + { + LOCK(cs_KeyStore); + vMasterKey.clear(); + } + + NotifyStatusChanged(this); + return true; +} + +bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) +{ + { + LOCK(cs_KeyStore); + if (!SetCrypted()) + return false; + + bool keyPass = false; + bool keyFail = false; + CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); + for (; mi != mapCryptedKeys.end(); ++mi) + { + const CPubKey &vchPubKey = (*mi).second.first; + const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; + CKey key; + if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) + { + keyFail = true; + break; + } + keyPass = true; + if (fDecryptionThoroughlyChecked) + break; + } + if (keyPass && keyFail) + { + LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); + assert(false); + } + if (keyFail || !keyPass) + return false; + vMasterKey = vMasterKeyIn; + fDecryptionThoroughlyChecked = true; + } + NotifyStatusChanged(this); + return true; +} + +bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return CBasicKeyStore::AddKeyPubKey(key, pubkey); + } + + if (IsLocked()) { + return false; + } + + std::vector<unsigned char> vchCryptedSecret; + CKeyingMaterial vchSecret(key.begin(), key.end()); + if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { + return false; + } + + if (!AddCryptedKey(pubkey, vchCryptedSecret)) { + return false; + } + return true; +} + + +bool CCryptoKeyStore::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) +{ + LOCK(cs_KeyStore); + if (!SetCrypted()) { + return false; + } + + mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); + ImplicitlyLearnRelatedKeyScripts(vchPubKey); + return true; +} + +bool CCryptoKeyStore::HaveKey(const CKeyID &address) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return CBasicKeyStore::HaveKey(address); + } + return mapCryptedKeys.count(address) > 0; +} + +bool CCryptoKeyStore::GetKey(const CKeyID &address, CKey& keyOut) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return CBasicKeyStore::GetKey(address, keyOut); + } + + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); + if (mi != mapCryptedKeys.end()) + { + const CPubKey &vchPubKey = (*mi).second.first; + const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; + return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); + } + return false; +} + +bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) + return CBasicKeyStore::GetPubKey(address, vchPubKeyOut); + + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); + if (mi != mapCryptedKeys.end()) + { + vchPubKeyOut = (*mi).second.first; + return true; + } + // Check for watch-only pubkeys + return CBasicKeyStore::GetPubKey(address, vchPubKeyOut); +} + +std::set<CKeyID> CCryptoKeyStore::GetKeys() const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return CBasicKeyStore::GetKeys(); + } + std::set<CKeyID> set_address; + for (const auto& mi : mapCryptedKeys) { + set_address.insert(mi.first); + } + return set_address; +} + +bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) +{ + LOCK(cs_KeyStore); + if (!mapCryptedKeys.empty() || IsCrypted()) + return false; + + fUseCrypto = true; + for (KeyMap::value_type& mKey : mapKeys) + { + const CKey &key = mKey.second; + CPubKey vchPubKey = key.GetPubKey(); + CKeyingMaterial vchSecret(key.begin(), key.end()); + std::vector<unsigned char> vchCryptedSecret; + if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) + return false; + if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) + return false; + } + mapKeys.clear(); + return true; +} diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h new file mode 100644 index 0000000000..f3ae7144b4 --- /dev/null +++ b/src/wallet/crypter.h @@ -0,0 +1,160 @@ +// Copyright (c) 2009-2017 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_CRYPTER_H +#define BITCOIN_WALLET_CRYPTER_H + +#include <keystore.h> +#include <serialize.h> +#include <support/allocators/secure.h> + +#include <atomic> + +const unsigned int WALLET_CRYPTO_KEY_SIZE = 32; +const unsigned int WALLET_CRYPTO_SALT_SIZE = 8; +const unsigned int WALLET_CRYPTO_IV_SIZE = 16; + +/** + * Private key encryption is done based on a CMasterKey, + * which holds a salt and random encryption key. + * + * CMasterKeys are encrypted using AES-256-CBC using a key + * derived using derivation method nDerivationMethod + * (0 == EVP_sha512()) and derivation iterations nDeriveIterations. + * vchOtherDerivationParameters is provided for alternative algorithms + * which may require more parameters (such as scrypt). + * + * Wallet Private Keys are then encrypted using AES-256-CBC + * with the double-sha256 of the public key as the IV, and the + * master key's key as the encryption key (see keystore.[ch]). + */ + +/** Master key for wallet encryption */ +class CMasterKey +{ +public: + std::vector<unsigned char> vchCryptedKey; + std::vector<unsigned char> vchSalt; + //! 0 = EVP_sha512() + //! 1 = scrypt() + unsigned int nDerivationMethod; + unsigned int nDeriveIterations; + //! Use this for more parameters to key derivation, + //! such as the various parameters to scrypt + std::vector<unsigned char> vchOtherDerivationParameters; + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(vchCryptedKey); + READWRITE(vchSalt); + READWRITE(nDerivationMethod); + READWRITE(nDeriveIterations); + READWRITE(vchOtherDerivationParameters); + } + + CMasterKey() + { + // 25000 rounds is just under 0.1 seconds on a 1.86 GHz Pentium M + // ie slightly lower than the lowest hardware we need bother supporting + nDeriveIterations = 25000; + nDerivationMethod = 0; + vchOtherDerivationParameters = std::vector<unsigned char>(0); + } +}; + +typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial; + +namespace wallet_crypto +{ + class TestCrypter; +} + +/** Encryption/decryption context with key information */ +class CCrypter +{ +friend class wallet_crypto::TestCrypter; // for test access to chKey/chIV +private: + 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; + +public: + bool SetKeyFromPassphrase(const SecureString &strKeyData, const std::vector<unsigned char>& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod); + bool Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char> &vchCiphertext) const; + bool Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext) const; + bool SetKey(const CKeyingMaterial& chNewKey, const std::vector<unsigned char>& chNewIV); + + void CleanKey() + { + memory_cleanse(vchKey.data(), vchKey.size()); + memory_cleanse(vchIV.data(), vchIV.size()); + fKeySet = false; + } + + CCrypter() + { + fKeySet = false; + vchKey.resize(WALLET_CRYPTO_KEY_SIZE); + vchIV.resize(WALLET_CRYPTO_IV_SIZE); + } + + ~CCrypter() + { + CleanKey(); + } +}; + +/** Keystore which keeps the private keys encrypted. + * It derives from the basic key store, which is used if no encryption is active. + */ +class CCryptoKeyStore : public CBasicKeyStore +{ +private: + + CKeyingMaterial vMasterKey; + + //! if fUseCrypto is true, mapKeys must be empty + //! if fUseCrypto is false, vMasterKey must be empty + std::atomic<bool> fUseCrypto; + + //! keeps track of whether Unlock has run a thorough check before + bool fDecryptionThoroughlyChecked; + +protected: + bool SetCrypted(); + + //! will encrypt previously unencrypted keys + bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); + + bool Unlock(const CKeyingMaterial& vMasterKeyIn); + CryptedKeyMap mapCryptedKeys; + +public: + CCryptoKeyStore() : fUseCrypto(false), fDecryptionThoroughlyChecked(false) + { + } + + bool IsCrypted() const { return fUseCrypto; } + bool IsLocked() const; + bool Lock(); + + virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); + bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; + bool HaveKey(const CKeyID &address) const override; + bool GetKey(const CKeyID &address, CKey& keyOut) const override; + bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; + std::set<CKeyID> GetKeys() const override; + + /** + * Wallet status (encrypted, locked) changed. + * Note: Called without locks held. + */ + boost::signals2::signal<void (CCryptoKeyStore* wallet)> NotifyStatusChanged; +}; + +#endif // BITCOIN_WALLET_CRYPTER_H diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp new file mode 100644 index 0000000000..23c6279128 --- /dev/null +++ b/src/wallet/db.cpp @@ -0,0 +1,735 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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 <wallet/db.h> + +#include <addrman.h> +#include <hash.h> +#include <protocol.h> +#include <util.h> +#include <utilstrencodings.h> +#include <wallet/walletutil.h> + +#include <stdint.h> + +#ifndef WIN32 +#include <sys/stat.h> +#endif + +#include <boost/thread.hpp> + +namespace { +//! Make sure database has a unique fileid within the environment. If it +//! doesn't, throw an error. BDB caches do not work properly when more than one +//! open database has the same fileid (values written to one database may show +//! up in reads to other databases). +//! +//! BerkeleyDB generates unique fileids by default +//! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html), +//! so bitcoin should never create different databases with the same fileid, but +//! this error can be triggered if users manually copy database files. +void CheckUniqueFileid(const CDBEnv& env, const std::string& filename, Db& db) +{ + if (env.IsMock()) return; + + u_int8_t fileid[DB_FILE_ID_LEN]; + int ret = db.get_mpf()->get_fileid(fileid); + if (ret != 0) { + throw std::runtime_error(strprintf("CDB: Can't open database %s (get_fileid failed with %d)", filename, ret)); + } + + for (const auto& item : env.mapDb) { + u_int8_t item_fileid[DB_FILE_ID_LEN]; + if (item.second && item.second->get_mpf()->get_fileid(item_fileid) == 0 && + memcmp(fileid, item_fileid, sizeof(fileid)) == 0) { + const char* item_filename = nullptr; + item.second->get_dbname(&item_filename, nullptr); + throw std::runtime_error(strprintf("CDB: Can't open database %s (duplicates fileid %s from %s)", filename, + HexStr(std::begin(item_fileid), std::end(item_fileid)), + item_filename ? item_filename : "(unknown database)")); + } + } +} +} // namespace + +// +// CDB +// + +CDBEnv bitdb; + +void CDBEnv::EnvShutdown() +{ + if (!fDbEnvInit) + return; + + fDbEnvInit = false; + int ret = dbenv->close(0); + if (ret != 0) + LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database environment: %s\n", ret, DbEnv::strerror(ret)); + if (!fMockDb) + DbEnv((u_int32_t)0).remove(strPath.c_str(), 0); +} + +void CDBEnv::Reset() +{ + dbenv.reset(new DbEnv(DB_CXX_NO_EXCEPTIONS)); + fDbEnvInit = false; + fMockDb = false; +} + +CDBEnv::CDBEnv() +{ + Reset(); +} + +CDBEnv::~CDBEnv() +{ + EnvShutdown(); +} + +void CDBEnv::Close() +{ + EnvShutdown(); +} + +bool CDBEnv::Open(const fs::path& pathIn, bool retry) +{ + if (fDbEnvInit) + return true; + + boost::this_thread::interruption_point(); + + strPath = pathIn.string(); + if (!LockDirectory(pathIn, ".walletlock")) { + LogPrintf("Cannot obtain a lock on wallet directory %s. Another instance of bitcoin may be using it.\n", strPath); + return false; + } + + fs::path pathLogDir = pathIn / "database"; + TryCreateDirectories(pathLogDir); + fs::path pathErrorFile = pathIn / "db.log"; + LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string()); + + unsigned int nEnvFlags = 0; + if (gArgs.GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB)) + nEnvFlags |= DB_PRIVATE; + + dbenv->set_lg_dir(pathLogDir.string().c_str()); + dbenv->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet + dbenv->set_lg_bsize(0x10000); + dbenv->set_lg_max(1048576); + dbenv->set_lk_max_locks(40000); + dbenv->set_lk_max_objects(40000); + dbenv->set_errfile(fsbridge::fopen(pathErrorFile, "a")); /// debug + dbenv->set_flags(DB_AUTO_COMMIT, 1); + dbenv->set_flags(DB_TXN_WRITE_NOSYNC, 1); + dbenv->log_set_config(DB_LOG_AUTO_REMOVE, 1); + int ret = dbenv->open(strPath.c_str(), + DB_CREATE | + DB_INIT_LOCK | + DB_INIT_LOG | + DB_INIT_MPOOL | + DB_INIT_TXN | + DB_THREAD | + DB_RECOVER | + nEnvFlags, + S_IRUSR | S_IWUSR); + if (ret != 0) { + dbenv->close(0); + LogPrintf("CDBEnv::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret)); + if (retry) { + // try moving the database env out of the way + fs::path pathDatabaseBak = pathIn / strprintf("database.%d.bak", GetTime()); + try { + fs::rename(pathLogDir, pathDatabaseBak); + LogPrintf("Moved old %s to %s. Retrying.\n", pathLogDir.string(), pathDatabaseBak.string()); + } catch (const fs::filesystem_error&) { + // failure is ok (well, not really, but it's not worse than what we started with) + } + // try opening it again one more time + if (!Open(pathIn, false)) { + // if it still fails, it probably means we can't even create the database env + return false; + } + } else { + return false; + } + } + + fDbEnvInit = true; + fMockDb = false; + return true; +} + +void CDBEnv::MakeMock() +{ + if (fDbEnvInit) + throw std::runtime_error("CDBEnv::MakeMock: Already initialized"); + + boost::this_thread::interruption_point(); + + LogPrint(BCLog::DB, "CDBEnv::MakeMock\n"); + + dbenv->set_cachesize(1, 0, 1); + dbenv->set_lg_bsize(10485760 * 4); + dbenv->set_lg_max(10485760); + dbenv->set_lk_max_locks(10000); + dbenv->set_lk_max_objects(10000); + dbenv->set_flags(DB_AUTO_COMMIT, 1); + dbenv->log_set_config(DB_LOG_IN_MEMORY, 1); + int ret = dbenv->open(nullptr, + DB_CREATE | + DB_INIT_LOCK | + DB_INIT_LOG | + DB_INIT_MPOOL | + DB_INIT_TXN | + DB_THREAD | + DB_PRIVATE, + S_IRUSR | S_IWUSR); + if (ret > 0) + throw std::runtime_error(strprintf("CDBEnv::MakeMock: Error %d opening database environment.", ret)); + + fDbEnvInit = true; + fMockDb = true; +} + +CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, recoverFunc_type recoverFunc, std::string& out_backup_filename) +{ + LOCK(cs_db); + assert(mapFileUseCount.count(strFile) == 0); + + Db db(dbenv.get(), 0); + int result = db.verify(strFile.c_str(), nullptr, nullptr, 0); + if (result == 0) + return VERIFY_OK; + else if (recoverFunc == nullptr) + return RECOVER_FAIL; + + // Try to recover: + bool fRecovered = (*recoverFunc)(strFile, out_backup_filename); + return (fRecovered ? RECOVER_OK : RECOVER_FAIL); +} + +bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename) +{ + // Recovery procedure: + // move wallet file to walletfilename.timestamp.bak + // Call Salvage with fAggressive=true to + // get as much data as possible. + // Rewrite salvaged data to fresh wallet file + // Set -rescan so any missing transactions will be + // found. + int64_t now = GetTime(); + newFilename = strprintf("%s.%d.bak", filename, now); + + int result = bitdb.dbenv->dbrename(nullptr, filename.c_str(), nullptr, + newFilename.c_str(), DB_AUTO_COMMIT); + if (result == 0) + LogPrintf("Renamed %s to %s\n", filename, newFilename); + else + { + LogPrintf("Failed to rename %s to %s\n", filename, newFilename); + return false; + } + + std::vector<CDBEnv::KeyValPair> salvagedData; + bool fSuccess = bitdb.Salvage(newFilename, true, salvagedData); + if (salvagedData.empty()) + { + LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename); + return false; + } + LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size()); + + std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(bitdb.dbenv.get(), 0); + int ret = pdbCopy->open(nullptr, // Txn pointer + filename.c_str(), // Filename + "main", // Logical db name + DB_BTREE, // Database type + DB_CREATE, // Flags + 0); + if (ret > 0) { + LogPrintf("Cannot create database file %s\n", filename); + pdbCopy->close(0); + return false; + } + + DbTxn* ptxn = bitdb.TxnBegin(); + for (CDBEnv::KeyValPair& row : salvagedData) + { + if (recoverKVcallback) + { + CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); + CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); + if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue)) + continue; + } + Dbt datKey(&row.first[0], row.first.size()); + Dbt datValue(&row.second[0], row.second.size()); + int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE); + if (ret2 > 0) + fSuccess = false; + } + ptxn->commit(0); + pdbCopy->close(0); + + return fSuccess; +} + +bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr) +{ + LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0)); + LogPrintf("Using wallet %s\n", walletFile); + + // Wallet file must be a plain filename without a directory + if (walletFile != fs::basename(walletFile) + fs::extension(walletFile)) + { + errorStr = strprintf(_("Wallet %s resides outside wallet directory %s"), walletFile, walletDir.string()); + return false; + } + + if (!bitdb.Open(walletDir, true)) { + errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir); + return false; + } + + return true; +} + +bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc) +{ + if (fs::exists(walletDir / walletFile)) + { + std::string backup_filename; + CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc, backup_filename); + if (r == CDBEnv::RECOVER_OK) + { + warningStr = strprintf(_("Warning: Wallet file corrupt, data salvaged!" + " Original %s saved as %s in %s; if" + " your balance or transactions are incorrect you should" + " restore from a backup."), + walletFile, backup_filename, walletDir); + } + if (r == CDBEnv::RECOVER_FAIL) + { + errorStr = strprintf(_("%s corrupt, salvage failed"), walletFile); + return false; + } + } + // also return true if files does not exists + return true; +} + +/* End of headers, beginning of key/value data */ +static const char *HEADER_END = "HEADER=END"; +/* End of key/value data */ +static const char *DATA_END = "DATA=END"; + +bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<CDBEnv::KeyValPair>& vResult) +{ + LOCK(cs_db); + assert(mapFileUseCount.count(strFile) == 0); + + u_int32_t flags = DB_SALVAGE; + if (fAggressive) + flags |= DB_AGGRESSIVE; + + std::stringstream strDump; + + Db db(dbenv.get(), 0); + int result = db.verify(strFile.c_str(), nullptr, &strDump, flags); + if (result == DB_VERIFY_BAD) { + LogPrintf("CDBEnv::Salvage: Database salvage found errors, all data may not be recoverable.\n"); + if (!fAggressive) { + LogPrintf("CDBEnv::Salvage: Rerun with aggressive mode to ignore errors and continue.\n"); + return false; + } + } + if (result != 0 && result != DB_VERIFY_BAD) { + LogPrintf("CDBEnv::Salvage: Database salvage failed with result %d.\n", result); + return false; + } + + // Format of bdb dump is ascii lines: + // header lines... + // HEADER=END + // hexadecimal key + // hexadecimal value + // ... repeated + // DATA=END + + std::string strLine; + while (!strDump.eof() && strLine != HEADER_END) + getline(strDump, strLine); // Skip past header + + std::string keyHex, valueHex; + while (!strDump.eof() && keyHex != DATA_END) { + getline(strDump, keyHex); + if (keyHex != DATA_END) { + if (strDump.eof()) + break; + getline(strDump, valueHex); + if (valueHex == DATA_END) { + LogPrintf("CDBEnv::Salvage: WARNING: Number of keys in data does not match number of values.\n"); + break; + } + vResult.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex))); + } + } + + if (keyHex != DATA_END) { + LogPrintf("CDBEnv::Salvage: WARNING: Unexpected end of file while reading salvage output.\n"); + return false; + } + + return (result == 0); +} + + +void CDBEnv::CheckpointLSN(const std::string& strFile) +{ + dbenv->txn_checkpoint(0, 0, 0); + if (fMockDb) + return; + dbenv->lsn_reset(strFile.c_str(), 0); +} + + +CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb(nullptr), activeTxn(nullptr) +{ + fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w')); + fFlushOnClose = fFlushOnCloseIn; + env = dbw.env; + if (dbw.IsDummy()) { + return; + } + const std::string &strFilename = dbw.strFile; + + bool fCreate = strchr(pszMode, 'c') != nullptr; + unsigned int nFlags = DB_THREAD; + if (fCreate) + nFlags |= DB_CREATE; + + { + LOCK(env->cs_db); + if (!env->Open(GetWalletDir())) + throw std::runtime_error("CDB: Failed to open database environment."); + + pdb = env->mapDb[strFilename]; + if (pdb == nullptr) { + int ret; + std::unique_ptr<Db> pdb_temp = MakeUnique<Db>(env->dbenv.get(), 0); + + bool fMockDb = env->IsMock(); + if (fMockDb) { + DbMpoolFile* mpf = pdb_temp->get_mpf(); + ret = mpf->set_flags(DB_MPOOL_NOFILE, 1); + if (ret != 0) { + throw std::runtime_error(strprintf("CDB: Failed to configure for no temp file backing for database %s", strFilename)); + } + } + + ret = pdb_temp->open(nullptr, // Txn pointer + fMockDb ? nullptr : strFilename.c_str(), // Filename + fMockDb ? strFilename.c_str() : "main", // Logical db name + DB_BTREE, // Database type + nFlags, // Flags + 0); + + if (ret != 0) { + throw std::runtime_error(strprintf("CDB: Error %d, can't open database %s", ret, strFilename)); + } + CheckUniqueFileid(*env, strFilename, *pdb_temp); + + pdb = pdb_temp.release(); + env->mapDb[strFilename] = pdb; + + if (fCreate && !Exists(std::string("version"))) { + bool fTmp = fReadOnly; + fReadOnly = false; + WriteVersion(CLIENT_VERSION); + fReadOnly = fTmp; + } + } + ++env->mapFileUseCount[strFilename]; + strFile = strFilename; + } +} + +void CDB::Flush() +{ + if (activeTxn) + return; + + // Flush database activity from memory pool to disk log + unsigned int nMinutes = 0; + if (fReadOnly) + nMinutes = 1; + + env->dbenv->txn_checkpoint(nMinutes ? gArgs.GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0); +} + +void CWalletDBWrapper::IncrementUpdateCounter() +{ + ++nUpdateCounter; +} + +void CDB::Close() +{ + if (!pdb) + return; + if (activeTxn) + activeTxn->abort(); + activeTxn = nullptr; + pdb = nullptr; + + if (fFlushOnClose) + Flush(); + + { + LOCK(env->cs_db); + --env->mapFileUseCount[strFile]; + } +} + +void CDBEnv::CloseDb(const std::string& strFile) +{ + { + LOCK(cs_db); + if (mapDb[strFile] != nullptr) { + // Close the database handle + Db* pdb = mapDb[strFile]; + pdb->close(0); + delete pdb; + mapDb[strFile] = nullptr; + } + } +} + +bool CDB::Rewrite(CWalletDBWrapper& dbw, const char* pszSkip) +{ + if (dbw.IsDummy()) { + return true; + } + CDBEnv *env = dbw.env; + const std::string& strFile = dbw.strFile; + while (true) { + { + LOCK(env->cs_db); + if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0) { + // Flush log data to the dat file + env->CloseDb(strFile); + env->CheckpointLSN(strFile); + env->mapFileUseCount.erase(strFile); + + bool fSuccess = true; + LogPrintf("CDB::Rewrite: Rewriting %s...\n", strFile); + std::string strFileRes = strFile + ".rewrite"; + { // surround usage of db with extra {} + CDB db(dbw, "r"); + std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env->dbenv.get(), 0); + + int ret = pdbCopy->open(nullptr, // Txn pointer + strFileRes.c_str(), // Filename + "main", // Logical db name + DB_BTREE, // Database type + DB_CREATE, // Flags + 0); + if (ret > 0) { + LogPrintf("CDB::Rewrite: Can't create database file %s\n", strFileRes); + fSuccess = false; + } + + Dbc* pcursor = db.GetCursor(); + if (pcursor) + while (fSuccess) { + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + int ret1 = db.ReadAtCursor(pcursor, ssKey, ssValue); + if (ret1 == DB_NOTFOUND) { + pcursor->close(); + break; + } else if (ret1 != 0) { + pcursor->close(); + fSuccess = false; + break; + } + if (pszSkip && + strncmp(ssKey.data(), pszSkip, std::min(ssKey.size(), strlen(pszSkip))) == 0) + continue; + if (strncmp(ssKey.data(), "\x07version", 8) == 0) { + // Update version: + ssValue.clear(); + ssValue << CLIENT_VERSION; + } + Dbt datKey(ssKey.data(), ssKey.size()); + Dbt datValue(ssValue.data(), ssValue.size()); + int ret2 = pdbCopy->put(nullptr, &datKey, &datValue, DB_NOOVERWRITE); + if (ret2 > 0) + fSuccess = false; + } + if (fSuccess) { + db.Close(); + env->CloseDb(strFile); + if (pdbCopy->close(0)) + fSuccess = false; + } else { + pdbCopy->close(0); + } + } + if (fSuccess) { + Db dbA(env->dbenv.get(), 0); + if (dbA.remove(strFile.c_str(), nullptr, 0)) + fSuccess = false; + Db dbB(env->dbenv.get(), 0); + if (dbB.rename(strFileRes.c_str(), nullptr, strFile.c_str(), 0)) + fSuccess = false; + } + if (!fSuccess) + LogPrintf("CDB::Rewrite: Failed to rewrite database file %s\n", strFileRes); + return fSuccess; + } + } + MilliSleep(100); + } +} + + +void CDBEnv::Flush(bool fShutdown) +{ + int64_t nStart = GetTimeMillis(); + // Flush log data to the actual data file on all files that are not in use + LogPrint(BCLog::DB, "CDBEnv::Flush: Flush(%s)%s\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started"); + if (!fDbEnvInit) + return; + { + LOCK(cs_db); + std::map<std::string, int>::iterator mi = mapFileUseCount.begin(); + while (mi != mapFileUseCount.end()) { + std::string strFile = (*mi).first; + int nRefCount = (*mi).second; + LogPrint(BCLog::DB, "CDBEnv::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount); + if (nRefCount == 0) { + // Move log data to the dat file + CloseDb(strFile); + LogPrint(BCLog::DB, "CDBEnv::Flush: %s checkpoint\n", strFile); + dbenv->txn_checkpoint(0, 0, 0); + LogPrint(BCLog::DB, "CDBEnv::Flush: %s detach\n", strFile); + if (!fMockDb) + dbenv->lsn_reset(strFile.c_str(), 0); + LogPrint(BCLog::DB, "CDBEnv::Flush: %s closed\n", strFile); + mapFileUseCount.erase(mi++); + } else + mi++; + } + LogPrint(BCLog::DB, "CDBEnv::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart); + if (fShutdown) { + char** listp; + if (mapFileUseCount.empty()) { + dbenv->log_archive(&listp, DB_ARCH_REMOVE); + Close(); + if (!fMockDb) + fs::remove_all(fs::path(strPath) / "database"); + } + } + } +} + +bool CDB::PeriodicFlush(CWalletDBWrapper& dbw) +{ + if (dbw.IsDummy()) { + return true; + } + bool ret = false; + CDBEnv *env = dbw.env; + const std::string& strFile = dbw.strFile; + TRY_LOCK(bitdb.cs_db,lockDb); + if (lockDb) + { + // Don't do this if any databases are in use + int nRefCount = 0; + std::map<std::string, int>::iterator mit = env->mapFileUseCount.begin(); + while (mit != env->mapFileUseCount.end()) + { + nRefCount += (*mit).second; + mit++; + } + + if (nRefCount == 0) + { + boost::this_thread::interruption_point(); + std::map<std::string, int>::iterator mi = env->mapFileUseCount.find(strFile); + if (mi != env->mapFileUseCount.end()) + { + LogPrint(BCLog::DB, "Flushing %s\n", strFile); + int64_t nStart = GetTimeMillis(); + + // Flush wallet file so it's self contained + env->CloseDb(strFile); + env->CheckpointLSN(strFile); + + env->mapFileUseCount.erase(mi++); + LogPrint(BCLog::DB, "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart); + ret = true; + } + } + } + + return ret; +} + +bool CWalletDBWrapper::Rewrite(const char* pszSkip) +{ + return CDB::Rewrite(*this, pszSkip); +} + +bool CWalletDBWrapper::Backup(const std::string& strDest) +{ + if (IsDummy()) { + return false; + } + while (true) + { + { + LOCK(env->cs_db); + if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0) + { + // Flush log data to the dat file + env->CloseDb(strFile); + env->CheckpointLSN(strFile); + env->mapFileUseCount.erase(strFile); + + // Copy wallet file + fs::path pathSrc = GetWalletDir() / strFile; + fs::path pathDest(strDest); + if (fs::is_directory(pathDest)) + pathDest /= strFile; + + try { + if (fs::equivalent(pathSrc, pathDest)) { + LogPrintf("cannot backup to wallet source file %s\n", pathDest.string()); + return false; + } + + fs::copy_file(pathSrc, pathDest, fs::copy_option::overwrite_if_exists); + LogPrintf("copied %s to %s\n", strFile, pathDest.string()); + return true; + } catch (const fs::filesystem_error& e) { + LogPrintf("error copying %s to %s - %s\n", strFile, pathDest.string(), e.what()); + return false; + } + } + } + MilliSleep(100); + } +} + +void CWalletDBWrapper::Flush(bool shutdown) +{ + if (!IsDummy()) { + env->Flush(shutdown); + } +} diff --git a/src/wallet/db.h b/src/wallet/db.h new file mode 100644 index 0000000000..787135e400 --- /dev/null +++ b/src/wallet/db.h @@ -0,0 +1,371 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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_DB_H +#define BITCOIN_WALLET_DB_H + +#include <clientversion.h> +#include <fs.h> +#include <serialize.h> +#include <streams.h> +#include <sync.h> +#include <version.h> + +#include <atomic> +#include <map> +#include <string> +#include <vector> + +#include <db_cxx.h> + +static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100; +static const bool DEFAULT_WALLET_PRIVDB = true; + +class CDBEnv +{ +private: + bool fDbEnvInit; + bool fMockDb; + // Don't change into fs::path, as that can result in + // shutdown problems/crashes caused by a static initialized internal pointer. + std::string strPath; + + void EnvShutdown(); + +public: + mutable CCriticalSection cs_db; + std::unique_ptr<DbEnv> dbenv; + std::map<std::string, int> mapFileUseCount; + std::map<std::string, Db*> mapDb; + + CDBEnv(); + ~CDBEnv(); + void Reset(); + + void MakeMock(); + bool IsMock() const { return fMockDb; } + + /** + * Verify that database file strFile is OK. If it is not, + * call the callback to try to recover. + * This must be called BEFORE strFile is opened. + * Returns true if strFile is OK. + */ + enum VerifyResult { VERIFY_OK, + RECOVER_OK, + RECOVER_FAIL }; + typedef bool (*recoverFunc_type)(const std::string& strFile, std::string& out_backup_filename); + VerifyResult Verify(const std::string& strFile, recoverFunc_type recoverFunc, std::string& out_backup_filename); + /** + * Salvage data from a file that Verify says is bad. + * fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method documentation). + * Appends binary key/value pairs to vResult, returns true if successful. + * NOTE: reads the entire database into memory, so cannot be used + * for huge databases. + */ + typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair; + bool Salvage(const std::string& strFile, bool fAggressive, std::vector<KeyValPair>& vResult); + + bool Open(const fs::path& path, bool retry = 0); + void Close(); + void Flush(bool fShutdown); + void CheckpointLSN(const std::string& strFile); + + void CloseDb(const std::string& strFile); + + DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC) + { + DbTxn* ptxn = nullptr; + int ret = dbenv->txn_begin(nullptr, &ptxn, flags); + if (!ptxn || ret != 0) + return nullptr; + return ptxn; + } +}; + +extern CDBEnv bitdb; + +/** An instance of this class represents one database. + * For BerkeleyDB this is just a (env, strFile) tuple. + **/ +class CWalletDBWrapper +{ + friend class CDB; +public: + /** Create dummy DB handle */ + CWalletDBWrapper() : nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(nullptr) + { + } + + /** Create DB handle to real database */ + CWalletDBWrapper(CDBEnv *env_in, const std::string &strFile_in) : + nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(env_in), strFile(strFile_in) + { + } + + /** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero + */ + bool Rewrite(const char* pszSkip=nullptr); + + /** Back up the entire database to a file. + */ + bool Backup(const std::string& strDest); + + /** Get a name for this database, for debugging etc. + */ + std::string GetName() const { return strFile; } + + /** Make sure all changes are flushed to disk. + */ + void Flush(bool shutdown); + + void IncrementUpdateCounter(); + + std::atomic<unsigned int> nUpdateCounter; + unsigned int nLastSeen; + unsigned int nLastFlushed; + int64_t nLastWalletUpdate; + +private: + /** BerkeleyDB specific */ + CDBEnv *env; + std::string strFile; + + /** Return whether this database handle is a dummy for testing. + * Only to be used at a low level, application should ideally not care + * about this. + */ + bool IsDummy() { return env == nullptr; } +}; + + +/** RAII class that provides access to a Berkeley database */ +class CDB +{ +protected: + Db* pdb; + std::string strFile; + DbTxn* activeTxn; + bool fReadOnly; + bool fFlushOnClose; + CDBEnv *env; + +public: + explicit CDB(CWalletDBWrapper& dbw, const char* pszMode = "r+", bool fFlushOnCloseIn=true); + ~CDB() { Close(); } + + CDB(const CDB&) = delete; + CDB& operator=(const CDB&) = delete; + + void Flush(); + void Close(); + static bool Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename); + + /* flush the wallet passively (TRY_LOCK) + ideal to be called periodically */ + static bool PeriodicFlush(CWalletDBWrapper& dbw); + /* verifies the database environment */ + static bool VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr); + /* verifies the database file */ + static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc); + +public: + template <typename K, typename T> + bool Read(const K& key, T& value) + { + if (!pdb) + return false; + + // Key + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + ssKey.reserve(1000); + ssKey << key; + Dbt datKey(ssKey.data(), ssKey.size()); + + // Read + Dbt datValue; + datValue.set_flags(DB_DBT_MALLOC); + int ret = pdb->get(activeTxn, &datKey, &datValue, 0); + memory_cleanse(datKey.get_data(), datKey.get_size()); + bool success = false; + if (datValue.get_data() != nullptr) { + // Unserialize value + try { + CDataStream ssValue((char*)datValue.get_data(), (char*)datValue.get_data() + datValue.get_size(), SER_DISK, CLIENT_VERSION); + ssValue >> value; + success = true; + } catch (const std::exception&) { + // In this case success remains 'false' + } + + // Clear and free memory + memory_cleanse(datValue.get_data(), datValue.get_size()); + free(datValue.get_data()); + } + return ret == 0 && success; + } + + template <typename K, typename T> + bool Write(const K& key, const T& value, bool fOverwrite = true) + { + if (!pdb) + return true; + if (fReadOnly) + assert(!"Write called on database in read-only mode"); + + // Key + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + ssKey.reserve(1000); + ssKey << key; + Dbt datKey(ssKey.data(), ssKey.size()); + + // Value + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + ssValue.reserve(10000); + ssValue << value; + Dbt datValue(ssValue.data(), ssValue.size()); + + // Write + int ret = pdb->put(activeTxn, &datKey, &datValue, (fOverwrite ? 0 : DB_NOOVERWRITE)); + + // Clear memory in case it was a private key + memory_cleanse(datKey.get_data(), datKey.get_size()); + memory_cleanse(datValue.get_data(), datValue.get_size()); + return (ret == 0); + } + + template <typename K> + bool Erase(const K& key) + { + if (!pdb) + return false; + if (fReadOnly) + assert(!"Erase called on database in read-only mode"); + + // Key + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + ssKey.reserve(1000); + ssKey << key; + Dbt datKey(ssKey.data(), ssKey.size()); + + // Erase + int ret = pdb->del(activeTxn, &datKey, 0); + + // Clear memory + memory_cleanse(datKey.get_data(), datKey.get_size()); + return (ret == 0 || ret == DB_NOTFOUND); + } + + template <typename K> + bool Exists(const K& key) + { + if (!pdb) + return false; + + // Key + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + ssKey.reserve(1000); + ssKey << key; + Dbt datKey(ssKey.data(), ssKey.size()); + + // Exists + int ret = pdb->exists(activeTxn, &datKey, 0); + + // Clear memory + memory_cleanse(datKey.get_data(), datKey.get_size()); + return (ret == 0); + } + + Dbc* GetCursor() + { + if (!pdb) + return nullptr; + Dbc* pcursor = nullptr; + int ret = pdb->cursor(nullptr, &pcursor, 0); + if (ret != 0) + return nullptr; + return pcursor; + } + + int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue, bool setRange = false) + { + // Read at cursor + Dbt datKey; + unsigned int fFlags = DB_NEXT; + if (setRange) { + datKey.set_data(ssKey.data()); + datKey.set_size(ssKey.size()); + fFlags = DB_SET_RANGE; + } + Dbt datValue; + datKey.set_flags(DB_DBT_MALLOC); + datValue.set_flags(DB_DBT_MALLOC); + int ret = pcursor->get(&datKey, &datValue, fFlags); + if (ret != 0) + return ret; + else if (datKey.get_data() == nullptr || datValue.get_data() == nullptr) + return 99999; + + // Convert to streams + ssKey.SetType(SER_DISK); + ssKey.clear(); + ssKey.write((char*)datKey.get_data(), datKey.get_size()); + ssValue.SetType(SER_DISK); + ssValue.clear(); + ssValue.write((char*)datValue.get_data(), datValue.get_size()); + + // Clear and free memory + memory_cleanse(datKey.get_data(), datKey.get_size()); + memory_cleanse(datValue.get_data(), datValue.get_size()); + free(datKey.get_data()); + free(datValue.get_data()); + return 0; + } + +public: + bool TxnBegin() + { + if (!pdb || activeTxn) + return false; + DbTxn* ptxn = bitdb.TxnBegin(); + if (!ptxn) + return false; + activeTxn = ptxn; + return true; + } + + bool TxnCommit() + { + if (!pdb || !activeTxn) + return false; + int ret = activeTxn->commit(0); + activeTxn = nullptr; + return (ret == 0); + } + + bool TxnAbort() + { + if (!pdb || !activeTxn) + return false; + int ret = activeTxn->abort(); + activeTxn = nullptr; + return (ret == 0); + } + + bool ReadVersion(int& nVersion) + { + nVersion = 0; + return Read(std::string("version"), nVersion); + } + + bool WriteVersion(int nVersion) + { + return Write(std::string("version"), nVersion); + } + + bool static Rewrite(CWalletDBWrapper& dbw, const char* pszSkip = nullptr); +}; + +#endif // BITCOIN_WALLET_DB_H diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp new file mode 100644 index 0000000000..5234a69710 --- /dev/null +++ b/src/wallet/feebumper.cpp @@ -0,0 +1,300 @@ +// Copyright (c) 2017 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 <consensus/validation.h> +#include <wallet/coincontrol.h> +#include <wallet/feebumper.h> +#include <wallet/fees.h> +#include <wallet/wallet.h> +#include <policy/fees.h> +#include <policy/policy.h> +#include <policy/rbf.h> +#include <validation.h> //for mempool access +#include <txmempool.h> +#include <utilmoneystr.h> +#include <util.h> +#include <net.h> + +// Calculate the size of the transaction assuming all signatures are max size +// Use DummySignatureCreator, which inserts 72 byte signatures everywhere. +// TODO: re-use this in CWallet::CreateTransaction (right now +// CreateTransaction uses the constructed dummy-signed tx to do a priority +// calculation, but we should be able to refactor after priority is removed). +// NOTE: this requires that all inputs must be in mapWallet (eg the tx should +// be IsAllFromMe). +static int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet) +{ + CMutableTransaction txNew(tx); + std::vector<CInputCoin> vCoins; + // Look up the inputs. We should have already checked that this transaction + // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our + // wallet, with a valid index into the vout array. + for (auto& input : tx.vin) { + const auto mi = wallet->mapWallet.find(input.prevout.hash); + assert(mi != wallet->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size()); + vCoins.emplace_back(CInputCoin(&(mi->second), input.prevout.n)); + } + if (!wallet->DummySignTx(txNew, vCoins)) { + // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) + // implies that we can sign for every input. + return -1; + } + return GetVirtualTransactionSize(txNew); +} + +//! Check whether transaction has descendant in wallet or mempool, or has been +//! mined, or conflicts with a mined transaction. Return a feebumper::Result. +static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWalletTx& wtx, std::vector<std::string>& errors) +{ + if (wallet->HasWalletSpend(wtx.GetHash())) { + errors.push_back("Transaction has descendants in the wallet"); + return feebumper::Result::INVALID_PARAMETER; + } + + { + LOCK(mempool.cs); + auto it_mp = mempool.mapTx.find(wtx.GetHash()); + if (it_mp != mempool.mapTx.end() && it_mp->GetCountWithDescendants() > 1) { + errors.push_back("Transaction has descendants in the mempool"); + return feebumper::Result::INVALID_PARAMETER; + } + } + + if (wtx.GetDepthInMainChain() != 0) { + errors.push_back("Transaction has been mined, or is conflicted with a mined transaction"); + return feebumper::Result::WALLET_ERROR; + } + + if (!SignalsOptInRBF(*wtx.tx)) { + errors.push_back("Transaction is not BIP 125 replaceable"); + return feebumper::Result::WALLET_ERROR; + } + + if (wtx.mapValue.count("replaced_by_txid")) { + errors.push_back(strprintf("Cannot bump transaction %s which was already bumped by transaction %s", wtx.GetHash().ToString(), wtx.mapValue.at("replaced_by_txid"))); + return feebumper::Result::WALLET_ERROR; + } + + // check that original tx consists entirely of our inputs + // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee) + if (!wallet->IsAllFromMe(*wtx.tx, ISMINE_SPENDABLE)) { + errors.push_back("Transaction contains inputs that don't belong to this wallet"); + return feebumper::Result::WALLET_ERROR; + } + + + return feebumper::Result::OK; +} + +namespace feebumper { + +bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid) +{ + LOCK2(cs_main, wallet->cs_wallet); + const CWalletTx* wtx = wallet->GetWalletTx(txid); + if (wtx == nullptr) return false; + + std::vector<std::string> errors_dummy; + feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy); + return res == feebumper::Result::OK; +} + +Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors, + CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) +{ + LOCK2(cs_main, wallet->cs_wallet); + errors.clear(); + auto it = wallet->mapWallet.find(txid); + if (it == wallet->mapWallet.end()) { + errors.push_back("Invalid or non-wallet transaction id"); + return Result::INVALID_ADDRESS_OR_KEY; + } + const CWalletTx& wtx = it->second; + + Result result = PreconditionChecks(wallet, wtx, errors); + if (result != Result::OK) { + return result; + } + + // figure out which output was change + // if there was no change output or multiple change outputs, fail + int nOutput = -1; + for (size_t i = 0; i < wtx.tx->vout.size(); ++i) { + if (wallet->IsChange(wtx.tx->vout[i])) { + if (nOutput != -1) { + errors.push_back("Transaction has multiple change outputs"); + return Result::WALLET_ERROR; + } + nOutput = i; + } + } + if (nOutput == -1) { + errors.push_back("Transaction does not have a change output"); + return Result::WALLET_ERROR; + } + + // Calculate the expected size of the new transaction. + int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); + const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx, wallet); + if (maxNewTxSize < 0) { + errors.push_back("Transaction contains inputs that cannot be signed"); + return Result::INVALID_ADDRESS_OR_KEY; + } + + // calculate the old fee and fee-rate + old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); + CFeeRate nOldFeeRate(old_fee, txSize); + CFeeRate nNewFeeRate; + // The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to + // future proof against changes to network wide policy for incremental relay + // fee that our node may not be aware of. + CFeeRate walletIncrementalRelayFee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE); + if (::incrementalRelayFee > walletIncrementalRelayFee) { + walletIncrementalRelayFee = ::incrementalRelayFee; + } + + if (total_fee > 0) { + CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + ::incrementalRelayFee.GetFee(maxNewTxSize); + if (total_fee < minTotalFee) { + errors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)", + FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(::incrementalRelayFee.GetFee(maxNewTxSize)))); + return Result::INVALID_PARAMETER; + } + CAmount requiredFee = GetRequiredFee(maxNewTxSize); + if (total_fee < requiredFee) { + errors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)", + FormatMoney(requiredFee))); + return Result::INVALID_PARAMETER; + } + new_fee = total_fee; + nNewFeeRate = CFeeRate(total_fee, maxNewTxSize); + } else { + new_fee = GetMinimumFee(maxNewTxSize, coin_control, mempool, ::feeEstimator, nullptr /* FeeCalculation */); + nNewFeeRate = CFeeRate(new_fee, maxNewTxSize); + + // New fee rate must be at least old rate + minimum incremental relay rate + // walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized + // in that unit (fee per kb). + // However, nOldFeeRate is a calculated value from the tx fee/size, so + // add 1 satoshi to the result, because it may have been rounded down. + if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) { + nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()); + new_fee = nNewFeeRate.GetFee(maxNewTxSize); + } + } + + // Check that in all cases the new fee doesn't violate maxTxFee + if (new_fee > maxTxFee) { + errors.push_back(strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)", + FormatMoney(new_fee), FormatMoney(maxTxFee))); + return Result::WALLET_ERROR; + } + + // check that fee rate is higher than mempool's minimum fee + // (no point in bumping fee if we know that the new tx won't be accepted to the mempool) + // This may occur if the user set TotalFee or paytxfee too low, if fallbackfee is too low, or, perhaps, + // in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a + // moment earlier. In this case, we report an error to the user, who may use total_fee to make an adjustment. + CFeeRate minMempoolFeeRate = mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000); + if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) { + errors.push_back(strprintf( + "New fee rate (%s) is lower than the minimum fee rate (%s) to get into the mempool -- " + "the totalFee value should be at least %s or the settxfee value should be at least %s to add transaction", + FormatMoney(nNewFeeRate.GetFeePerK()), + FormatMoney(minMempoolFeeRate.GetFeePerK()), + FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)), + FormatMoney(minMempoolFeeRate.GetFeePerK()))); + return Result::WALLET_ERROR; + } + + // Now modify the output to increase the fee. + // If the output is not large enough to pay the fee, fail. + CAmount nDelta = new_fee - old_fee; + assert(nDelta > 0); + mtx = *wtx.tx; + CTxOut* poutput = &(mtx.vout[nOutput]); + if (poutput->nValue < nDelta) { + errors.push_back("Change output is too small to bump the fee"); + return Result::WALLET_ERROR; + } + + // If the output would become dust, discard it (converting the dust to fee) + poutput->nValue -= nDelta; + if (poutput->nValue <= GetDustThreshold(*poutput, ::dustRelayFee)) { + LogPrint(BCLog::RPC, "Bumping fee and discarding dust output\n"); + new_fee += poutput->nValue; + mtx.vout.erase(mtx.vout.begin() + nOutput); + } + + // Mark new tx not replaceable, if requested. + if (!coin_control.signalRbf) { + for (auto& input : mtx.vin) { + if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe; + } + } + + + return Result::OK; +} + +bool SignTransaction(CWallet* wallet, CMutableTransaction& mtx) { + LOCK2(cs_main, wallet->cs_wallet); + return wallet->SignTransaction(mtx); +} + +Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransaction&& mtx, std::vector<std::string>& errors, uint256& bumped_txid) +{ + LOCK2(cs_main, wallet->cs_wallet); + if (!errors.empty()) { + return Result::MISC_ERROR; + } + auto it = txid.IsNull() ? wallet->mapWallet.end() : wallet->mapWallet.find(txid); + if (it == wallet->mapWallet.end()) { + errors.push_back("Invalid or non-wallet transaction id"); + return Result::MISC_ERROR; + } + CWalletTx& oldWtx = it->second; + + // make sure the transaction still has no descendants and hasn't been mined in the meantime + Result result = PreconditionChecks(wallet, oldWtx, errors); + if (result != Result::OK) { + return result; + } + + CWalletTx wtxBumped(wallet, MakeTransactionRef(std::move(mtx))); + // commit/broadcast the tx + CReserveKey reservekey(wallet); + wtxBumped.mapValue = oldWtx.mapValue; + wtxBumped.mapValue["replaces_txid"] = oldWtx.GetHash().ToString(); + wtxBumped.vOrderForm = oldWtx.vOrderForm; + wtxBumped.strFromAccount = oldWtx.strFromAccount; + wtxBumped.fTimeReceivedIsTxTime = true; + wtxBumped.fFromMe = true; + CValidationState state; + if (!wallet->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) { + // NOTE: CommitTransaction never returns false, so this should never happen. + errors.push_back(strprintf("The transaction was rejected: %s", state.GetRejectReason())); + return Result::WALLET_ERROR; + } + + bumped_txid = wtxBumped.GetHash(); + if (state.IsInvalid()) { + // This can happen if the mempool rejected the transaction. Report + // what happened in the "errors" response. + errors.push_back(strprintf("Error: The transaction was rejected: %s", FormatStateMessage(state))); + } + + // mark the original tx as bumped + if (!wallet->MarkReplaced(oldWtx.GetHash(), wtxBumped.GetHash())) { + // TODO: see if JSON-RPC has a standard way of returning a response + // along with an exception. It would be good to return information about + // wtxBumped to the caller even if marking the original transaction + // replaced does not succeed for some reason. + errors.push_back("Created new bumpfee transaction but could not mark the original transaction as replaced"); + } + return Result::OK; +} + +} // namespace feebumper + diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h new file mode 100644 index 0000000000..7e36a9766b --- /dev/null +++ b/src/wallet/feebumper.h @@ -0,0 +1,58 @@ +// Copyright (c) 2017 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_FEEBUMPER_H +#define BITCOIN_WALLET_FEEBUMPER_H + +#include <primitives/transaction.h> + +class CWallet; +class CWalletTx; +class uint256; +class CCoinControl; +enum class FeeEstimateMode; + +namespace feebumper { + +enum class Result +{ + OK, + INVALID_ADDRESS_OR_KEY, + INVALID_REQUEST, + INVALID_PARAMETER, + WALLET_ERROR, + MISC_ERROR, +}; + +//! Return whether transaction can be bumped. +bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid); + +//! Create bumpfee transaction. +Result CreateTransaction(const CWallet* wallet, + const uint256& txid, + const CCoinControl& coin_control, + CAmount total_fee, + std::vector<std::string>& errors, + CAmount& old_fee, + CAmount& new_fee, + CMutableTransaction& mtx); + +//! Sign the new transaction, +//! @return false if the tx couldn't be found or if it was +//! impossible to create the signature(s) +bool SignTransaction(CWallet* wallet, CMutableTransaction& mtx); + +//! Commit the bumpfee transaction. +//! @return success in case of CWallet::CommitTransaction was successful, +//! but sets errors if the tx could not be added to the mempool (will try later) +//! or if the old transaction could not be marked as replaced. +Result CommitTransaction(CWallet* wallet, + const uint256& txid, + CMutableTransaction&& mtx, + std::vector<std::string>& errors, + uint256& bumped_txid); + +} // namespace feebumper + +#endif // BITCOIN_WALLET_FEEBUMPER_H diff --git a/src/wallet/fees.cpp b/src/wallet/fees.cpp new file mode 100644 index 0000000000..73985dcf25 --- /dev/null +++ b/src/wallet/fees.cpp @@ -0,0 +1,89 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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 <wallet/fees.h> + +#include <policy/policy.h> +#include <txmempool.h> +#include <util.h> +#include <validation.h> +#include <wallet/coincontrol.h> +#include <wallet/wallet.h> + + +CAmount GetRequiredFee(unsigned int nTxBytes) +{ + return std::max(CWallet::minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes)); +} + + +CAmount GetMinimumFee(unsigned int nTxBytes, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc) +{ + /* User control of how to calculate fee uses the following parameter precedence: + 1. coin_control.m_feerate + 2. coin_control.m_confirm_target + 3. payTxFee (user-set global variable) + 4. nTxConfirmTarget (user-set global variable) + The first parameter that is set is used. + */ + CAmount fee_needed; + if (coin_control.m_feerate) { // 1. + fee_needed = coin_control.m_feerate->GetFee(nTxBytes); + if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE; + // Allow to override automatic min/max check over coin control instance + if (coin_control.fOverrideFeeRate) return fee_needed; + } + else if (!coin_control.m_confirm_target && ::payTxFee != CFeeRate(0)) { // 3. TODO: remove magic value of 0 for global payTxFee + fee_needed = ::payTxFee.GetFee(nTxBytes); + if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE; + } + else { // 2. or 4. + // We will use smart fee estimation + unsigned int target = coin_control.m_confirm_target ? *coin_control.m_confirm_target : ::nTxConfirmTarget; + // By default estimates are economical iff we are signaling opt-in-RBF + bool conservative_estimate = !coin_control.signalRbf; + // Allow to override the default fee estimate mode over the CoinControl instance + if (coin_control.m_fee_mode == FeeEstimateMode::CONSERVATIVE) conservative_estimate = true; + else if (coin_control.m_fee_mode == FeeEstimateMode::ECONOMICAL) conservative_estimate = false; + + fee_needed = estimator.estimateSmartFee(target, feeCalc, conservative_estimate).GetFee(nTxBytes); + if (fee_needed == 0) { + // if we don't have enough data for estimateSmartFee, then use fallbackFee + fee_needed = CWallet::fallbackFee.GetFee(nTxBytes); + if (feeCalc) feeCalc->reason = FeeReason::FALLBACK; + } + // Obey mempool min fee when using smart fee estimation + CAmount min_mempool_fee = pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nTxBytes); + if (fee_needed < min_mempool_fee) { + fee_needed = min_mempool_fee; + if (feeCalc) feeCalc->reason = FeeReason::MEMPOOL_MIN; + } + } + + // prevent user from paying a fee below minRelayTxFee or minTxFee + CAmount required_fee = GetRequiredFee(nTxBytes); + if (required_fee > fee_needed) { + fee_needed = required_fee; + if (feeCalc) feeCalc->reason = FeeReason::REQUIRED; + } + // But always obey the maximum + if (fee_needed > maxTxFee) { + fee_needed = maxTxFee; + if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE; + } + return fee_needed; +} + + +CFeeRate GetDiscardRate(const CBlockPolicyEstimator& estimator) +{ + unsigned int highest_target = estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); + CFeeRate discard_rate = estimator.estimateSmartFee(highest_target, nullptr /* FeeCalculation */, false /* conservative */); + // Don't let discard_rate be greater than longest possible fee estimate if we get a valid fee estimate + discard_rate = (discard_rate == CFeeRate(0)) ? CWallet::m_discard_rate : std::min(discard_rate, CWallet::m_discard_rate); + // Discard rate must be at least dustRelayFee + discard_rate = std::max(discard_rate, ::dustRelayFee); + return discard_rate; +} diff --git a/src/wallet/fees.h b/src/wallet/fees.h new file mode 100644 index 0000000000..225aff08ad --- /dev/null +++ b/src/wallet/fees.h @@ -0,0 +1,34 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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_FEES_H +#define BITCOIN_WALLET_FEES_H + +#include <amount.h> + +class CBlockPolicyEstimator; +class CCoinControl; +class CFeeRate; +class CTxMemPool; +struct FeeCalculation; + +/** + * Return the minimum required fee taking into account the + * floating relay fee and user set minimum transaction fee + */ +CAmount GetRequiredFee(unsigned int nTxBytes); + +/** + * Estimate the minimum fee considering user set parameters + * and the required fee + */ +CAmount GetMinimumFee(unsigned int nTxBytes, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc); + +/** + * Return the maximum feerate for discarding change. + */ +CFeeRate GetDiscardRate(const CBlockPolicyEstimator& estimator); + +#endif // BITCOIN_WALLET_FEES_H diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp new file mode 100644 index 0000000000..74036f4f0f --- /dev/null +++ b/src/wallet/init.cpp @@ -0,0 +1,316 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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 <wallet/init.h> + +#include <net.h> +#include <util.h> +#include <utilmoneystr.h> +#include <validation.h> +#include <wallet/rpcwallet.h> +#include <wallet/wallet.h> +#include <wallet/walletutil.h> + +std::string GetWalletHelpString(bool showDebug) +{ + std::string strUsage = HelpMessageGroup(_("Wallet options:")); + strUsage += HelpMessageOpt("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")", FormatOutputType(OUTPUT_TYPE_DEFAULT))); + strUsage += HelpMessageOpt("-changetype", "What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\"). Default is same as -addresstype, except when -addresstype=p2sh-segwit a native segwit output is used when sending to a native segwit address)"); + strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls")); + strUsage += HelpMessageOpt("-discardfee=<amt>", strprintf(_("The fee rate (in %s/kB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). " + "Note: An output is discarded if it is dust at this rate, but we will always discard up to the dust relay fee and a discard fee above that is limited by the fee estimate for the longest target"), + CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE))); + strUsage += HelpMessageOpt("-fallbackfee=<amt>", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"), + CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE))); + strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), DEFAULT_KEYPOOL_SIZE)); + strUsage += HelpMessageOpt("-mintxfee=<amt>", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)"), + CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE))); + strUsage += HelpMessageOpt("-paytxfee=<amt>", strprintf(_("Fee (in %s/kB) to add to transactions you send (default: %s)"), + CURRENCY_UNIT, FormatMoney(payTxFee.GetFeePerK()))); + strUsage += HelpMessageOpt("-rescan", _("Rescan the block chain for missing wallet transactions on startup")); + strUsage += HelpMessageOpt("-salvagewallet", _("Attempt to recover private keys from a corrupt wallet on startup")); + strUsage += HelpMessageOpt("-spendzeroconfchange", strprintf(_("Spend unconfirmed change when sending transactions (default: %u)"), DEFAULT_SPEND_ZEROCONF_CHANGE)); + strUsage += HelpMessageOpt("-txconfirmtarget=<n>", strprintf(_("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)"), DEFAULT_TX_CONFIRM_TARGET)); + strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup")); + strUsage += HelpMessageOpt("-wallet=<file>", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT)); + strUsage += HelpMessageOpt("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %u)"), DEFAULT_WALLETBROADCAST)); + strUsage += HelpMessageOpt("-walletdir=<dir>", _("Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)")); + strUsage += HelpMessageOpt("-walletnotify=<cmd>", _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)")); + strUsage += HelpMessageOpt("-walletrbf", strprintf(_("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)"), DEFAULT_WALLET_RBF)); + strUsage += HelpMessageOpt("-zapwallettxes=<mode>", _("Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup") + + " " + _("(1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)")); + + if (showDebug) + { + strUsage += HelpMessageGroup(_("Wallet debugging/testing options:")); + + strUsage += HelpMessageOpt("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE)); + strUsage += HelpMessageOpt("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET)); + strUsage += HelpMessageOpt("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB)); + strUsage += HelpMessageOpt("-walletrejectlongchains", strprintf(_("Wallet will not create transactions that violate mempool chain limits (default: %u)"), DEFAULT_WALLET_REJECT_LONG_CHAINS)); + } + + return strUsage; +} + +bool WalletParameterInteraction() +{ + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { + for (const std::string& wallet : gArgs.GetArgs("-wallet")) { + LogPrintf("%s: parameter interaction: -disablewallet -> ignoring -wallet=%s\n", __func__, wallet); + } + + return true; + } + + gArgs.SoftSetArg("-wallet", DEFAULT_WALLET_DAT); + const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1; + + if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) { + LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -walletbroadcast=0\n", __func__); + } + + if (gArgs.GetBoolArg("-salvagewallet", false)) { + if (is_multiwallet) { + return InitError(strprintf("%s is only allowed with a single wallet file", "-salvagewallet")); + } + // Rewrite just private keys: rescan to find transactions + if (gArgs.SoftSetBoolArg("-rescan", true)) { + LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__); + } + } + + int zapwallettxes = gArgs.GetArg("-zapwallettxes", 0); + // -zapwallettxes implies dropping the mempool on startup + if (zapwallettxes != 0 && gArgs.SoftSetBoolArg("-persistmempool", false)) { + LogPrintf("%s: parameter interaction: -zapwallettxes=%s -> setting -persistmempool=0\n", __func__, zapwallettxes); + } + + // -zapwallettxes implies a rescan + if (zapwallettxes != 0) { + if (is_multiwallet) { + return InitError(strprintf("%s is only allowed with a single wallet file", "-zapwallettxes")); + } + if (gArgs.SoftSetBoolArg("-rescan", true)) { + LogPrintf("%s: parameter interaction: -zapwallettxes=%s -> setting -rescan=1\n", __func__, zapwallettxes); + } + } + + if (is_multiwallet) { + if (gArgs.GetBoolArg("-upgradewallet", false)) { + return InitError(strprintf("%s is only allowed with a single wallet file", "-upgradewallet")); + } + } + + if (gArgs.GetBoolArg("-sysperms", false)) + return InitError("-sysperms is not allowed in combination with enabled wallet functionality"); + if (gArgs.GetArg("-prune", 0) && gArgs.GetBoolArg("-rescan", false)) + return InitError(_("Rescans are not possible in pruned mode. You will need to use -reindex which will download the whole blockchain again.")); + + if (::minRelayTxFee.GetFeePerK() > HIGH_TX_FEE_PER_KB) + InitWarning(AmountHighWarn("-minrelaytxfee") + " " + + _("The wallet will avoid paying less than the minimum relay fee.")); + + if (gArgs.IsArgSet("-mintxfee")) + { + CAmount n = 0; + if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || 0 == n) + return InitError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""))); + if (n > HIGH_TX_FEE_PER_KB) + InitWarning(AmountHighWarn("-mintxfee") + " " + + _("This is the minimum transaction fee you pay on every transaction.")); + CWallet::minTxFee = CFeeRate(n); + } + if (gArgs.IsArgSet("-fallbackfee")) + { + CAmount nFeePerK = 0; + if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) + return InitError(strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), gArgs.GetArg("-fallbackfee", ""))); + if (nFeePerK > HIGH_TX_FEE_PER_KB) + InitWarning(AmountHighWarn("-fallbackfee") + " " + + _("This is the transaction fee you may pay when fee estimates are not available.")); + CWallet::fallbackFee = CFeeRate(nFeePerK); + } + if (gArgs.IsArgSet("-discardfee")) + { + CAmount nFeePerK = 0; + if (!ParseMoney(gArgs.GetArg("-discardfee", ""), nFeePerK)) + return InitError(strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), gArgs.GetArg("-discardfee", ""))); + if (nFeePerK > HIGH_TX_FEE_PER_KB) + InitWarning(AmountHighWarn("-discardfee") + " " + + _("This is the transaction fee you may discard if change is smaller than dust at this level")); + CWallet::m_discard_rate = CFeeRate(nFeePerK); + } + if (gArgs.IsArgSet("-paytxfee")) + { + CAmount nFeePerK = 0; + if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) + return InitError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""))); + if (nFeePerK > HIGH_TX_FEE_PER_KB) + InitWarning(AmountHighWarn("-paytxfee") + " " + + _("This is the transaction fee you will pay if you send a transaction.")); + + payTxFee = CFeeRate(nFeePerK, 1000); + if (payTxFee < ::minRelayTxFee) + { + return InitError(strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"), + gArgs.GetArg("-paytxfee", ""), ::minRelayTxFee.ToString())); + } + } + if (gArgs.IsArgSet("-maxtxfee")) + { + CAmount nMaxFee = 0; + if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) + return InitError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""))); + if (nMaxFee > HIGH_MAX_TX_FEE) + InitWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction.")); + maxTxFee = nMaxFee; + if (CFeeRate(maxTxFee, 1000) < ::minRelayTxFee) + { + return InitError(strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"), + gArgs.GetArg("-maxtxfee", ""), ::minRelayTxFee.ToString())); + } + } + nTxConfirmTarget = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET); + bSpendZeroConfChange = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); + fWalletRbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF); + + g_address_type = ParseOutputType(gArgs.GetArg("-addresstype", "")); + if (g_address_type == OUTPUT_TYPE_NONE) { + return InitError(strprintf("Unknown address type '%s'", gArgs.GetArg("-addresstype", ""))); + } + + // If changetype is set in config file or parameter, check that it's valid. + // Default to OUTPUT_TYPE_NONE if not set. + g_change_type = ParseOutputType(gArgs.GetArg("-changetype", ""), OUTPUT_TYPE_NONE); + if (g_change_type == OUTPUT_TYPE_NONE && !gArgs.GetArg("-changetype", "").empty()) { + return InitError(strprintf("Unknown change type '%s'", gArgs.GetArg("-changetype", ""))); + } + + return true; +} + +void RegisterWalletRPC(CRPCTable &t) +{ + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { + return; + } + + RegisterWalletRPCCommands(t); +} + +bool VerifyWallets() +{ + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { + return true; + } + + if (gArgs.IsArgSet("-walletdir")) { + fs::path wallet_dir = gArgs.GetArg("-walletdir", ""); + if (!fs::exists(wallet_dir)) { + return InitError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string())); + } else if (!fs::is_directory(wallet_dir)) { + return InitError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string())); + } else if (!wallet_dir.is_absolute()) { + return InitError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string())); + } + } + + LogPrintf("Using wallet directory %s\n", GetWalletDir().string()); + + uiInterface.InitMessage(_("Verifying wallet(s)...")); + + // Keep track of each wallet absolute path to detect duplicates. + std::set<fs::path> wallet_paths; + + for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { + if (boost::filesystem::path(walletFile).filename() != walletFile) { + return InitError(strprintf(_("Error loading wallet %s. -wallet parameter must only specify a filename (not a path)."), walletFile)); + } + + if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) { + return InitError(strprintf(_("Error loading wallet %s. Invalid characters in -wallet filename."), walletFile)); + } + + fs::path wallet_path = fs::absolute(walletFile, GetWalletDir()); + + if (fs::exists(wallet_path) && (!fs::is_regular_file(wallet_path) || fs::is_symlink(wallet_path))) { + return InitError(strprintf(_("Error loading wallet %s. -wallet filename must be a regular file."), walletFile)); + } + + if (!wallet_paths.insert(wallet_path).second) { + return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), walletFile)); + } + + std::string strError; + if (!CWalletDB::VerifyEnvironment(walletFile, GetWalletDir().string(), strError)) { + return InitError(strError); + } + + if (gArgs.GetBoolArg("-salvagewallet", false)) { + // Recover readable keypairs: + CWallet dummyWallet; + std::string backup_filename; + if (!CWalletDB::Recover(walletFile, (void *)&dummyWallet, CWalletDB::RecoverKeysOnlyFilter, backup_filename)) { + return false; + } + } + + std::string strWarning; + bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetWalletDir().string(), strWarning, strError); + if (!strWarning.empty()) { + InitWarning(strWarning); + } + if (!dbV) { + InitError(strError); + return false; + } + } + + return true; +} + +bool OpenWallets() +{ + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { + LogPrintf("Wallet disabled!\n"); + return true; + } + + for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { + CWallet * const pwallet = CWallet::CreateWalletFromFile(walletFile); + if (!pwallet) { + return false; + } + vpwallets.push_back(pwallet); + } + + return true; +} + +void StartWallets(CScheduler& scheduler) { + for (CWalletRef pwallet : vpwallets) { + pwallet->postInitProcess(scheduler); + } +} + +void FlushWallets() { + for (CWalletRef pwallet : vpwallets) { + pwallet->Flush(false); + } +} + +void StopWallets() { + for (CWalletRef pwallet : vpwallets) { + pwallet->Flush(true); + } +} + +void CloseWallets() { + for (CWalletRef pwallet : vpwallets) { + delete pwallet; + } + vpwallets.clear(); +} diff --git a/src/wallet/init.h b/src/wallet/init.h new file mode 100644 index 0000000000..0b3ee2dda2 --- /dev/null +++ b/src/wallet/init.h @@ -0,0 +1,43 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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_INIT_H +#define BITCOIN_WALLET_INIT_H + +#include <string> + +class CRPCTable; +class CScheduler; + +//! Return the wallets help message. +std::string GetWalletHelpString(bool showDebug); + +//! Wallets parameter interaction +bool WalletParameterInteraction(); + +//! Register wallet RPCs. +void RegisterWalletRPC(CRPCTable &tableRPC); + +//! Responsible for reading and validating the -wallet arguments and verifying the wallet database. +// This function will perform salvage on the wallet if requested, as long as only one wallet is +// being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). +bool VerifyWallets(); + +//! Load wallet databases. +bool OpenWallets(); + +//! Complete startup of wallets. +void StartWallets(CScheduler& scheduler); + +//! Flush all wallets in preparation for shutdown. +void FlushWallets(); + +//! Stop all wallets. Wallets will be flushed first. +void StopWallets(); + +//! Close all wallets. +void CloseWallets(); + +#endif // BITCOIN_WALLET_INIT_H diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp new file mode 100644 index 0000000000..741ea25340 --- /dev/null +++ b/src/wallet/rpcdump.cpp @@ -0,0 +1,1275 @@ +// Copyright (c) 2009-2017 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/safemode.h> +#include <rpc/server.h> +#include <wallet/init.h> +#include <validation.h> +#include <script/script.h> +#include <script/standard.h> +#include <sync.h> +#include <util.h> +#include <utiltime.h> +#include <wallet/wallet.h> +#include <merkleblock.h> +#include <core_io.h> + +#include <wallet/rpcwallet.h> + +#include <fstream> +#include <stdint.h> + +#include <boost/algorithm/string.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +#include <univalue.h> + + +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; + for (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(); +} + +bool GetWalletAddressesForKey(CWallet * const pwallet, const CKeyID &keyid, std::string &strAddr, std::string &strLabel) +{ + bool fLabelFound = false; + CKey key; + pwallet->GetKey(keyid, key); + for (const auto& dest : GetAllDestinationsForKey(key.GetPubKey())) { + if (pwallet->mapAddressBook.count(dest)) { + if (!strAddr.empty()) { + strAddr += ","; + } + strAddr += EncodeDestination(dest); + strLabel = EncodeDumpString(pwallet->mapAddressBook[dest].name); + fLabelFound = true; + } + } + if (!fLabelFound) { + strAddr = EncodeDestination(GetDestinationForKey(key.GetPubKey(), g_address_type)); + } + return fLabelFound; +} + + +UniValue importprivkey(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) + throw std::runtime_error( + "importprivkey \"privkey\" ( \"label\" ) ( rescan )\n" + "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n" + "\nArguments:\n" + "1. \"privkey\" (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, during that time, other rpc calls\n" + "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\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") + + "\nImport using default blank label and without rescan\n" + + HelpExampleCli("importprivkey", "\"mykey\" \"\" false") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false") + ); + + + WalletRescanReserver reserver(pwallet); + bool fRescan = true; + { + LOCK2(cs_main, pwallet->cs_wallet); + + EnsureWalletIsUnlocked(pwallet); + + std::string strSecret = request.params[0].get_str(); + std::string strLabel = ""; + if (!request.params[1].isNull()) + strLabel = request.params[1].get_str(); + + // Whether to perform rescan after import + if (!request.params[2].isNull()) + fRescan = request.params[2].get_bool(); + + if (fRescan && fPruneMode) + throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); + + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + + 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(); + { + pwallet->MarkDirty(); + // We don't know which corresponding address will be used; label them all + for (const auto& dest : GetAllDestinationsForKey(pubkey)) { + pwallet->SetAddressBook(dest, strLabel, "receive"); + } + + // Don't throw error in case a key is already there + if (pwallet->HaveKey(vchAddress)) { + return NullUniValue; + } + + // whenever a key is imported, we need to scan the whole chain + pwallet->UpdateTimeFirstKey(1); + pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1; + + if (!pwallet->AddKeyPubKey(key, pubkey)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); + } + pwallet->LearnAllRelatedScripts(pubkey); + } + } + if (fRescan) { + pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */); + } + + return NullUniValue; +} + +UniValue abortrescan(const JSONRPCRequest& request) +{ + CWallet* const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 0) + throw std::runtime_error( + "abortrescan\n" + "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n" + "\nExamples:\n" + "\nImport a private key\n" + + HelpExampleCli("importprivkey", "\"mykey\"") + + "\nAbort the running wallet rescan\n" + + HelpExampleCli("abortrescan", "") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("abortrescan", "") + ); + + ObserveSafeMode(); + if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false; + pwallet->AbortRescan(); + return true; +} + +void ImportAddress(CWallet*, const CTxDestination& dest, const std::string& strLabel); +void ImportScript(CWallet* const pwallet, const CScript& script, const std::string& strLabel, bool isRedeemScript) +{ + if (!isRedeemScript && ::IsMine(*pwallet, script) == ISMINE_SPENDABLE) { + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + } + + pwallet->MarkDirty(); + + if (!pwallet->HaveWatchOnly(script) && !pwallet->AddWatchOnly(script, 0 /* nCreateTime */)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + + if (isRedeemScript) { + if (!pwallet->HaveCScript(script) && !pwallet->AddCScript(script)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); + } + ImportAddress(pwallet, CScriptID(script), strLabel); + } else { + CTxDestination destination; + if (ExtractDestination(script, destination)) { + pwallet->SetAddressBook(destination, strLabel, "receive"); + } + } +} + +void ImportAddress(CWallet* const pwallet, const CTxDestination& dest, const std::string& strLabel) +{ + CScript script = GetScriptForDestination(dest); + ImportScript(pwallet, script, strLabel, false); + // add to address book or update label + if (IsValidDestination(dest)) + pwallet->SetAddressBook(dest, strLabel, "receive"); +} + +UniValue importaddress(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) + throw std::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. Requires a new wallet backup.\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, during that time, other rpc calls\n" + "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\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") + ); + + + std::string strLabel = ""; + if (!request.params[1].isNull()) + strLabel = request.params[1].get_str(); + + // Whether to perform rescan after import + bool fRescan = true; + if (!request.params[2].isNull()) + fRescan = request.params[2].get_bool(); + + if (fRescan && fPruneMode) + throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); + + WalletRescanReserver reserver(pwallet); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + + // Whether to import a p2sh version, too + bool fP2SH = false; + if (!request.params[3].isNull()) + fP2SH = request.params[3].get_bool(); + + { + LOCK2(cs_main, pwallet->cs_wallet); + + CTxDestination dest = DecodeDestination(request.params[0].get_str()); + if (IsValidDestination(dest)) { + if (fP2SH) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); + } + ImportAddress(pwallet, dest, strLabel); + } else if (IsHex(request.params[0].get_str())) { + std::vector<unsigned char> data(ParseHex(request.params[0].get_str())); + ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH); + } else { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); + } + } + if (fRescan) + { + pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */); + pwallet->ReacceptWalletTransactions(); + } + + return NullUniValue; +} + +UniValue importprunedfunds(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 2) + throw std::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" + ); + + CMutableTransaction tx; + if (!DecodeHexTx(tx, request.params[0].get_str())) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + uint256 hashTx = tx.GetHash(); + CWalletTx wtx(pwallet, MakeTransactionRef(std::move(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 + std::vector<uint256> vMatch; + std::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"); + + std::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, pwallet->cs_wallet); + + if (pwallet->IsMine(*wtx.tx)) { + pwallet->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) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) + throw std::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 affect 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("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + uint256 hash; + hash.SetHex(request.params[0].get_str()); + std::vector<uint256> vHash; + vHash.push_back(hash); + std::vector<uint256> vHashOut; + + if (pwallet->ZapSelectTx(vHash, vHashOut) != DB_LOAD_OK) { + throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete the transaction."); + } + + if(vHashOut.empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet."); + } + + return NullUniValue; +} + +UniValue importpubkey(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) + throw std::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. Requires a new wallet backup.\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, during that time, other rpc calls\n" + "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\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") + ); + + + std::string strLabel = ""; + if (!request.params[1].isNull()) + strLabel = request.params[1].get_str(); + + // Whether to perform rescan after import + bool fRescan = true; + if (!request.params[2].isNull()) + fRescan = request.params[2].get_bool(); + + if (fRescan && fPruneMode) + throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); + + WalletRescanReserver reserver(pwallet); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + + 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, pwallet->cs_wallet); + + for (const auto& dest : GetAllDestinationsForKey(pubKey)) { + ImportAddress(pwallet, dest, strLabel); + } + ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false); + pwallet->LearnAllRelatedScripts(pubKey); + } + if (fRescan) + { + pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */); + pwallet->ReacceptWalletTransactions(); + } + + return NullUniValue; +} + + +UniValue importwallet(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "importwallet \"filename\"\n" + "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\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"); + + WalletRescanReserver reserver(pwallet); + if (!reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + + int64_t nTimeBegin = 0; + bool fGood = true; + { + LOCK2(cs_main, pwallet->cs_wallet); + + EnsureWalletIsUnlocked(pwallet); + + std::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"); + } + nTimeBegin = chainActive.Tip()->GetBlockTime(); + + int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); + file.seekg(0, file.beg); + + pwallet->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI + while (file.good()) { + pwallet->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])) { + CKey key = vchSecret.GetKey(); + CPubKey pubkey = key.GetPubKey(); + assert(key.VerifyPubKey(pubkey)); + CKeyID keyid = pubkey.GetID(); + if (pwallet->HaveKey(keyid)) { + LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid)); + 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", EncodeDestination(keyid)); + if (!pwallet->AddKeyPubKey(key, pubkey)) { + fGood = false; + continue; + } + pwallet->mapKeyMetadata[keyid].nCreateTime = nTime; + if (fLabel) + pwallet->SetAddressBook(keyid, strLabel, "receive"); + nTimeBegin = std::min(nTimeBegin, nTime); + } else if(IsHex(vstr[0])) { + std::vector<unsigned char> vData(ParseHex(vstr[0])); + CScript script = CScript(vData.begin(), vData.end()); + if (pwallet->HaveCScript(script)) { + LogPrintf("Skipping import of %s (script already present)\n", vstr[0]); + continue; + } + if(!pwallet->AddCScript(script)) { + LogPrintf("Error importing script %s\n", vstr[0]); + fGood = false; + continue; + } + int64_t birth_time = DecodeDumpTime(vstr[1]); + if (birth_time > 0) { + pwallet->m_script_metadata[CScriptID(script)].nCreateTime = birth_time; + nTimeBegin = std::min(nTimeBegin, birth_time); + } + } + } + file.close(); + pwallet->ShowProgress("", 100); // hide progress dialog in GUI + pwallet->UpdateTimeFirstKey(nTimeBegin); + } + pwallet->RescanFromTime(nTimeBegin, reserver, false /* update */); + pwallet->MarkDirty(); + + if (!fGood) + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys/scripts to wallet"); + + return NullUniValue; +} + +UniValue dumpprivkey(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "dumpprivkey \"address\"\n" + "\nReveals the private key corresponding to 'address'.\n" + "Then the importprivkey can be used with this output\n" + "\nArguments:\n" + "1. \"address\" (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, pwallet->cs_wallet); + + EnsureWalletIsUnlocked(pwallet); + + std::string strAddress = request.params[0].get_str(); + CTxDestination dest = DecodeDestination(strAddress); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + } + auto keyid = GetKeyForDestination(*pwallet, dest); + if (keyid.IsNull()) { + throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); + } + CKey vchSecret; + if (!pwallet->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) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "dumpwallet \"filename\"\n" + "\nDumps all wallet keys in a human-readable format to a server-side file. This does not allow overwriting existing files.\n" + "Imported scripts are included in the dumpfile, but corresponding BIP173 addresses, etc. may not be added automatically by importwallet.\n" + "Note that if your wallet contains keys which are not derived from your HD seed (e.g. imported keys), these are not covered by\n" + "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n" + "\nArguments:\n" + "1. \"filename\" (string, required) The filename with path (either absolute or relative to bitcoind)\n" + "\nResult:\n" + "{ (json object)\n" + " \"filename\" : { (string) The filename with full absolute path\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("dumpwallet", "\"test\"") + + HelpExampleRpc("dumpwallet", "\"test\"") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + EnsureWalletIsUnlocked(pwallet); + + boost::filesystem::path filepath = request.params[0].get_str(); + filepath = boost::filesystem::absolute(filepath); + + /* Prevent arbitrary files from being overwritten. There have been reports + * that users have overwritten wallet files this way: + * https://github.com/bitcoin/bitcoin/issues/9934 + * It may also avoid other security issues. + */ + if (boost::filesystem::exists(filepath)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.string() + " already exists. If you are sure this is what you want, move it out of the way first"); + } + + std::ofstream file; + file.open(filepath.string().c_str()); + if (!file.is_open()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); + + std::map<CTxDestination, int64_t> mapKeyBirth; + const std::map<CKeyID, int64_t>& mapKeyPool = pwallet->GetAllReserveKeys(); + pwallet->GetKeyBirthTimes(mapKeyBirth); + + std::set<CScriptID> scripts = pwallet->GetCScripts(); + // TODO: include scripts in GetKeyBirthTimes() output instead of separate + + // sort time/key pairs + std::vector<std::pair<int64_t, CKeyID> > vKeyBirth; + for (const auto& entry : mapKeyBirth) { + if (const CKeyID* keyID = boost::get<CKeyID>(&entry.first)) { // set and test + vKeyBirth.push_back(std::make_pair(entry.second, *keyID)); + } + } + 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 = pwallet->GetHDChain().masterKeyID; + if (!masterKeyID.IsNull()) + { + CKey key; + if (pwallet->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; + std::string strLabel; + CKey key; + if (pwallet->GetKey(keyid, key)) { + file << strprintf("%s %s ", CBitcoinSecret(key).ToString(), strTime); + if (GetWalletAddressesForKey(pwallet, keyid, strAddr, strLabel)) { + file << strprintf("label=%s", strLabel); + } else if (keyid == masterKeyID) { + file << "hdmaster=1"; + } else if (mapKeyPool.count(keyid)) { + file << "reserve=1"; + } else if (pwallet->mapKeyMetadata[keyid].hdKeypath == "m") { + file << "inactivehdmaster=1"; + } else { + file << "change=1"; + } + file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath="+pwallet->mapKeyMetadata[keyid].hdKeypath : "")); + } + } + file << "\n"; + for (const CScriptID &scriptid : scripts) { + CScript script; + std::string create_time = "0"; + std::string address = EncodeDestination(scriptid); + // get birth times for scripts with metadata + auto it = pwallet->m_script_metadata.find(scriptid); + if (it != pwallet->m_script_metadata.end()) { + create_time = EncodeDumpTime(it->second.nCreateTime); + } + if(pwallet->GetCScript(scriptid, script)) { + file << strprintf("%s %s script=1", HexStr(script.begin(), script.end()), create_time); + file << strprintf(" # addr=%s\n", address); + } + } + file << "\n"; + file << "# End of dump\n"; + file.close(); + + UniValue reply(UniValue::VOBJ); + reply.pushKV("filename", filepath.string()); + + return reply; +} + + +UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) +{ + 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 std::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 std::string& label = data.exists("label") && !internal ? data["label"].get_str() : ""; + + bool isScript = scriptPubKey.getType() == UniValue::VSTR; + bool isP2SH = strRedeemScript.length() > 0; + const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); + + // Parse the output. + CScript script; + CTxDestination dest; + + if (!isScript) { + dest = DecodeDestination(output); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + script = GetScriptForDestination(dest); + } 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"); + } + + pwallet->MarkDirty(); + + if (!pwallet->AddWatchOnly(redeemScript, timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + + if (!pwallet->HaveCScript(redeemScript) && !pwallet->AddCScript(redeemScript)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); + } + + CTxDestination redeem_dest = CScriptID(redeemScript); + CScript redeemDestination = GetScriptForDestination(redeem_dest); + + if (::IsMine(*pwallet, redeemDestination) == ISMINE_SPENDABLE) { + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + } + + pwallet->MarkDirty(); + + if (!pwallet->AddWatchOnly(redeemDestination, timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + + // add to address book or update label + if (IsValidDestination(dest)) { + pwallet->SetAddressBook(dest, label, "receive"); + } + + // Import private keys. + if (keys.size()) { + for (size_t i = 0; i < keys.size(); i++) { + const std::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(); + pwallet->MarkDirty(); + pwallet->SetAddressBook(vchAddress, label, "receive"); + + if (pwallet->HaveKey(vchAddress)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key"); + } + + pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp; + + if (!pwallet->AddKeyPubKey(key, pubkey)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); + } + + pwallet->UpdateTimeFirstKey(timestamp); + } + } + + success = true; + } else { + // Import public keys. + if (pubKeys.size() && keys.size() == 0) { + const std::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> vData(ParseHex(strPubKey)); + CPubKey pubKey(vData.begin(), vData.end()); + + if (!pubKey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); + } + + CTxDestination pubkey_dest = pubKey.GetID(); + + // Consistency check. + if (!isScript && !(pubkey_dest == dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + + // Consistency check. + if (isScript) { + CTxDestination destination; + + if (ExtractDestination(script, destination)) { + if (!(destination == pubkey_dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + } + } + + CScript pubKeyScript = GetScriptForDestination(pubkey_dest); + + if (::IsMine(*pwallet, pubKeyScript) == ISMINE_SPENDABLE) { + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + } + + pwallet->MarkDirty(); + + if (!pwallet->AddWatchOnly(pubKeyScript, timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + + // add to address book or update label + if (IsValidDestination(pubkey_dest)) { + pwallet->SetAddressBook(pubkey_dest, label, "receive"); + } + + // TODO Is this necessary? + CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey); + + if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) { + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + } + + pwallet->MarkDirty(); + + if (!pwallet->AddWatchOnly(scriptRawPubKey, timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + + success = true; + } + + // Import private keys. + if (keys.size()) { + const std::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)); + + CTxDestination pubkey_dest = pubKey.GetID(); + + // Consistency check. + if (!isScript && !(pubkey_dest == dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + + // Consistency check. + if (isScript) { + CTxDestination destination; + + if (ExtractDestination(script, destination)) { + if (!(destination == pubkey_dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + } + } + + CKeyID vchAddress = pubKey.GetID(); + pwallet->MarkDirty(); + pwallet->SetAddressBook(vchAddress, label, "receive"); + + if (pwallet->HaveKey(vchAddress)) { + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + } + + pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp; + + if (!pwallet->AddKeyPubKey(key, pubKey)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); + } + + pwallet->UpdateTimeFirstKey(timestamp); + + success = true; + } + + // Import scriptPubKey only. + if (pubKeys.size() == 0 && keys.size() == 0) { + if (::IsMine(*pwallet, script) == ISMINE_SPENDABLE) { + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + } + + pwallet->MarkDirty(); + + if (!pwallet->AddWatchOnly(script, timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + + if (scriptPubKey.getType() == UniValue::VOBJ) { + // add to address book or update label + if (IsValidDestination(dest)) { + pwallet->SetAddressBook(dest, 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; + } +} + +int64_t GetImportTimestamp(const UniValue& data, int64_t now) +{ + if (data.exists("timestamp")) { + const UniValue& timestamp = data["timestamp"]; + if (timestamp.isNum()) { + return timestamp.get_int64(); + } else if (timestamp.isStr() && timestamp.get_str() == "now") { + return now; + } + throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type()))); + } + throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key"); +} + +UniValue importmulti(const JSONRPCRequest& mainRequest) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(mainRequest); + if (!EnsureWalletIsAvailable(pwallet, mainRequest.fHelp)) { + return NullUniValue; + } + + // clang-format off + if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) + throw std::runtime_error( + "importmulti \"requests\" ( \"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). Requires a new wallet backup.\n\n" + "Arguments:\n" + "1. requests (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" + " \"timestamp\": timestamp | \"now\" , (integer / string, required) Creation time of the key in seconds since epoch (Jan 1 1970 GMT),\n" + " or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n" + " key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n" + " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n" + " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n" + " creation time of all keys being imported by the importmulti call will be scanned.\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 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" + " }\n" + " ,...\n" + " ]\n" + "2. options (json, optional)\n" + " {\n" + " \"rescan\": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\n" + " }\n" + "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\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 + + RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); + + const UniValue& requests = mainRequest.params[0]; + + //Default options + bool fRescan = true; + + if (!mainRequest.params[1].isNull()) { + const UniValue& options = mainRequest.params[1]; + + if (options.exists("rescan")) { + fRescan = options["rescan"].get_bool(); + } + } + + WalletRescanReserver reserver(pwallet); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + + int64_t now = 0; + bool fRunScan = false; + int64_t nLowestTimestamp = 0; + UniValue response(UniValue::VARR); + { + LOCK2(cs_main, pwallet->cs_wallet); + EnsureWalletIsUnlocked(pwallet); + + // Verify all timestamps are present before importing any keys. + now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0; + for (const UniValue& data : requests.getValues()) { + GetImportTimestamp(data, now); + } + + const int64_t minimumTimestamp = 1; + + if (fRescan && chainActive.Tip()) { + nLowestTimestamp = chainActive.Tip()->GetBlockTime(); + } else { + fRescan = false; + } + + for (const UniValue& data : requests.getValues()) { + const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp); + const UniValue result = ProcessImport(pwallet, data, timestamp); + 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. + if (timestamp < nLowestTimestamp) { + nLowestTimestamp = timestamp; + } + } + } + if (fRescan && fRunScan && requests.size()) { + int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */); + pwallet->ReacceptWalletTransactions(); + + if (scannedTime > nLowestTimestamp) { + std::vector<UniValue> results = response.getValues(); + response.clear(); + response.setArray(); + size_t i = 0; + for (const UniValue& request : requests.getValues()) { + // If key creation date is within the successfully scanned + // range, or if the import result already has an error set, let + // the result stand unmodified. Otherwise replace the result + // with an error message. + if (scannedTime <= GetImportTimestamp(request, now) || results.at(i).exists("error")) { + response.push_back(results.at(i)); + } else { + UniValue result = UniValue(UniValue::VOBJ); + result.pushKV("success", UniValue(false)); + result.pushKV( + "error", + JSONRPCError( + RPC_MISC_ERROR, + strprintf("Rescan failed for key with creation timestamp %d. There was an error reading a " + "block from time %d, which is after or within %d seconds of key creation, and " + "could contain transactions pertaining to the key. As a result, transactions " + "and coins using this key may not appear in the wallet. This error could be " + "caused by pruning or data corruption (see bitcoind log for details) and could " + "be dealt with by downloading and rescanning the relevant blocks (see -reindex " + "and -rescan options).", + GetImportTimestamp(request, now), scannedTime - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW))); + response.push_back(std::move(result)); + } + ++i; + } + } + } + + return response; +} diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp new file mode 100644 index 0000000000..b466cf1a81 --- /dev/null +++ b/src/wallet/rpcwallet.cpp @@ -0,0 +1,3592 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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 <amount.h> +#include <base58.h> +#include <chain.h> +#include <consensus/validation.h> +#include <core_io.h> +#include <httpserver.h> +#include <validation.h> +#include <net.h> +#include <policy/feerate.h> +#include <policy/fees.h> +#include <policy/policy.h> +#include <policy/rbf.h> +#include <rpc/mining.h> +#include <rpc/safemode.h> +#include <rpc/server.h> +#include <rpc/util.h> +#include <script/sign.h> +#include <timedata.h> +#include <util.h> +#include <utilmoneystr.h> +#include <wallet/coincontrol.h> +#include <wallet/feebumper.h> +#include <wallet/wallet.h> +#include <wallet/walletdb.h> +#include <wallet/walletutil.h> + +#include <init.h> // For StartShutdown + +#include <stdint.h> + +#include <univalue.h> + +static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; + +CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request) +{ + if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { + // wallet endpoint was used + std::string requestedWallet = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); + for (CWalletRef pwallet : ::vpwallets) { + if (pwallet->GetName() == requestedWallet) { + return pwallet; + } + } + throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); + } + return ::vpwallets.size() == 1 || (request.fHelp && ::vpwallets.size() > 0) ? ::vpwallets[0] : nullptr; +} + +std::string HelpRequiringPassphrase(CWallet * const pwallet) +{ + return pwallet && pwallet->IsCrypted() + ? "\nRequires wallet passphrase to be set with walletpassphrase call." + : ""; +} + +bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException) +{ + if (pwallet) return true; + if (avoidException) return false; + if (::vpwallets.empty()) { + // Note: It isn't currently possible to trigger this error because + // wallet RPC methods aren't registered unless a wallet is loaded. But + // this error is being kept as a precaution, because it's possible in + // the future that wallet RPC methods might get or remain registered + // when no wallets are loaded. + throw JSONRPCError( + RPC_METHOD_NOT_FOUND, "Method not found (wallet method is disabled because no wallet is loaded)"); + } + throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED, + "Wallet file not specified (must request wallet RPC through /wallet/<filename> uri-path)."); +} + +void EnsureWalletIsUnlocked(CWallet * const pwallet) +{ + if (pwallet->IsLocked()) { + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); + } +} + +void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) +{ + int confirms = wtx.GetDepthInMainChain(); + entry.pushKV("confirmations", confirms); + if (wtx.IsCoinBase()) + entry.pushKV("generated", true); + if (confirms > 0) + { + entry.pushKV("blockhash", wtx.hashBlock.GetHex()); + entry.pushKV("blockindex", wtx.nIndex); + entry.pushKV("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()); + } else { + entry.pushKV("trusted", wtx.IsTrusted()); + } + uint256 hash = wtx.GetHash(); + entry.pushKV("txid", hash.GetHex()); + UniValue conflicts(UniValue::VARR); + for (const uint256& conflict : wtx.GetConflicts()) + conflicts.push_back(conflict.GetHex()); + entry.pushKV("walletconflicts", conflicts); + entry.pushKV("time", wtx.GetTxTime()); + entry.pushKV("timereceived", (int64_t)wtx.nTimeReceived); + + // Add opt-in RBF status + std::string rbfStatus = "no"; + if (confirms <= 0) { + LOCK(mempool.cs); + RBFTransactionState rbfState = IsRBFOptIn(*wtx.tx, mempool); + if (rbfState == RBF_TRANSACTIONSTATE_UNKNOWN) + rbfStatus = "unknown"; + else if (rbfState == RBF_TRANSACTIONSTATE_REPLACEABLE_BIP125) + rbfStatus = "yes"; + } + entry.pushKV("bip125-replaceable", rbfStatus); + + for (const std::pair<std::string, std::string>& item : wtx.mapValue) + entry.pushKV(item.first, item.second); +} + +std::string AccountFromValue(const UniValue& value) +{ + std::string strAccount = value.get_str(); + if (strAccount == "*") + throw JSONRPCError(RPC_WALLET_INVALID_ACCOUNT_NAME, "Invalid account name"); + return strAccount; +} + +UniValue getnewaddress(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 2) + throw std::runtime_error( + "getnewaddress ( \"account\" \"address_type\" )\n" + "\nReturns a new Bitcoin address for receiving payments.\n" + "If 'account' is specified (DEPRECATED), it is added to the address book \n" + "so payments received with the address will be credited to 'account'.\n" + "\nArguments:\n" + "1. \"account\" (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n" + "2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n" + "\nResult:\n" + "\"address\" (string) The new bitcoin address\n" + "\nExamples:\n" + + HelpExampleCli("getnewaddress", "") + + HelpExampleRpc("getnewaddress", "") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + // Parse the account first so we don't generate a key if there's an error + std::string strAccount; + if (!request.params[0].isNull()) + strAccount = AccountFromValue(request.params[0]); + + OutputType output_type = g_address_type; + if (!request.params[1].isNull()) { + output_type = ParseOutputType(request.params[1].get_str(), g_address_type); + if (output_type == OUTPUT_TYPE_NONE) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str())); + } + } + + if (!pwallet->IsLocked()) { + pwallet->TopUpKeyPool(); + } + + // Generate a new key that is added to wallet + CPubKey newKey; + if (!pwallet->GetKeyFromPool(newKey)) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + } + pwallet->LearnRelatedScripts(newKey, output_type); + CTxDestination dest = GetDestinationForKey(newKey, output_type); + + pwallet->SetAddressBook(dest, strAccount, "receive"); + + return EncodeDestination(dest); +} + + +CTxDestination GetAccountDestination(CWallet* const pwallet, std::string strAccount, bool bForceNew=false) +{ + CTxDestination dest; + if (!pwallet->GetAccountDestination(dest, strAccount, bForceNew)) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + } + + return dest; +} + +UniValue getaccountaddress(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "getaccountaddress \"account\"\n" + "\nDEPRECATED. Returns the current Bitcoin address for receiving payments to this account.\n" + "\nArguments:\n" + "1. \"account\" (string, required) The account name for the address. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created and a new address created if there is no account by the given name.\n" + "\nResult:\n" + "\"address\" (string) The account bitcoin address\n" + "\nExamples:\n" + + HelpExampleCli("getaccountaddress", "") + + HelpExampleCli("getaccountaddress", "\"\"") + + HelpExampleCli("getaccountaddress", "\"myaccount\"") + + HelpExampleRpc("getaccountaddress", "\"myaccount\"") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + // Parse the account first so we don't generate a key if there's an error + std::string strAccount = AccountFromValue(request.params[0]); + + UniValue ret(UniValue::VSTR); + + ret = EncodeDestination(GetAccountDestination(pwallet, strAccount)); + return ret; +} + + +UniValue getrawchangeaddress(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 1) + throw std::runtime_error( + "getrawchangeaddress ( \"address_type\" )\n" + "\nReturns a new Bitcoin address, for receiving change.\n" + "This is for use with raw transactions, NOT normal use.\n" + "\nArguments:\n" + "1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" + "\nResult:\n" + "\"address\" (string) The address\n" + "\nExamples:\n" + + HelpExampleCli("getrawchangeaddress", "") + + HelpExampleRpc("getrawchangeaddress", "") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + if (!pwallet->IsLocked()) { + pwallet->TopUpKeyPool(); + } + + OutputType output_type = g_change_type != OUTPUT_TYPE_NONE ? g_change_type : g_address_type; + if (!request.params[0].isNull()) { + output_type = ParseOutputType(request.params[0].get_str(), output_type); + if (output_type == OUTPUT_TYPE_NONE) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); + } + } + + CReserveKey reservekey(pwallet); + CPubKey vchPubKey; + if (!reservekey.GetReservedKey(vchPubKey, true)) + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + + reservekey.KeepKey(); + + pwallet->LearnRelatedScripts(vchPubKey, output_type); + CTxDestination dest = GetDestinationForKey(vchPubKey, output_type); + + return EncodeDestination(dest); +} + + +UniValue setaccount(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + throw std::runtime_error( + "setaccount \"address\" \"account\"\n" + "\nDEPRECATED. Sets the account associated with the given address.\n" + "\nArguments:\n" + "1. \"address\" (string, required) The bitcoin address to be associated with an account.\n" + "2. \"account\" (string, required) The account to assign the address to.\n" + "\nExamples:\n" + + HelpExampleCli("setaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") + + HelpExampleRpc("setaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + CTxDestination dest = DecodeDestination(request.params[0].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + } + + std::string strAccount; + if (!request.params[1].isNull()) + strAccount = AccountFromValue(request.params[1]); + + // Only add the account if the address is yours. + if (IsMine(*pwallet, dest)) { + // Detect when changing the account of an address that is the 'unused current key' of another account: + if (pwallet->mapAddressBook.count(dest)) { + std::string strOldAccount = pwallet->mapAddressBook[dest].name; + if (dest == GetAccountDestination(pwallet, strOldAccount)) { + GetAccountDestination(pwallet, strOldAccount, true); + } + } + pwallet->SetAddressBook(dest, strAccount, "receive"); + } + else + throw JSONRPCError(RPC_MISC_ERROR, "setaccount can only be used with own address"); + + return NullUniValue; +} + + +UniValue getaccount(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "getaccount \"address\"\n" + "\nDEPRECATED. Returns the account associated with the given address.\n" + "\nArguments:\n" + "1. \"address\" (string, required) The bitcoin address for account lookup.\n" + "\nResult:\n" + "\"accountname\" (string) the account address\n" + "\nExamples:\n" + + HelpExampleCli("getaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") + + HelpExampleRpc("getaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + CTxDestination dest = DecodeDestination(request.params[0].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + } + + std::string strAccount; + std::map<CTxDestination, CAddressBookData>::iterator mi = pwallet->mapAddressBook.find(dest); + if (mi != pwallet->mapAddressBook.end() && !(*mi).second.name.empty()) { + strAccount = (*mi).second.name; + } + return strAccount; +} + + +UniValue getaddressesbyaccount(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "getaddressesbyaccount \"account\"\n" + "\nDEPRECATED. Returns the list of addresses for the given account.\n" + "\nArguments:\n" + "1. \"account\" (string, required) The account name.\n" + "\nResult:\n" + "[ (json array of string)\n" + " \"address\" (string) a bitcoin address associated with the given account\n" + " ,...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getaddressesbyaccount", "\"tabby\"") + + HelpExampleRpc("getaddressesbyaccount", "\"tabby\"") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + std::string strAccount = AccountFromValue(request.params[0]); + + // Find all addresses that have the given account + UniValue ret(UniValue::VARR); + for (const std::pair<CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { + const CTxDestination& dest = item.first; + const std::string& strName = item.second.name; + if (strName == strAccount) { + ret.push_back(EncodeDestination(dest)); + } + } + return ret; +} + +static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew, const CCoinControl& coin_control) +{ + CAmount curBalance = pwallet->GetBalance(); + + // Check amount + if (nValue <= 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount"); + + if (nValue > curBalance) + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); + + if (pwallet->GetBroadcastTransactions() && !g_connman) { + throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + } + + // Parse Bitcoin address + CScript scriptPubKey = GetScriptForDestination(address); + + // Create and send the transaction + CReserveKey reservekey(pwallet); + CAmount nFeeRequired; + std::string strError; + std::vector<CRecipient> vecSend; + int nChangePosRet = -1; + CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; + vecSend.push_back(recipient); + if (!pwallet->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) { + if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance) + strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired)); + throw JSONRPCError(RPC_WALLET_ERROR, strError); + } + CValidationState state; + if (!pwallet->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); + } +} + +UniValue sendtoaddress(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) + throw std::runtime_error( + "sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount replaceable conf_target \"estimate_mode\")\n" + "\nSend an amount to a given address.\n" + + HelpRequiringPassphrase(pwallet) + + "\nArguments:\n" + "1. \"address\" (string, required) The bitcoin address to send to.\n" + "2. \"amount\" (numeric or string, required) The amount in " + CURRENCY_UNIT + " to send. eg 0.1\n" + "3. \"comment\" (string, optional) A comment used to store what the transaction is for. \n" + " This is not part of the transaction, just kept in your wallet.\n" + "4. \"comment_to\" (string, optional) A comment to store the name of the person or organization \n" + " to which you're sending the transaction. This is not part of the \n" + " transaction, just kept in your wallet.\n" + "5. subtractfeefromamount (boolean, optional, default=false) The fee will be deducted from the amount being sent.\n" + " The recipient will receive less bitcoins than you enter in the amount field.\n" + "6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n" + "7. conf_target (numeric, optional) Confirmation target (in blocks)\n" + "8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" + "\nResult:\n" + "\"txid\" (string) The transaction id.\n" + "\nExamples:\n" + + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1") + + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"donation\" \"seans outpost\"") + + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"\" \"\" true") + + HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.1, \"donation\", \"seans outpost\"") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + CTxDestination dest = DecodeDestination(request.params[0].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + + // Amount + CAmount nAmount = AmountFromValue(request.params[1]); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); + + // Wallet comments + CWalletTx wtx; + if (!request.params[2].isNull() && !request.params[2].get_str().empty()) + wtx.mapValue["comment"] = request.params[2].get_str(); + if (!request.params[3].isNull() && !request.params[3].get_str().empty()) + wtx.mapValue["to"] = request.params[3].get_str(); + + bool fSubtractFeeFromAmount = false; + if (!request.params[4].isNull()) { + fSubtractFeeFromAmount = request.params[4].get_bool(); + } + + CCoinControl coin_control; + if (!request.params[5].isNull()) { + coin_control.signalRbf = request.params[5].get_bool(); + } + + if (!request.params[6].isNull()) { + coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]); + } + + if (!request.params[7].isNull()) { + if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } + } + + + EnsureWalletIsUnlocked(pwallet); + + SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, wtx, coin_control); + + return wtx.GetHash().GetHex(); +} + +UniValue listaddressgroupings(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 0) + throw std::runtime_error( + "listaddressgroupings\n" + "\nLists groups of addresses which have had their common ownership\n" + "made public by common use as inputs or as the resulting change\n" + "in past transactions\n" + "\nResult:\n" + "[\n" + " [\n" + " [\n" + " \"address\", (string) The bitcoin address\n" + " amount, (numeric) The amount in " + CURRENCY_UNIT + "\n" + " \"account\" (string, optional) DEPRECATED. The account\n" + " ]\n" + " ,...\n" + " ]\n" + " ,...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("listaddressgroupings", "") + + HelpExampleRpc("listaddressgroupings", "") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + UniValue jsonGroupings(UniValue::VARR); + std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(); + for (const std::set<CTxDestination>& grouping : pwallet->GetAddressGroupings()) { + UniValue jsonGrouping(UniValue::VARR); + for (const CTxDestination& address : grouping) + { + UniValue addressInfo(UniValue::VARR); + addressInfo.push_back(EncodeDestination(address)); + addressInfo.push_back(ValueFromAmount(balances[address])); + { + if (pwallet->mapAddressBook.find(address) != pwallet->mapAddressBook.end()) { + addressInfo.push_back(pwallet->mapAddressBook.find(address)->second.name); + } + } + jsonGrouping.push_back(addressInfo); + } + jsonGroupings.push_back(jsonGrouping); + } + return jsonGroupings; +} + +UniValue signmessage(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 2) + throw std::runtime_error( + "signmessage \"address\" \"message\"\n" + "\nSign a message with the private key of an address" + + HelpRequiringPassphrase(pwallet) + "\n" + "\nArguments:\n" + "1. \"address\" (string, required) The bitcoin address to use for the private key.\n" + "2. \"message\" (string, required) The message to create a signature of.\n" + "\nResult:\n" + "\"signature\" (string) The signature of the message encoded in base 64\n" + "\nExamples:\n" + "\nUnlock the wallet for 30 seconds\n" + + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + + "\nCreate the signature\n" + + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + + "\nVerify the signature\n" + + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + + "\nAs json rpc\n" + + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + EnsureWalletIsUnlocked(pwallet); + + std::string strAddress = request.params[0].get_str(); + std::string strMessage = request.params[1].get_str(); + + CTxDestination dest = DecodeDestination(strAddress); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); + } + + const CKeyID *keyID = boost::get<CKeyID>(&dest); + if (!keyID) { + throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); + } + + CKey key; + if (!pwallet->GetKey(*keyID, key)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); + } + + CHashWriter ss(SER_GETHASH, 0); + ss << strMessageMagic; + ss << strMessage; + + std::vector<unsigned char> vchSig; + if (!key.SignCompact(ss.GetHash(), vchSig)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); + + return EncodeBase64(vchSig.data(), vchSig.size()); +} + +UniValue getreceivedbyaddress(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + throw std::runtime_error( + "getreceivedbyaddress \"address\" ( minconf )\n" + "\nReturns the total amount received by the given address in transactions with at least minconf confirmations.\n" + "\nArguments:\n" + "1. \"address\" (string, required) The bitcoin address for transactions.\n" + "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" + "\nResult:\n" + "amount (numeric) The total amount in " + CURRENCY_UNIT + " received at this address.\n" + "\nExamples:\n" + "\nThe amount from transactions with at least 1 confirmation\n" + + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") + + "\nThe amount including unconfirmed transactions, zero confirmations\n" + + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 0") + + "\nThe amount with at least 6 confirmations\n" + + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 6") + + "\nAs a json rpc call\n" + + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + // Bitcoin address + CTxDestination dest = DecodeDestination(request.params[0].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + } + CScript scriptPubKey = GetScriptForDestination(dest); + if (!IsMine(*pwallet, scriptPubKey)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet"); + } + + // Minimum confirmations + int nMinDepth = 1; + if (!request.params[1].isNull()) + nMinDepth = request.params[1].get_int(); + + // Tally + CAmount nAmount = 0; + for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + const CWalletTx& wtx = pairWtx.second; + if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) + continue; + + for (const CTxOut& txout : wtx.tx->vout) + if (txout.scriptPubKey == scriptPubKey) + if (wtx.GetDepthInMainChain() >= nMinDepth) + nAmount += txout.nValue; + } + + return ValueFromAmount(nAmount); +} + + +UniValue getreceivedbyaccount(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + throw std::runtime_error( + "getreceivedbyaccount \"account\" ( minconf )\n" + "\nDEPRECATED. Returns the total amount received by addresses with <account> in transactions with at least [minconf] confirmations.\n" + "\nArguments:\n" + "1. \"account\" (string, required) The selected account, may be the default account using \"\".\n" + "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" + "\nResult:\n" + "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n" + "\nExamples:\n" + "\nAmount received by the default account with at least 1 confirmation\n" + + HelpExampleCli("getreceivedbyaccount", "\"\"") + + "\nAmount received at the tabby account including unconfirmed amounts with zero confirmations\n" + + HelpExampleCli("getreceivedbyaccount", "\"tabby\" 0") + + "\nThe amount with at least 6 confirmations\n" + + HelpExampleCli("getreceivedbyaccount", "\"tabby\" 6") + + "\nAs a json rpc call\n" + + HelpExampleRpc("getreceivedbyaccount", "\"tabby\", 6") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + // Minimum confirmations + int nMinDepth = 1; + if (!request.params[1].isNull()) + nMinDepth = request.params[1].get_int(); + + // Get the set of pub keys assigned to account + std::string strAccount = AccountFromValue(request.params[0]); + std::set<CTxDestination> setAddress = pwallet->GetAccountAddresses(strAccount); + + // Tally + CAmount nAmount = 0; + for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + const CWalletTx& wtx = pairWtx.second; + if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) + continue; + + for (const CTxOut& txout : wtx.tx->vout) + { + CTxDestination address; + if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwallet, address) && setAddress.count(address)) { + if (wtx.GetDepthInMainChain() >= nMinDepth) + nAmount += txout.nValue; + } + } + } + + return ValueFromAmount(nAmount); +} + + +UniValue getbalance(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 3) + throw std::runtime_error( + "getbalance ( \"account\" minconf include_watchonly )\n" + "\nIf account is not specified, returns the server's total available balance.\n" + "The available balance is what the wallet considers currently spendable, and is\n" + "thus affected by options which limit spendability such as -spendzeroconfchange.\n" + "If account is specified (DEPRECATED), returns the balance in the account.\n" + "Note that the account \"\" is not the same as leaving the parameter out.\n" + "The server total may be different to the balance in the default \"\" account.\n" + "\nArguments:\n" + "1. \"account\" (string, optional) DEPRECATED. The account string may be given as a\n" + " specific account name to find the balance associated with wallet keys in\n" + " a named account, or as the empty string (\"\") to find the balance\n" + " associated with wallet keys not in any named account, or as \"*\" to find\n" + " the balance associated with all wallet keys regardless of account.\n" + " When this option is specified, it calculates the balance in a different\n" + " way than when it is not specified, and which can count spends twice when\n" + " there are conflicting pending transactions (such as those created by\n" + " the bumpfee command), temporarily resulting in low or even negative\n" + " balances. In general, account balance calculation is not considered\n" + " reliable and has resulted in confusing outcomes, so it is recommended to\n" + " avoid passing this argument.\n" + "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" + "3. include_watchonly (bool, optional, default=false) Also include balance in watch-only addresses (see 'importaddress')\n" + "\nResult:\n" + "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n" + "\nExamples:\n" + "\nThe total amount in the wallet with 1 or more confirmations\n" + + HelpExampleCli("getbalance", "") + + "\nThe total amount in the wallet at least 6 blocks confirmed\n" + + HelpExampleCli("getbalance", "\"*\" 6") + + "\nAs a json rpc call\n" + + HelpExampleRpc("getbalance", "\"*\", 6") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + const UniValue& account_value = request.params[0]; + const UniValue& minconf = request.params[1]; + const UniValue& include_watchonly = request.params[2]; + + if (account_value.isNull()) { + if (!minconf.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "getbalance minconf option is only currently supported if an account is specified"); + } + if (!include_watchonly.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "getbalance include_watchonly option is only currently supported if an account is specified"); + } + return ValueFromAmount(pwallet->GetBalance()); + } + + const std::string& account_param = account_value.get_str(); + const std::string* account = account_param != "*" ? &account_param : nullptr; + + int nMinDepth = 1; + if (!minconf.isNull()) + nMinDepth = minconf.get_int(); + isminefilter filter = ISMINE_SPENDABLE; + if(!include_watchonly.isNull()) + if(include_watchonly.get_bool()) + filter = filter | ISMINE_WATCH_ONLY; + + return ValueFromAmount(pwallet->GetLegacyBalance(filter, nMinDepth, account)); +} + +UniValue getunconfirmedbalance(const JSONRPCRequest &request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 0) + throw std::runtime_error( + "getunconfirmedbalance\n" + "Returns the server's total unconfirmed balance\n"); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + return ValueFromAmount(pwallet->GetUnconfirmedBalance()); +} + + +UniValue movecmd(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 3 || request.params.size() > 5) + throw std::runtime_error( + "move \"fromaccount\" \"toaccount\" amount ( minconf \"comment\" )\n" + "\nDEPRECATED. Move a specified amount from one account in your wallet to another.\n" + "\nArguments:\n" + "1. \"fromaccount\" (string, required) The name of the account to move funds from. May be the default account using \"\".\n" + "2. \"toaccount\" (string, required) The name of the account to move funds to. May be the default account using \"\".\n" + "3. amount (numeric) Quantity of " + CURRENCY_UNIT + " to move between accounts.\n" + "4. (dummy) (numeric, optional) Ignored. Remains for backward compatibility.\n" + "5. \"comment\" (string, optional) An optional comment, stored in the wallet only.\n" + "\nResult:\n" + "true|false (boolean) true if successful.\n" + "\nExamples:\n" + "\nMove 0.01 " + CURRENCY_UNIT + " from the default account to the account named tabby\n" + + HelpExampleCli("move", "\"\" \"tabby\" 0.01") + + "\nMove 0.01 " + CURRENCY_UNIT + " timotei to akiko with a comment and funds have 6 confirmations\n" + + HelpExampleCli("move", "\"timotei\" \"akiko\" 0.01 6 \"happy birthday!\"") + + "\nAs a json rpc call\n" + + HelpExampleRpc("move", "\"timotei\", \"akiko\", 0.01, 6, \"happy birthday!\"") + ); + + ObserveSafeMode(); + LOCK2(cs_main, pwallet->cs_wallet); + + std::string strFrom = AccountFromValue(request.params[0]); + std::string strTo = AccountFromValue(request.params[1]); + CAmount nAmount = AmountFromValue(request.params[2]); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); + if (!request.params[3].isNull()) + // unused parameter, used to be nMinDepth, keep type-checking it though + (void)request.params[3].get_int(); + std::string strComment; + if (!request.params[4].isNull()) + strComment = request.params[4].get_str(); + + if (!pwallet->AccountMove(strFrom, strTo, nAmount, strComment)) { + throw JSONRPCError(RPC_DATABASE_ERROR, "database error"); + } + + return true; +} + + +UniValue sendfrom(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 3 || request.params.size() > 6) + throw std::runtime_error( + "sendfrom \"fromaccount\" \"toaddress\" amount ( minconf \"comment\" \"comment_to\" )\n" + "\nDEPRECATED (use sendtoaddress). Sent an amount from an account to a bitcoin address." + + HelpRequiringPassphrase(pwallet) + "\n" + "\nArguments:\n" + "1. \"fromaccount\" (string, required) The name of the account to send funds from. May be the default account using \"\".\n" + " Specifying an account does not influence coin selection, but it does associate the newly created\n" + " transaction with the account, so the account's balance computation and transaction history can reflect\n" + " the spend.\n" + "2. \"toaddress\" (string, required) The bitcoin address to send funds to.\n" + "3. amount (numeric or string, required) The amount in " + CURRENCY_UNIT + " (transaction fee is added on top).\n" + "4. minconf (numeric, optional, default=1) Only use funds with at least this many confirmations.\n" + "5. \"comment\" (string, optional) A comment used to store what the transaction is for. \n" + " This is not part of the transaction, just kept in your wallet.\n" + "6. \"comment_to\" (string, optional) An optional comment to store the name of the person or organization \n" + " to which you're sending the transaction. This is not part of the transaction, \n" + " it is just kept in your wallet.\n" + "\nResult:\n" + "\"txid\" (string) The transaction id.\n" + "\nExamples:\n" + "\nSend 0.01 " + CURRENCY_UNIT + " from the default account to the address, must have at least 1 confirmation\n" + + HelpExampleCli("sendfrom", "\"\" \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.01") + + "\nSend 0.01 from the tabby account to the given address, funds must have at least 6 confirmations\n" + + HelpExampleCli("sendfrom", "\"tabby\" \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.01 6 \"donation\" \"seans outpost\"") + + "\nAs a json rpc call\n" + + HelpExampleRpc("sendfrom", "\"tabby\", \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.01, 6, \"donation\", \"seans outpost\"") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + std::string strAccount = AccountFromValue(request.params[0]); + CTxDestination dest = DecodeDestination(request.params[1].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + } + CAmount nAmount = AmountFromValue(request.params[2]); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); + int nMinDepth = 1; + if (!request.params[3].isNull()) + nMinDepth = request.params[3].get_int(); + + CWalletTx wtx; + wtx.strFromAccount = strAccount; + if (!request.params[4].isNull() && !request.params[4].get_str().empty()) + wtx.mapValue["comment"] = request.params[4].get_str(); + if (!request.params[5].isNull() && !request.params[5].get_str().empty()) + wtx.mapValue["to"] = request.params[5].get_str(); + + EnsureWalletIsUnlocked(pwallet); + + // Check funds + CAmount nBalance = pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount); + if (nAmount > nBalance) + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); + + CCoinControl no_coin_control; // This is a deprecated API + SendMoney(pwallet, dest, nAmount, false, wtx, no_coin_control); + + return wtx.GetHash().GetHex(); +} + + +UniValue sendmany(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) + throw std::runtime_error( + "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] replaceable conf_target \"estimate_mode\")\n" + "\nSend multiple times. Amounts are double-precision floating point numbers." + + HelpRequiringPassphrase(pwallet) + "\n" + "\nArguments:\n" + "1. \"fromaccount\" (string, required) DEPRECATED. The account to send the funds from. Should be \"\" for the default account\n" + "2. \"amounts\" (string, required) A json object with addresses and amounts\n" + " {\n" + " \"address\":amount (numeric or string) The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value\n" + " ,...\n" + " }\n" + "3. minconf (numeric, optional, default=1) Only use the balance confirmed at least this many times.\n" + "4. \"comment\" (string, optional) A comment\n" + "5. subtractfeefrom (array, optional) A json array with addresses.\n" + " The fee will be equally deducted from the amount of each selected address.\n" + " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" + " If no addresses are specified here, the sender pays the fee.\n" + " [\n" + " \"address\" (string) Subtract fee from this address\n" + " ,...\n" + " ]\n" + "6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n" + "7. conf_target (numeric, optional) Confirmation target (in blocks)\n" + "8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" + "\nResult:\n" + "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" + " the number of addresses.\n" + "\nExamples:\n" + "\nSend two amounts to two different addresses:\n" + + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") + + "\nSend two amounts to two different addresses setting the confirmation and comment:\n" + + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 6 \"testing\"") + + "\nSend two amounts to two different addresses, subtract fee from amount:\n" + + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") + + "\nAs a json rpc call\n" + + HelpExampleRpc("sendmany", "\"\", {\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\":0.01,\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\":0.02}, 6, \"testing\"") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + if (pwallet->GetBroadcastTransactions() && !g_connman) { + throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + } + + std::string strAccount = AccountFromValue(request.params[0]); + UniValue sendTo = request.params[1].get_obj(); + int nMinDepth = 1; + if (!request.params[2].isNull()) + nMinDepth = request.params[2].get_int(); + + CWalletTx wtx; + wtx.strFromAccount = strAccount; + if (!request.params[3].isNull() && !request.params[3].get_str().empty()) + wtx.mapValue["comment"] = request.params[3].get_str(); + + UniValue subtractFeeFromAmount(UniValue::VARR); + if (!request.params[4].isNull()) + subtractFeeFromAmount = request.params[4].get_array(); + + CCoinControl coin_control; + if (!request.params[5].isNull()) { + coin_control.signalRbf = request.params[5].get_bool(); + } + + if (!request.params[6].isNull()) { + coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]); + } + + if (!request.params[7].isNull()) { + if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } + } + + std::set<CTxDestination> destinations; + std::vector<CRecipient> vecSend; + + CAmount totalAmount = 0; + std::vector<std::string> keys = sendTo.getKeys(); + for (const std::string& name_ : keys) { + CTxDestination dest = DecodeDestination(name_); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + name_); + } + + if (destinations.count(dest)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_); + } + destinations.insert(dest); + + CScript scriptPubKey = GetScriptForDestination(dest); + CAmount nAmount = AmountFromValue(sendTo[name_]); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); + totalAmount += nAmount; + + bool fSubtractFeeFromAmount = false; + for (unsigned int idx = 0; idx < subtractFeeFromAmount.size(); idx++) { + const UniValue& addr = subtractFeeFromAmount[idx]; + if (addr.get_str() == name_) + fSubtractFeeFromAmount = true; + } + + CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; + vecSend.push_back(recipient); + } + + EnsureWalletIsUnlocked(pwallet); + + // Check funds + CAmount nBalance = pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount); + if (totalAmount > nBalance) + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); + + // Send + CReserveKey keyChange(pwallet); + CAmount nFeeRequired = 0; + int nChangePosRet = -1; + std::string strFailReason; + bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); + if (!fCreated) + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); + CValidationState state; + if (!pwallet->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(); +} + +UniValue addmultisigaddress(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { + std::string msg = "addmultisigaddress nrequired [\"key\",...] ( \"account\" \"address_type\" )\n" + "\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n" + "Each key is a Bitcoin address or hex-encoded public key.\n" + "This functionality is only intended for use with non-watchonly addresses.\n" + "See `importaddress` for watchonly p2sh address support.\n" + "If 'account' is specified (DEPRECATED), assign address to that account.\n" + + "\nArguments:\n" + "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n" + "2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n" + " [\n" + " \"address\" (string) bitcoin address or hex-encoded public key\n" + " ...,\n" + " ]\n" + "3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n" + "4. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n" + + "\nResult:\n" + "{\n" + " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n" + " \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n" + "}\n" + "\nExamples:\n" + "\nAdd a multisig address from 2 addresses\n" + + HelpExampleCli("addmultisigaddress", "2 \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") + + "\nAs json rpc call\n" + + HelpExampleRpc("addmultisigaddress", "2, \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") + ; + throw std::runtime_error(msg); + } + + LOCK2(cs_main, pwallet->cs_wallet); + + std::string strAccount; + if (!request.params[2].isNull()) + strAccount = AccountFromValue(request.params[2]); + + int required = request.params[0].get_int(); + + // Get the public keys + const UniValue& keys_or_addrs = request.params[1].get_array(); + std::vector<CPubKey> pubkeys; + for (unsigned int i = 0; i < keys_or_addrs.size(); ++i) { + if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) { + pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str())); + } else { + pubkeys.push_back(AddrToPubKey(pwallet, keys_or_addrs[i].get_str())); + } + } + + OutputType output_type = g_address_type; + if (!request.params[3].isNull()) { + output_type = ParseOutputType(request.params[3].get_str(), output_type); + if (output_type == OUTPUT_TYPE_NONE) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str())); + } + } + + // Construct using pay-to-script-hash: + CScript inner = CreateMultisigRedeemscript(required, pubkeys); + pwallet->AddCScript(inner); + CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, output_type); + pwallet->SetAddressBook(dest, strAccount, "send"); + + UniValue result(UniValue::VOBJ); + result.pushKV("address", EncodeDestination(dest)); + result.pushKV("redeemScript", HexStr(inner.begin(), inner.end())); + return result; +} + +class Witnessifier : public boost::static_visitor<bool> +{ +public: + CWallet * const pwallet; + CTxDestination result; + bool already_witness; + + explicit Witnessifier(CWallet *_pwallet) : pwallet(_pwallet), already_witness(false) {} + + bool operator()(const CKeyID &keyID) { + if (pwallet) { + CScript basescript = GetScriptForDestination(keyID); + CScript witscript = GetScriptForWitness(basescript); + if (!IsSolvable(*pwallet, witscript)) { + return false; + } + return ExtractDestination(witscript, result); + } + return false; + } + + bool operator()(const CScriptID &scriptID) { + CScript subscript; + if (pwallet && pwallet->GetCScript(scriptID, subscript)) { + int witnessversion; + std::vector<unsigned char> witprog; + if (subscript.IsWitnessProgram(witnessversion, witprog)) { + ExtractDestination(subscript, result); + already_witness = true; + return true; + } + CScript witscript = GetScriptForWitness(subscript); + if (!IsSolvable(*pwallet, witscript)) { + return false; + } + return ExtractDestination(witscript, result); + } + return false; + } + + bool operator()(const WitnessV0KeyHash& id) + { + already_witness = true; + result = id; + return true; + } + + bool operator()(const WitnessV0ScriptHash& id) + { + already_witness = true; + result = id; + return true; + } + + template<typename T> + bool operator()(const T& dest) { return false; } +}; + +UniValue addwitnessaddress(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + { + std::string msg = "addwitnessaddress \"address\" ( p2sh )\n" + "\nDEPRECATED: set the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n" + "Add a witness address for a script (with pubkey or redeemscript known). Requires a new wallet backup.\n" + "It returns the witness script.\n" + + "\nArguments:\n" + "1. \"address\" (string, required) An address known to the wallet\n" + "2. p2sh (bool, optional, default=true) Embed inside P2SH\n" + + "\nResult:\n" + "\"witnessaddress\", (string) The value of the new address (P2SH or BIP173).\n" + "}\n" + ; + throw std::runtime_error(msg); + } + + if (!IsDeprecatedRPCEnabled("addwitnessaddress")) { + throw JSONRPCError(RPC_METHOD_DEPRECATED, "addwitnessaddress is deprecated and will be fully removed in v0.17. " + "To use addwitnessaddress in v0.16, restart bitcoind with -deprecatedrpc=addwitnessaddress.\n" + "Projects should transition to using the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n"); + } + + { + LOCK(cs_main); + if (!IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus()) && !gArgs.GetBoolArg("-walletprematurewitness", false)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Segregated witness not enabled on network"); + } + } + + CTxDestination dest = DecodeDestination(request.params[0].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + } + + bool p2sh = true; + if (!request.params[1].isNull()) { + p2sh = request.params[1].get_bool(); + } + + Witnessifier w(pwallet); + bool ret = boost::apply_visitor(w, dest); + if (!ret) { + throw JSONRPCError(RPC_WALLET_ERROR, "Public key or redeemscript not known to wallet, or the key is uncompressed"); + } + + CScript witprogram = GetScriptForDestination(w.result); + + if (p2sh) { + w.result = CScriptID(witprogram); + } + + if (w.already_witness) { + if (!(dest == w.result)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Cannot convert between witness address types"); + } + } else { + pwallet->AddCScript(witprogram); // Implicit for single-key now, but necessary for multisig and for compatibility with older software + pwallet->SetAddressBook(w.result, "", "receive"); + } + + return EncodeDestination(w.result); +} + +struct tallyitem +{ + CAmount nAmount; + int nConf; + std::vector<uint256> txids; + bool fIsWatchonly; + tallyitem() + { + nAmount = 0; + nConf = std::numeric_limits<int>::max(); + fIsWatchonly = false; + } +}; + +UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByAccounts) +{ + // Minimum confirmations + int nMinDepth = 1; + if (!params[0].isNull()) + nMinDepth = params[0].get_int(); + + // Whether to include empty accounts + bool fIncludeEmpty = false; + if (!params[1].isNull()) + fIncludeEmpty = params[1].get_bool(); + + isminefilter filter = ISMINE_SPENDABLE; + if(!params[2].isNull()) + if(params[2].get_bool()) + filter = filter | ISMINE_WATCH_ONLY; + + // Tally + std::map<CTxDestination, tallyitem> mapTally; + for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + const CWalletTx& wtx = pairWtx.second; + + if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) + continue; + + int nDepth = wtx.GetDepthInMainChain(); + if (nDepth < nMinDepth) + continue; + + for (const CTxOut& txout : wtx.tx->vout) + { + CTxDestination address; + if (!ExtractDestination(txout.scriptPubKey, address)) + continue; + + isminefilter mine = IsMine(*pwallet, address); + if(!(mine & filter)) + continue; + + tallyitem& item = mapTally[address]; + item.nAmount += txout.nValue; + item.nConf = std::min(item.nConf, nDepth); + item.txids.push_back(wtx.GetHash()); + if (mine & ISMINE_WATCH_ONLY) + item.fIsWatchonly = true; + } + } + + // Reply + UniValue ret(UniValue::VARR); + std::map<std::string, tallyitem> mapAccountTally; + for (const std::pair<CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { + const CTxDestination& dest = item.first; + const std::string& strAccount = item.second.name; + std::map<CTxDestination, tallyitem>::iterator it = mapTally.find(dest); + if (it == mapTally.end() && !fIncludeEmpty) + continue; + + CAmount nAmount = 0; + int nConf = std::numeric_limits<int>::max(); + bool fIsWatchonly = false; + if (it != mapTally.end()) + { + nAmount = (*it).second.nAmount; + nConf = (*it).second.nConf; + fIsWatchonly = (*it).second.fIsWatchonly; + } + + if (fByAccounts) + { + tallyitem& _item = mapAccountTally[strAccount]; + _item.nAmount += nAmount; + _item.nConf = std::min(_item.nConf, nConf); + _item.fIsWatchonly = fIsWatchonly; + } + else + { + UniValue obj(UniValue::VOBJ); + if(fIsWatchonly) + obj.pushKV("involvesWatchonly", true); + obj.pushKV("address", EncodeDestination(dest)); + obj.pushKV("account", strAccount); + obj.pushKV("amount", ValueFromAmount(nAmount)); + obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)); + if (!fByAccounts) + obj.pushKV("label", strAccount); + UniValue transactions(UniValue::VARR); + if (it != mapTally.end()) + { + for (const uint256& _item : (*it).second.txids) + { + transactions.push_back(_item.GetHex()); + } + } + obj.pushKV("txids", transactions); + ret.push_back(obj); + } + } + + if (fByAccounts) + { + for (const auto& entry : mapAccountTally) + { + CAmount nAmount = entry.second.nAmount; + int nConf = entry.second.nConf; + UniValue obj(UniValue::VOBJ); + if (entry.second.fIsWatchonly) + obj.pushKV("involvesWatchonly", true); + obj.pushKV("account", entry.first); + obj.pushKV("amount", ValueFromAmount(nAmount)); + obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)); + ret.push_back(obj); + } + } + + return ret; +} + +UniValue listreceivedbyaddress(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 3) + throw std::runtime_error( + "listreceivedbyaddress ( minconf include_empty include_watchonly)\n" + "\nList balances by receiving address.\n" + "\nArguments:\n" + "1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n" + "2. include_empty (bool, optional, default=false) Whether to include addresses that haven't received any payments.\n" + "3. include_watchonly (bool, optional, default=false) Whether to include watch-only addresses (see 'importaddress').\n" + + "\nResult:\n" + "[\n" + " {\n" + " \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n" + " \"address\" : \"receivingaddress\", (string) The receiving address\n" + " \"account\" : \"accountname\", (string) DEPRECATED. The account of the receiving address. The default account is \"\".\n" + " \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " received by the address\n" + " \"confirmations\" : n, (numeric) The number of confirmations of the most recent transaction included\n" + " \"label\" : \"label\", (string) A comment for the address/transaction, if any\n" + " \"txids\": [\n" + " n, (numeric) The ids of transactions received with the address \n" + " ...\n" + " ]\n" + " }\n" + " ,...\n" + "]\n" + + "\nExamples:\n" + + HelpExampleCli("listreceivedbyaddress", "") + + HelpExampleCli("listreceivedbyaddress", "6 true") + + HelpExampleRpc("listreceivedbyaddress", "6, true, true") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + return ListReceived(pwallet, request.params, false); +} + +UniValue listreceivedbyaccount(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 3) + throw std::runtime_error( + "listreceivedbyaccount ( minconf include_empty include_watchonly)\n" + "\nDEPRECATED. List balances by account.\n" + "\nArguments:\n" + "1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n" + "2. include_empty (bool, optional, default=false) Whether to include accounts that haven't received any payments.\n" + "3. include_watchonly (bool, optional, default=false) Whether to include watch-only addresses (see 'importaddress').\n" + + "\nResult:\n" + "[\n" + " {\n" + " \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n" + " \"account\" : \"accountname\", (string) The account name of the receiving account\n" + " \"amount\" : x.xxx, (numeric) The total amount received by addresses with this account\n" + " \"confirmations\" : n, (numeric) The number of confirmations of the most recent transaction included\n" + " \"label\" : \"label\" (string) A comment for the address/transaction, if any\n" + " }\n" + " ,...\n" + "]\n" + + "\nExamples:\n" + + HelpExampleCli("listreceivedbyaccount", "") + + HelpExampleCli("listreceivedbyaccount", "6 true") + + HelpExampleRpc("listreceivedbyaccount", "6, true, true") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + return ListReceived(pwallet, request.params, true); +} + +static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) +{ + if (IsValidDestination(dest)) { + entry.pushKV("address", EncodeDestination(dest)); + } +} + +/** + * List transactions based on the given criteria. + * + * @param pwallet The wallet. + * @param wtx The wallet transaction. + * @param strAccount The account, if any, or "*" for all. + * @param nMinDepth The minimum confirmation depth. + * @param fLong Whether to include the JSON version of the transaction. + * @param ret The UniValue into which the result is stored. + * @param filter The "is mine" filter bool. + */ +void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) +{ + CAmount nFee; + std::string strSentAccount; + std::list<COutputEntry> listReceived; + std::list<COutputEntry> listSent; + + wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, filter); + + bool fAllAccounts = (strAccount == std::string("*")); + bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); + + // Sent + if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount)) + { + for (const COutputEntry& s : listSent) + { + UniValue entry(UniValue::VOBJ); + if (involvesWatchonly || (::IsMine(*pwallet, s.destination) & ISMINE_WATCH_ONLY)) { + entry.pushKV("involvesWatchonly", true); + } + entry.pushKV("account", strSentAccount); + MaybePushAddress(entry, s.destination); + entry.pushKV("category", "send"); + entry.pushKV("amount", ValueFromAmount(-s.amount)); + if (pwallet->mapAddressBook.count(s.destination)) { + entry.pushKV("label", pwallet->mapAddressBook[s.destination].name); + } + entry.pushKV("vout", s.vout); + entry.pushKV("fee", ValueFromAmount(-nFee)); + if (fLong) + WalletTxToJSON(wtx, entry); + entry.pushKV("abandoned", wtx.isAbandoned()); + ret.push_back(entry); + } + } + + // Received + if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) + { + for (const COutputEntry& r : listReceived) + { + std::string account; + if (pwallet->mapAddressBook.count(r.destination)) { + account = pwallet->mapAddressBook[r.destination].name; + } + if (fAllAccounts || (account == strAccount)) + { + UniValue entry(UniValue::VOBJ); + if (involvesWatchonly || (::IsMine(*pwallet, r.destination) & ISMINE_WATCH_ONLY)) { + entry.pushKV("involvesWatchonly", true); + } + entry.pushKV("account", account); + MaybePushAddress(entry, r.destination); + if (wtx.IsCoinBase()) + { + if (wtx.GetDepthInMainChain() < 1) + entry.pushKV("category", "orphan"); + else if (wtx.GetBlocksToMaturity() > 0) + entry.pushKV("category", "immature"); + else + entry.pushKV("category", "generate"); + } + else + { + entry.pushKV("category", "receive"); + } + entry.pushKV("amount", ValueFromAmount(r.amount)); + if (pwallet->mapAddressBook.count(r.destination)) { + entry.pushKV("label", account); + } + entry.pushKV("vout", r.vout); + if (fLong) + WalletTxToJSON(wtx, entry); + ret.push_back(entry); + } + } + } +} + +void AcentryToJSON(const CAccountingEntry& acentry, const std::string& strAccount, UniValue& ret) +{ + bool fAllAccounts = (strAccount == std::string("*")); + + if (fAllAccounts || acentry.strAccount == strAccount) + { + UniValue entry(UniValue::VOBJ); + entry.pushKV("account", acentry.strAccount); + entry.pushKV("category", "move"); + entry.pushKV("time", acentry.nTime); + entry.pushKV("amount", ValueFromAmount(acentry.nCreditDebit)); + entry.pushKV("otheraccount", acentry.strOtherAccount); + entry.pushKV("comment", acentry.strComment); + ret.push_back(entry); + } +} + +UniValue listtransactions(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 4) + throw std::runtime_error( + "listtransactions ( \"account\" count skip include_watchonly)\n" + "\nReturns up to 'count' most recent transactions skipping the first 'from' transactions for account 'account'.\n" + "\nArguments:\n" + "1. \"account\" (string, optional) DEPRECATED. The account name. Should be \"*\".\n" + "2. count (numeric, optional, default=10) The number of transactions to return\n" + "3. skip (numeric, optional, default=0) The number of transactions to skip\n" + "4. include_watchonly (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n" + "\nResult:\n" + "[\n" + " {\n" + " \"account\":\"accountname\", (string) DEPRECATED. The account name associated with the transaction. \n" + " It will be \"\" for the default account.\n" + " \"address\":\"address\", (string) The bitcoin address of the transaction. Not present for \n" + " move transactions (category = move).\n" + " \"category\":\"send|receive|move\", (string) The transaction category. 'move' is a local (off blockchain)\n" + " transaction between accounts, and not associated with an address,\n" + " transaction id or block. 'send' and 'receive' transactions are \n" + " associated with an address, transaction id and block details\n" + " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and for the\n" + " 'move' category for moves outbound. It is positive for the 'receive' category,\n" + " and for the 'move' category for inbound funds.\n" + " \"label\": \"label\", (string) A comment for the address/transaction, if any\n" + " \"vout\": n, (numeric) the vout value\n" + " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" + " 'send' category of transactions.\n" + " \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and \n" + " 'receive' category of transactions. Negative confirmations indicate the\n" + " transaction conflicts with the block chain\n" + " \"trusted\": xxx, (bool) Whether we consider the outputs of this unconfirmed transaction safe to spend.\n" + " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive'\n" + " category of transactions.\n" + " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it. Available for 'send' and 'receive'\n" + " category of transactions.\n" + " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n" + " \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n" + " \"time\": xxx, (numeric) The transaction time in seconds since epoch (midnight Jan 1 1970 GMT).\n" + " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (midnight Jan 1 1970 GMT). Available \n" + " for 'send' and 'receive' category of transactions.\n" + " \"comment\": \"...\", (string) If a comment is associated with the transaction.\n" + " \"otheraccount\": \"accountname\", (string) DEPRECATED. For the 'move' category of transactions, the account the funds came \n" + " from (for receiving funds, positive amounts), or went to (for sending funds,\n" + " negative amounts).\n" + " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" + " may be unknown for unconfirmed transactions not in the mempool\n" + " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n" + " 'send' category of transactions.\n" + " }\n" + "]\n" + + "\nExamples:\n" + "\nList the most recent 10 transactions in the systems\n" + + HelpExampleCli("listtransactions", "") + + "\nList transactions 100 to 120\n" + + HelpExampleCli("listtransactions", "\"*\" 20 100") + + "\nAs a json rpc call\n" + + HelpExampleRpc("listtransactions", "\"*\", 20, 100") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + std::string strAccount = "*"; + if (!request.params[0].isNull()) + strAccount = request.params[0].get_str(); + int nCount = 10; + if (!request.params[1].isNull()) + nCount = request.params[1].get_int(); + int nFrom = 0; + if (!request.params[2].isNull()) + nFrom = request.params[2].get_int(); + isminefilter filter = ISMINE_SPENDABLE; + if(!request.params[3].isNull()) + if(request.params[3].get_bool()) + filter = filter | ISMINE_WATCH_ONLY; + + if (nCount < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count"); + if (nFrom < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from"); + + UniValue ret(UniValue::VARR); + + { + LOCK2(cs_main, pwallet->cs_wallet); + + const CWallet::TxItems & txOrdered = pwallet->wtxOrdered; + + // iterate backwards until we have nCount items to return: + for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) + { + CWalletTx *const pwtx = (*it).second.first; + if (pwtx != nullptr) + ListTransactions(pwallet, *pwtx, strAccount, 0, true, ret, filter); + CAccountingEntry *const pacentry = (*it).second.second; + if (pacentry != nullptr) + AcentryToJSON(*pacentry, strAccount, ret); + + if ((int)ret.size() >= (nCount+nFrom)) break; + } + } + + // ret is newest to oldest + + if (nFrom > (int)ret.size()) + nFrom = ret.size(); + if ((nFrom + nCount) > (int)ret.size()) + nCount = ret.size() - nFrom; + + std::vector<UniValue> arrTmp = ret.getValues(); + + std::vector<UniValue>::iterator first = arrTmp.begin(); + std::advance(first, nFrom); + std::vector<UniValue>::iterator last = arrTmp.begin(); + std::advance(last, nFrom+nCount); + + if (last != arrTmp.end()) arrTmp.erase(last, arrTmp.end()); + if (first != arrTmp.begin()) arrTmp.erase(arrTmp.begin(), first); + + std::reverse(arrTmp.begin(), arrTmp.end()); // Return oldest to newest + + ret.clear(); + ret.setArray(); + ret.push_backV(arrTmp); + + return ret; +} + +UniValue listaccounts(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 2) + throw std::runtime_error( + "listaccounts ( minconf include_watchonly)\n" + "\nDEPRECATED. Returns Object that has account names as keys, account balances as values.\n" + "\nArguments:\n" + "1. minconf (numeric, optional, default=1) Only include transactions with at least this many confirmations\n" + "2. include_watchonly (bool, optional, default=false) Include balances in watch-only addresses (see 'importaddress')\n" + "\nResult:\n" + "{ (json object where keys are account names, and values are numeric balances\n" + " \"account\": x.xxx, (numeric) The property name is the account name, and the value is the total balance for the account.\n" + " ...\n" + "}\n" + "\nExamples:\n" + "\nList account balances where there at least 1 confirmation\n" + + HelpExampleCli("listaccounts", "") + + "\nList account balances including zero confirmation transactions\n" + + HelpExampleCli("listaccounts", "0") + + "\nList account balances for 6 or more confirmations\n" + + HelpExampleCli("listaccounts", "6") + + "\nAs json rpc call\n" + + HelpExampleRpc("listaccounts", "6") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + int nMinDepth = 1; + if (!request.params[0].isNull()) + nMinDepth = request.params[0].get_int(); + isminefilter includeWatchonly = ISMINE_SPENDABLE; + if(!request.params[1].isNull()) + if(request.params[1].get_bool()) + includeWatchonly = includeWatchonly | ISMINE_WATCH_ONLY; + + std::map<std::string, CAmount> mapAccountBalances; + for (const std::pair<CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) { + if (IsMine(*pwallet, entry.first) & includeWatchonly) { // This address belongs to me + mapAccountBalances[entry.second.name] = 0; + } + } + + for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + const CWalletTx& wtx = pairWtx.second; + CAmount nFee; + std::string strSentAccount; + std::list<COutputEntry> listReceived; + std::list<COutputEntry> listSent; + int nDepth = wtx.GetDepthInMainChain(); + if (wtx.GetBlocksToMaturity() > 0 || nDepth < 0) + continue; + wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, includeWatchonly); + mapAccountBalances[strSentAccount] -= nFee; + for (const COutputEntry& s : listSent) + mapAccountBalances[strSentAccount] -= s.amount; + if (nDepth >= nMinDepth) + { + for (const COutputEntry& r : listReceived) + if (pwallet->mapAddressBook.count(r.destination)) { + mapAccountBalances[pwallet->mapAddressBook[r.destination].name] += r.amount; + } + else + mapAccountBalances[""] += r.amount; + } + } + + const std::list<CAccountingEntry>& acentries = pwallet->laccentries; + for (const CAccountingEntry& entry : acentries) + mapAccountBalances[entry.strAccount] += entry.nCreditDebit; + + UniValue ret(UniValue::VOBJ); + for (const std::pair<std::string, CAmount>& accountBalance : mapAccountBalances) { + ret.pushKV(accountBalance.first, ValueFromAmount(accountBalance.second)); + } + return ret; +} + +UniValue listsinceblock(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 4) + throw std::runtime_error( + "listsinceblock ( \"blockhash\" target_confirmations include_watchonly include_removed )\n" + "\nGet all transactions in blocks since block [blockhash], or all transactions if omitted.\n" + "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n" + "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n" + "\nArguments:\n" + "1. \"blockhash\" (string, optional) The block hash to list transactions since\n" + "2. target_confirmations: (numeric, optional, default=1) Return the nth block hash from the main chain. e.g. 1 would mean the best block hash. Note: this is not used as a filter, but only affects [lastblock] in the return value\n" + "3. include_watchonly: (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n" + "4. include_removed: (bool, optional, default=true) Show transactions that were removed due to a reorg in the \"removed\" array\n" + " (not guaranteed to work on pruned nodes)\n" + "\nResult:\n" + "{\n" + " \"transactions\": [\n" + " \"account\":\"accountname\", (string) DEPRECATED. The account name associated with the transaction. Will be \"\" for the default account.\n" + " \"address\":\"address\", (string) The bitcoin address of the transaction. Not present for move transactions (category = move).\n" + " \"category\":\"send|receive\", (string) The transaction category. 'send' has negative amounts, 'receive' has positive amounts.\n" + " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and for the 'move' category for moves \n" + " outbound. It is positive for the 'receive' category, and for the 'move' category for inbound funds.\n" + " \"vout\" : n, (numeric) the vout value\n" + " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the 'send' category of transactions.\n" + " \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and 'receive' category of transactions.\n" + " When it's < 0, it means the transaction conflicted that many blocks ago.\n" + " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive' category of transactions.\n" + " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it. Available for 'send' and 'receive' category of transactions.\n" + " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n" + " \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n" + " \"time\": xxx, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT).\n" + " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT). Available for 'send' and 'receive' category of transactions.\n" + " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" + " may be unknown for unconfirmed transactions not in the mempool\n" + " \"abandoned\": xxx, (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the 'send' category of transactions.\n" + " \"comment\": \"...\", (string) If a comment is associated with the transaction.\n" + " \"label\" : \"label\" (string) A comment for the address/transaction, if any\n" + " \"to\": \"...\", (string) If a comment to is associated with the transaction.\n" + " ],\n" + " \"removed\": [\n" + " <structure is the same as \"transactions\" above, only present if include_removed=true>\n" + " Note: transactions that were readded in the active chain will appear as-is in this array, and may thus have a positive confirmation count.\n" + " ],\n" + " \"lastblock\": \"lastblockhash\" (string) The hash of the block (target_confirmations-1) from the best block on the main chain. This is typically used to feed back into listsinceblock the next time you call it. So you would generally use a target_confirmations of say 6, so you will be continually re-notified of transactions until they've reached 6 confirmations plus any new ones\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("listsinceblock", "") + + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6") + + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + const CBlockIndex* pindex = nullptr; // Block index of the specified block or the common ancestor, if the block provided was in a deactivated chain. + const CBlockIndex* paltindex = nullptr; // Block index of the specified block, even if it's in a deactivated chain. + int target_confirms = 1; + isminefilter filter = ISMINE_SPENDABLE; + + if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { + uint256 blockId; + + blockId.SetHex(request.params[0].get_str()); + BlockMap::iterator it = mapBlockIndex.find(blockId); + if (it == mapBlockIndex.end()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } + paltindex = pindex = it->second; + if (chainActive[pindex->nHeight] != pindex) { + // the block being asked for is a part of a deactivated chain; + // we don't want to depend on its perceived height in the block + // chain, we want to instead use the last common ancestor + pindex = chainActive.FindFork(pindex); + } + } + + if (!request.params[1].isNull()) { + target_confirms = request.params[1].get_int(); + + if (target_confirms < 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); + } + } + + if (!request.params[2].isNull() && request.params[2].get_bool()) { + filter = filter | ISMINE_WATCH_ONLY; + } + + bool include_removed = (request.params[3].isNull() || request.params[3].get_bool()); + + int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1; + + UniValue transactions(UniValue::VARR); + + for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + CWalletTx tx = pairWtx.second; + + if (depth == -1 || tx.GetDepthInMainChain() < depth) { + ListTransactions(pwallet, tx, "*", 0, true, transactions, filter); + } + } + + // when a reorg'd block is requested, we also list any relevant transactions + // in the blocks of the chain that was detached + UniValue removed(UniValue::VARR); + while (include_removed && paltindex && paltindex != pindex) { + CBlock block; + if (!ReadBlockFromDisk(block, paltindex, Params().GetConsensus())) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); + } + for (const CTransactionRef& tx : block.vtx) { + auto it = pwallet->mapWallet.find(tx->GetHash()); + if (it != pwallet->mapWallet.end()) { + // We want all transactions regardless of confirmation count to appear here, + // even negative confirmation ones, hence the big negative. + ListTransactions(pwallet, it->second, "*", -100000000, true, removed, filter); + } + } + paltindex = paltindex->pprev; + } + + CBlockIndex *pblockLast = chainActive[chainActive.Height() + 1 - target_confirms]; + uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256(); + + UniValue ret(UniValue::VOBJ); + ret.pushKV("transactions", transactions); + if (include_removed) ret.pushKV("removed", removed); + ret.pushKV("lastblock", lastblock.GetHex()); + + return ret; +} + +UniValue gettransaction(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + throw std::runtime_error( + "gettransaction \"txid\" ( include_watchonly )\n" + "\nGet detailed information about in-wallet transaction <txid>\n" + "\nArguments:\n" + "1. \"txid\" (string, required) The transaction id\n" + "2. \"include_watchonly\" (bool, optional, default=false) Whether to include watch-only addresses in balance calculation and details[]\n" + "\nResult:\n" + "{\n" + " \"amount\" : x.xxx, (numeric) The transaction amount in " + CURRENCY_UNIT + "\n" + " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" + " 'send' category of transactions.\n" + " \"confirmations\" : n, (numeric) The number of confirmations\n" + " \"blockhash\" : \"hash\", (string) The block hash\n" + " \"blockindex\" : xx, (numeric) The index of the transaction in the block that includes it\n" + " \"blocktime\" : ttt, (numeric) The time in seconds since epoch (1 Jan 1970 GMT)\n" + " \"txid\" : \"transactionid\", (string) The transaction id.\n" + " \"time\" : ttt, (numeric) The transaction time in seconds since epoch (1 Jan 1970 GMT)\n" + " \"timereceived\" : ttt, (numeric) The time received in seconds since epoch (1 Jan 1970 GMT)\n" + " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" + " may be unknown for unconfirmed transactions not in the mempool\n" + " \"details\" : [\n" + " {\n" + " \"account\" : \"accountname\", (string) DEPRECATED. The account name involved in the transaction, can be \"\" for the default account.\n" + " \"address\" : \"address\", (string) The bitcoin address involved in the transaction\n" + " \"category\" : \"send|receive\", (string) The category, either 'send' or 'receive'\n" + " \"amount\" : x.xxx, (numeric) The amount in " + CURRENCY_UNIT + "\n" + " \"label\" : \"label\", (string) A comment for the address/transaction, if any\n" + " \"vout\" : n, (numeric) the vout value\n" + " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" + " 'send' category of transactions.\n" + " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n" + " 'send' category of transactions.\n" + " }\n" + " ,...\n" + " ],\n" + " \"hex\" : \"data\" (string) Raw data for transaction\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true") + + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + uint256 hash; + hash.SetHex(request.params[0].get_str()); + + isminefilter filter = ISMINE_SPENDABLE; + if(!request.params[1].isNull()) + if(request.params[1].get_bool()) + filter = filter | ISMINE_WATCH_ONLY; + + UniValue entry(UniValue::VOBJ); + auto it = pwallet->mapWallet.find(hash); + if (it == pwallet->mapWallet.end()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); + } + const CWalletTx& wtx = it->second; + + CAmount nCredit = wtx.GetCredit(filter); + CAmount nDebit = wtx.GetDebit(filter); + CAmount nNet = nCredit - nDebit; + CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0); + + entry.pushKV("amount", ValueFromAmount(nNet - nFee)); + if (wtx.IsFromMe(filter)) + entry.pushKV("fee", ValueFromAmount(nFee)); + + WalletTxToJSON(wtx, entry); + + UniValue details(UniValue::VARR); + ListTransactions(pwallet, wtx, "*", 0, false, details, filter); + entry.pushKV("details", details); + + std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags()); + entry.pushKV("hex", strHex); + + return entry; +} + +UniValue abandontransaction(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) { + throw std::runtime_error( + "abandontransaction \"txid\"\n" + "\nMark in-wallet transaction <txid> as abandoned\n" + "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n" + "for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n" + "It only works on transactions which are not included in a block and are not currently in the mempool.\n" + "It has no effect on transactions which are already abandoned.\n" + "\nArguments:\n" + "1. \"txid\" (string, required) The transaction id\n" + "\nResult:\n" + "\nExamples:\n" + + HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + ); + } + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + uint256 hash; + hash.SetHex(request.params[0].get_str()); + + if (!pwallet->mapWallet.count(hash)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); + } + if (!pwallet->AbandonTransaction(hash)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); + } + + return NullUniValue; +} + + +UniValue backupwallet(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "backupwallet \"destination\"\n" + "\nSafely copies current wallet file to destination, which can be a directory or a path with filename.\n" + "\nArguments:\n" + "1. \"destination\" (string) The destination directory or file\n" + "\nExamples:\n" + + HelpExampleCli("backupwallet", "\"backup.dat\"") + + HelpExampleRpc("backupwallet", "\"backup.dat\"") + ); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + std::string strDest = request.params[0].get_str(); + if (!pwallet->BackupWallet(strDest)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!"); + } + + return NullUniValue; +} + + +UniValue keypoolrefill(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 1) + throw std::runtime_error( + "keypoolrefill ( newsize )\n" + "\nFills the keypool." + + HelpRequiringPassphrase(pwallet) + "\n" + "\nArguments\n" + "1. newsize (numeric, optional, default=100) The new keypool size\n" + "\nExamples:\n" + + HelpExampleCli("keypoolrefill", "") + + HelpExampleRpc("keypoolrefill", "") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool + unsigned int kpSize = 0; + if (!request.params[0].isNull()) { + if (request.params[0].get_int() < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected valid size."); + kpSize = (unsigned int)request.params[0].get_int(); + } + + EnsureWalletIsUnlocked(pwallet); + pwallet->TopUpKeyPool(kpSize); + + if (pwallet->GetKeyPoolSize() < kpSize) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool."); + } + + return NullUniValue; +} + + +static void LockWallet(CWallet* pWallet) +{ + LOCK(pWallet->cs_wallet); + pWallet->nRelockTime = 0; + pWallet->Lock(); +} + +UniValue walletpassphrase(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 2) { + throw std::runtime_error( + "walletpassphrase \"passphrase\" timeout\n" + "\nStores the wallet decryption key in memory for 'timeout' seconds.\n" + "This is needed prior to performing transactions related to private keys such as sending bitcoins\n" + "\nArguments:\n" + "1. \"passphrase\" (string, required) The wallet passphrase\n" + "2. timeout (numeric, required) The time to keep the decryption key in seconds. Limited to at most 1073741824 (2^30) seconds.\n" + " Any value greater than 1073741824 seconds will be set to 1073741824 seconds.\n" + "\nNote:\n" + "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n" + "time that overrides the old one.\n" + "\nExamples:\n" + "\nUnlock the wallet for 60 seconds\n" + + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") + + "\nLock the wallet again (before 60 seconds)\n" + + HelpExampleCli("walletlock", "") + + "\nAs json rpc call\n" + + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60") + ); + } + + LOCK2(cs_main, pwallet->cs_wallet); + + if (request.fHelp) + return true; + if (!pwallet->IsCrypted()) { + throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called."); + } + + // Note that the walletpassphrase is stored in request.params[0] which is not mlock()ed + SecureString strWalletPass; + strWalletPass.reserve(100); + // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) + // Alternately, find a way to make request.params[0] mlock()'d to begin with. + strWalletPass = request.params[0].get_str().c_str(); + + // Get the timeout + int64_t nSleepTime = request.params[1].get_int64(); + // Timeout cannot be negative, otherwise it will relock immediately + if (nSleepTime < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative."); + } + // Clamp timeout to 2^30 seconds + if (nSleepTime > (int64_t)1 << 30) { + nSleepTime = (int64_t)1 << 30; + } + + if (strWalletPass.length() > 0) + { + if (!pwallet->Unlock(strWalletPass)) { + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + } + } + else + throw std::runtime_error( + "walletpassphrase <passphrase> <timeout>\n" + "Stores the wallet decryption key in memory for <timeout> seconds."); + + pwallet->TopUpKeyPool(); + + pwallet->nRelockTime = GetTime() + nSleepTime; + RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), boost::bind(LockWallet, pwallet), nSleepTime); + + return NullUniValue; +} + + +UniValue walletpassphrasechange(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 2) { + throw std::runtime_error( + "walletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\n" + "\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n" + "\nArguments:\n" + "1. \"oldpassphrase\" (string) The current passphrase\n" + "2. \"newpassphrase\" (string) The new passphrase\n" + "\nExamples:\n" + + HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") + + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"") + ); + } + + LOCK2(cs_main, pwallet->cs_wallet); + + if (request.fHelp) + return true; + if (!pwallet->IsCrypted()) { + throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); + } + + // TODO: get rid of these .c_str() calls by implementing SecureString::operator=(std::string) + // Alternately, find a way to make request.params[0] mlock()'d to begin with. + SecureString strOldWalletPass; + strOldWalletPass.reserve(100); + strOldWalletPass = request.params[0].get_str().c_str(); + + SecureString strNewWalletPass; + strNewWalletPass.reserve(100); + strNewWalletPass = request.params[1].get_str().c_str(); + + if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1) + throw std::runtime_error( + "walletpassphrasechange <oldpassphrase> <newpassphrase>\n" + "Changes the wallet passphrase from <oldpassphrase> to <newpassphrase>."); + + if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) { + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + } + + return NullUniValue; +} + + +UniValue walletlock(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 0) { + throw std::runtime_error( + "walletlock\n" + "\nRemoves the wallet encryption key from memory, locking the wallet.\n" + "After calling this method, you will need to call walletpassphrase again\n" + "before being able to call any methods which require the wallet to be unlocked.\n" + "\nExamples:\n" + "\nSet the passphrase for 2 minutes to perform a transaction\n" + + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") + + "\nPerform a send (requires passphrase set)\n" + + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 1.0") + + "\nClear the passphrase since we are done before 2 minutes is up\n" + + HelpExampleCli("walletlock", "") + + "\nAs json rpc call\n" + + HelpExampleRpc("walletlock", "") + ); + } + + LOCK2(cs_main, pwallet->cs_wallet); + + if (request.fHelp) + return true; + if (!pwallet->IsCrypted()) { + throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called."); + } + + pwallet->Lock(); + pwallet->nRelockTime = 0; + + return NullUniValue; +} + + +UniValue encryptwallet(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) { + throw std::runtime_error( + "encryptwallet \"passphrase\"\n" + "\nEncrypts the wallet with 'passphrase'. This is for first time encryption.\n" + "After this, any calls that interact with private keys such as sending or signing \n" + "will require the passphrase to be set prior the making these calls.\n" + "Use the walletpassphrase call for this, and then walletlock call.\n" + "If the wallet is already encrypted, use the walletpassphrasechange call.\n" + "Note that this will shutdown the server.\n" + "\nArguments:\n" + "1. \"passphrase\" (string) The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long.\n" + "\nExamples:\n" + "\nEncrypt your wallet\n" + + HelpExampleCli("encryptwallet", "\"my pass phrase\"") + + "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n" + + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") + + "\nNow we can do something like sign\n" + + HelpExampleCli("signmessage", "\"address\" \"test message\"") + + "\nNow lock the wallet again by removing the passphrase\n" + + HelpExampleCli("walletlock", "") + + "\nAs a json rpc call\n" + + HelpExampleRpc("encryptwallet", "\"my pass phrase\"") + ); + } + + LOCK2(cs_main, pwallet->cs_wallet); + + if (request.fHelp) + return true; + if (pwallet->IsCrypted()) { + throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called."); + } + + // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) + // Alternately, find a way to make request.params[0] mlock()'d to begin with. + SecureString strWalletPass; + strWalletPass.reserve(100); + strWalletPass = request.params[0].get_str().c_str(); + + if (strWalletPass.length() < 1) + throw std::runtime_error( + "encryptwallet <passphrase>\n" + "Encrypts the wallet with <passphrase>."); + + if (!pwallet->EncryptWallet(strWalletPass)) { + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet."); + } + + // BDB seems to have a bad habit of writing old data into + // slack space in .dat files; that is bad if the old data is + // unencrypted private keys. So: + StartShutdown(); + return "wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet. The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup."; +} + +UniValue lockunspent(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + throw std::runtime_error( + "lockunspent unlock ([{\"txid\":\"txid\",\"vout\":n},...])\n" + "\nUpdates list of temporarily unspendable outputs.\n" + "Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n" + "If no transaction outputs are specified when unlocking then all current locked transaction outputs are unlocked.\n" + "A locked transaction output will not be chosen by automatic coin selection, when spending bitcoins.\n" + "Locks are stored in memory only. Nodes start with zero locked outputs, and the locked output list\n" + "is always cleared (by virtue of process exit) when a node stops or fails.\n" + "Also see the listunspent call\n" + "\nArguments:\n" + "1. unlock (boolean, required) Whether to unlock (true) or lock (false) the specified transactions\n" + "2. \"transactions\" (string, optional) A json array of objects. Each object the txid (string) vout (numeric)\n" + " [ (json array of json objects)\n" + " {\n" + " \"txid\":\"id\", (string) The transaction id\n" + " \"vout\": n (numeric) The output number\n" + " }\n" + " ,...\n" + " ]\n" + + "\nResult:\n" + "true|false (boolean) Whether the command was successful or not\n" + + "\nExamples:\n" + "\nList the unspent transactions\n" + + HelpExampleCli("listunspent", "") + + "\nLock an unspent transaction\n" + + HelpExampleCli("lockunspent", "false \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + + "\nList the locked transactions\n" + + HelpExampleCli("listlockunspent", "") + + "\nUnlock the transaction again\n" + + HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + + "\nAs a json rpc call\n" + + HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + ); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + RPCTypeCheckArgument(request.params[0], UniValue::VBOOL); + + bool fUnlock = request.params[0].get_bool(); + + if (request.params[1].isNull()) { + if (fUnlock) + pwallet->UnlockAllCoins(); + return true; + } + + RPCTypeCheckArgument(request.params[1], UniValue::VARR); + + const UniValue& output_params = request.params[1]; + + // Create and validate the COutPoints first. + + std::vector<COutPoint> outputs; + outputs.reserve(output_params.size()); + + for (unsigned int idx = 0; idx < output_params.size(); idx++) { + const UniValue& o = output_params[idx].get_obj(); + + RPCTypeCheckObj(o, + { + {"txid", UniValueType(UniValue::VSTR)}, + {"vout", UniValueType(UniValue::VNUM)}, + }); + + const std::string& txid = find_value(o, "txid").get_str(); + if (!IsHex(txid)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid"); + } + + const int nOutput = find_value(o, "vout").get_int(); + if (nOutput < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive"); + } + + const COutPoint outpt(uint256S(txid), nOutput); + + const auto it = pwallet->mapWallet.find(outpt.hash); + if (it == pwallet->mapWallet.end()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, unknown transaction"); + } + + const CWalletTx& trans = it->second; + + if (outpt.n >= trans.tx->vout.size()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds"); + } + + if (pwallet->IsSpent(outpt.hash, outpt.n)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output"); + } + + const bool is_locked = pwallet->IsLockedCoin(outpt.hash, outpt.n); + + if (fUnlock && !is_locked) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output"); + } + + if (!fUnlock && is_locked) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output already locked"); + } + + outputs.push_back(outpt); + } + + // Atomically set (un)locked status for the outputs. + for (const COutPoint& outpt : outputs) { + if (fUnlock) pwallet->UnlockCoin(outpt); + else pwallet->LockCoin(outpt); + } + + return true; +} + +UniValue listlockunspent(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 0) + throw std::runtime_error( + "listlockunspent\n" + "\nReturns list of temporarily unspendable outputs.\n" + "See the lockunspent call to lock and unlock transactions for spending.\n" + "\nResult:\n" + "[\n" + " {\n" + " \"txid\" : \"transactionid\", (string) The transaction id locked\n" + " \"vout\" : n (numeric) The vout value\n" + " }\n" + " ,...\n" + "]\n" + "\nExamples:\n" + "\nList the unspent transactions\n" + + HelpExampleCli("listunspent", "") + + "\nLock an unspent transaction\n" + + HelpExampleCli("lockunspent", "false \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + + "\nList the locked transactions\n" + + HelpExampleCli("listlockunspent", "") + + "\nUnlock the transaction again\n" + + HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + + "\nAs a json rpc call\n" + + HelpExampleRpc("listlockunspent", "") + ); + + ObserveSafeMode(); + LOCK2(cs_main, pwallet->cs_wallet); + + std::vector<COutPoint> vOutpts; + pwallet->ListLockedCoins(vOutpts); + + UniValue ret(UniValue::VARR); + + for (COutPoint &outpt : vOutpts) { + UniValue o(UniValue::VOBJ); + + o.pushKV("txid", outpt.hash.GetHex()); + o.pushKV("vout", (int)outpt.n); + ret.push_back(o); + } + + return ret; +} + +UniValue settxfee(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 1) + throw std::runtime_error( + "settxfee amount\n" + "\nSet the transaction fee per kB. Overwrites the paytxfee parameter.\n" + "\nArguments:\n" + "1. amount (numeric or string, required) The transaction fee in " + CURRENCY_UNIT + "/kB\n" + "\nResult\n" + "true|false (boolean) Returns true if successful\n" + "\nExamples:\n" + + HelpExampleCli("settxfee", "0.00001") + + HelpExampleRpc("settxfee", "0.00001") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + // Amount + CAmount nAmount = AmountFromValue(request.params[0]); + + payTxFee = CFeeRate(nAmount, 1000); + return true; +} + +UniValue getwalletinfo(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 0) + throw std::runtime_error( + "getwalletinfo\n" + "Returns an object containing various wallet state info.\n" + "\nResult:\n" + "{\n" + " \"walletname\": xxxxx, (string) the wallet name\n" + " \"walletversion\": xxxxx, (numeric) the wallet version\n" + " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n" + " \"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 Unix epoch) of the oldest pre-generated key in the key pool\n" + " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n" + " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\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" + " \"hdmasterkeyid\": \"<hash160>\" (string, optional) the Hash160 of the HD master pubkey (only present when HD is enabled)\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("getwalletinfo", "") + + HelpExampleRpc("getwalletinfo", "") + ); + + ObserveSafeMode(); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + + UniValue obj(UniValue::VOBJ); + + size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); + obj.pushKV("walletname", pwallet->GetName()); + obj.pushKV("walletversion", pwallet->GetVersion()); + obj.pushKV("balance", ValueFromAmount(pwallet->GetBalance())); + obj.pushKV("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance())); + obj.pushKV("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance())); + obj.pushKV("txcount", (int)pwallet->mapWallet.size()); + obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime()); + obj.pushKV("keypoolsize", (int64_t)kpExternalSize); + CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID; + if (!masterKeyID.IsNull() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { + obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize)); + } + if (pwallet->IsCrypted()) { + obj.pushKV("unlocked_until", pwallet->nRelockTime); + } + obj.pushKV("paytxfee", ValueFromAmount(payTxFee.GetFeePerK())); + if (!masterKeyID.IsNull()) + obj.pushKV("hdmasterkeyid", masterKeyID.GetHex()); + return obj; +} + +UniValue listwallets(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 0) + throw std::runtime_error( + "listwallets\n" + "Returns a list of currently loaded wallets.\n" + "For full information on the wallet, use \"getwalletinfo\"\n" + "\nResult:\n" + "[ (json array of strings)\n" + " \"walletname\" (string) the wallet name\n" + " ...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("listwallets", "") + + HelpExampleRpc("listwallets", "") + ); + + UniValue obj(UniValue::VARR); + + for (CWalletRef pwallet : vpwallets) { + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + LOCK(pwallet->cs_wallet); + + obj.push_back(pwallet->GetName()); + } + + return obj; +} + +UniValue resendwallettransactions(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 0) + throw std::runtime_error( + "resendwallettransactions\n" + "Immediately re-broadcast unconfirmed wallet transactions to all peers.\n" + "Intended only for testing; the wallet code periodically re-broadcasts\n" + "automatically.\n" + "Returns an RPC error if -walletbroadcast is set to false.\n" + "Returns array of transaction ids that were re-broadcast.\n" + ); + + if (!g_connman) + throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + + LOCK2(cs_main, pwallet->cs_wallet); + + if (!pwallet->GetBroadcastTransactions()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet transaction broadcasting is disabled with -walletbroadcast"); + } + + std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(GetTime(), g_connman.get()); + UniValue result(UniValue::VARR); + for (const uint256& txid : txids) + { + result.push_back(txid.ToString()); + } + return result; +} + +UniValue listunspent(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 5) + throw std::runtime_error( + "listunspent ( minconf maxconf [\"addresses\",...] [include_unsafe] [query_options])\n" + "\nReturns array of unspent transaction outputs\n" + "with between minconf and maxconf (inclusive) confirmations.\n" + "Optionally filter to only include txouts paid to specified addresses.\n" + "\nArguments:\n" + "1. minconf (numeric, optional, default=1) The minimum confirmations to filter\n" + "2. maxconf (numeric, optional, default=9999999) The maximum confirmations to filter\n" + "3. \"addresses\" (string) A json array of bitcoin addresses to filter\n" + " [\n" + " \"address\" (string) bitcoin address\n" + " ,...\n" + " ]\n" + "4. include_unsafe (bool, optional, default=true) Include outputs that are not safe to spend\n" + " See description of \"safe\" attribute below.\n" + "5. query_options (json, optional) JSON with query options\n" + " {\n" + " \"minimumAmount\" (numeric or string, default=0) Minimum value of each UTXO in " + CURRENCY_UNIT + "\n" + " \"maximumAmount\" (numeric or string, default=unlimited) Maximum value of each UTXO in " + CURRENCY_UNIT + "\n" + " \"maximumCount\" (numeric or string, default=unlimited) Maximum number of UTXOs\n" + " \"minimumSumAmount\" (numeric or string, default=unlimited) Minimum sum value of all UTXOs in " + CURRENCY_UNIT + "\n" + " }\n" + "\nResult\n" + "[ (array of json object)\n" + " {\n" + " \"txid\" : \"txid\", (string) the transaction id \n" + " \"vout\" : n, (numeric) the vout value\n" + " \"address\" : \"address\", (string) the bitcoin address\n" + " \"account\" : \"account\", (string) DEPRECATED. The associated account, or \"\" for the default account\n" + " \"scriptPubKey\" : \"key\", (string) the script key\n" + " \"amount\" : x.xxx, (numeric) the transaction output amount in " + CURRENCY_UNIT + "\n" + " \"confirmations\" : n, (numeric) The number of confirmations\n" + " \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\n" + " \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n" + " \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n" + " \"safe\" : xxx (bool) Whether this output is considered safe to spend. Unconfirmed transactions\n" + " from outside keys and unconfirmed replacement transactions are considered unsafe\n" + " and are not eligible for spending by fundrawtransaction and sendtoaddress.\n" + " }\n" + " ,...\n" + "]\n" + + "\nExamples\n" + + HelpExampleCli("listunspent", "") + + HelpExampleCli("listunspent", "6 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + + HelpExampleRpc("listunspent", "6, 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + + HelpExampleCli("listunspent", "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'") + + HelpExampleRpc("listunspent", "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ") + ); + + ObserveSafeMode(); + + int nMinDepth = 1; + if (!request.params[0].isNull()) { + RPCTypeCheckArgument(request.params[0], UniValue::VNUM); + nMinDepth = request.params[0].get_int(); + } + + int nMaxDepth = 9999999; + if (!request.params[1].isNull()) { + RPCTypeCheckArgument(request.params[1], UniValue::VNUM); + nMaxDepth = request.params[1].get_int(); + } + + std::set<CTxDestination> destinations; + if (!request.params[2].isNull()) { + RPCTypeCheckArgument(request.params[2], UniValue::VARR); + UniValue inputs = request.params[2].get_array(); + for (unsigned int idx = 0; idx < inputs.size(); idx++) { + const UniValue& input = inputs[idx]; + CTxDestination dest = DecodeDestination(input.get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + input.get_str()); + } + if (!destinations.insert(dest).second) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + input.get_str()); + } + } + } + + bool include_unsafe = true; + if (!request.params[3].isNull()) { + RPCTypeCheckArgument(request.params[3], UniValue::VBOOL); + include_unsafe = request.params[3].get_bool(); + } + + CAmount nMinimumAmount = 0; + CAmount nMaximumAmount = MAX_MONEY; + CAmount nMinimumSumAmount = MAX_MONEY; + uint64_t nMaximumCount = 0; + + if (!request.params[4].isNull()) { + const UniValue& options = request.params[4].get_obj(); + + if (options.exists("minimumAmount")) + nMinimumAmount = AmountFromValue(options["minimumAmount"]); + + if (options.exists("maximumAmount")) + nMaximumAmount = AmountFromValue(options["maximumAmount"]); + + if (options.exists("minimumSumAmount")) + nMinimumSumAmount = AmountFromValue(options["minimumSumAmount"]); + + if (options.exists("maximumCount")) + nMaximumCount = options["maximumCount"].get_int64(); + } + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + UniValue results(UniValue::VARR); + std::vector<COutput> vecOutputs; + LOCK2(cs_main, pwallet->cs_wallet); + + pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); + for (const COutput& out : vecOutputs) { + CTxDestination address; + const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; + bool fValidAddress = ExtractDestination(scriptPubKey, address); + + if (destinations.size() && (!fValidAddress || !destinations.count(address))) + continue; + + UniValue entry(UniValue::VOBJ); + entry.pushKV("txid", out.tx->GetHash().GetHex()); + entry.pushKV("vout", out.i); + + if (fValidAddress) { + entry.pushKV("address", EncodeDestination(address)); + + if (pwallet->mapAddressBook.count(address)) { + entry.pushKV("account", pwallet->mapAddressBook[address].name); + } + + if (scriptPubKey.IsPayToScriptHash()) { + const CScriptID& hash = boost::get<CScriptID>(address); + CScript redeemScript; + if (pwallet->GetCScript(hash, redeemScript)) { + entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); + } + } + } + + entry.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); + entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue)); + entry.pushKV("confirmations", out.nDepth); + entry.pushKV("spendable", out.fSpendable); + entry.pushKV("solvable", out.fSolvable); + entry.pushKV("safe", out.fSafe); + results.push_back(entry); + } + + return results; +} + +UniValue fundrawtransaction(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) + throw std::runtime_error( + "fundrawtransaction \"hexstring\" ( options iswitness )\n" + "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" + "This will not modify existing inputs, and will add at most one change output to the outputs.\n" + "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" + "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" + "The inputs added will not be signed, use signrawtransaction for that.\n" + "Note that all existing inputs must have their previous output transaction be in the wallet.\n" + "Note that all inputs selected must be of standard form and P2SH scripts must be\n" + "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" + "You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n" + "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n" + "\nArguments:\n" + "1. \"hexstring\" (string, required) The hex string of the raw transaction\n" + "2. options (object, optional)\n" + " {\n" + " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n" + " \"changePosition\" (numeric, optional, default random) The index of the change output\n" + " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" + " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" + " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" + " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n" + " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" + " The fee will be equally deducted from the amount of each specified output.\n" + " The outputs are specified by their zero-based index, before any change output is added.\n" + " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" + " If no outputs are specified here, the sender pays the fee.\n" + " [vout_index,...]\n" + " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees\n" + " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n" + " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" + " }\n" + " for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n" + "3. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction \n" + " If iswitness is not present, heuristic tests will be used in decoding\n" + + "\nResult:\n" + "{\n" + " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" + " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" + " \"changepos\": n (numeric) The position of the added change output, or -1\n" + "}\n" + "\nExamples:\n" + "\nCreate a transaction with no inputs\n" + + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + + "\nAdd sufficient unsigned inputs to meet the output value\n" + + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + + "\nSign the transaction\n" + + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + + "\nSend the transaction\n" + + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") + ); + + ObserveSafeMode(); + RPCTypeCheck(request.params, {UniValue::VSTR}); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + CCoinControl coinControl; + int changePosition = -1; + bool lockUnspents = false; + UniValue subtractFeeFromOutputs; + std::set<int> setSubtractFeeFromOutputs; + + if (!request.params[1].isNull()) { + if (request.params[1].type() == UniValue::VBOOL) { + // backward compatibility bool only fallback + coinControl.fAllowWatchOnly = request.params[1].get_bool(); + } + else { + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ, UniValue::VBOOL}); + + UniValue options = request.params[1]; + + RPCTypeCheckObj(options, + { + {"changeAddress", UniValueType(UniValue::VSTR)}, + {"changePosition", UniValueType(UniValue::VNUM)}, + {"change_type", UniValueType(UniValue::VSTR)}, + {"includeWatching", UniValueType(UniValue::VBOOL)}, + {"lockUnspents", UniValueType(UniValue::VBOOL)}, + {"reserveChangeKey", UniValueType(UniValue::VBOOL)}, // DEPRECATED (and ignored), should be removed in 0.16 or so. + {"feeRate", UniValueType()}, // will be checked below + {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)}, + {"replaceable", UniValueType(UniValue::VBOOL)}, + {"conf_target", UniValueType(UniValue::VNUM)}, + {"estimate_mode", UniValueType(UniValue::VSTR)}, + }, + true, true); + + if (options.exists("changeAddress")) { + CTxDestination dest = DecodeDestination(options["changeAddress"].get_str()); + + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "changeAddress must be a valid bitcoin address"); + } + + coinControl.destChange = dest; + } + + if (options.exists("changePosition")) + changePosition = options["changePosition"].get_int(); + + if (options.exists("change_type")) { + if (options.exists("changeAddress")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both changeAddress and address_type options"); + } + coinControl.change_type = ParseOutputType(options["change_type"].get_str(), coinControl.change_type); + if (coinControl.change_type == OUTPUT_TYPE_NONE) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown change type '%s'", options["change_type"].get_str())); + } + } + + if (options.exists("includeWatching")) + coinControl.fAllowWatchOnly = options["includeWatching"].get_bool(); + + if (options.exists("lockUnspents")) + lockUnspents = options["lockUnspents"].get_bool(); + + if (options.exists("feeRate")) + { + coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"])); + coinControl.fOverrideFeeRate = true; + } + + if (options.exists("subtractFeeFromOutputs")) + subtractFeeFromOutputs = options["subtractFeeFromOutputs"].get_array(); + + if (options.exists("replaceable")) { + coinControl.signalRbf = options["replaceable"].get_bool(); + } + if (options.exists("conf_target")) { + if (options.exists("feeRate")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate"); + } + coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"]); + } + if (options.exists("estimate_mode")) { + if (options.exists("feeRate")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate"); + } + if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } + } + } + } + + // parse hex string from parameter + CMutableTransaction tx; + bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool(); + bool try_no_witness = request.params[2].isNull() ? true : !request.params[2].get_bool(); + if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + if (tx.vout.size() == 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output"); + + if (changePosition != -1 && (changePosition < 0 || (unsigned int)changePosition > tx.vout.size())) + throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds"); + + for (unsigned int idx = 0; idx < subtractFeeFromOutputs.size(); idx++) { + int pos = subtractFeeFromOutputs[idx].get_int(); + if (setSubtractFeeFromOutputs.count(pos)) + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, duplicated position: %d", pos)); + if (pos < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, negative position: %d", pos)); + if (pos >= int(tx.vout.size())) + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, position too large: %d", pos)); + setSubtractFeeFromOutputs.insert(pos); + } + + CAmount nFeeOut; + std::string strFailReason; + + if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { + throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); + } + + UniValue result(UniValue::VOBJ); + result.pushKV("hex", EncodeHexTx(tx)); + result.pushKV("changepos", changePosition); + result.pushKV("fee", ValueFromAmount(nFeeOut)); + + return result; +} + +UniValue bumpfee(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) + return NullUniValue; + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { + throw std::runtime_error( + "bumpfee \"txid\" ( options ) \n" + "\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n" + "An opt-in RBF transaction with the given txid must be in the wallet.\n" + "The command will pay the additional fee by decreasing (or perhaps removing) its change output.\n" + "If the change output is not big enough to cover the increased fee, the command will currently fail\n" + "instead of adding new inputs to compensate. (A future implementation could improve this.)\n" + "The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n" + "By default, the new fee will be calculated automatically using estimatesmartfee.\n" + "The user can specify a confirmation target for estimatesmartfee.\n" + "Alternatively, the user can specify totalFee, or use RPC settxfee to set a higher fee rate.\n" + "At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n" + "returned by getnetworkinfo) to enter the node's mempool.\n" + "\nArguments:\n" + "1. txid (string, required) The txid to be bumped\n" + "2. options (object, optional)\n" + " {\n" + " \"confTarget\" (numeric, optional) Confirmation target (in blocks)\n" + " \"totalFee\" (numeric, optional) Total fee (NOT feerate) to pay, in satoshis.\n" + " In rare cases, the actual fee paid might be slightly higher than the specified\n" + " totalFee if the tx change output has to be removed because it is too close to\n" + " the dust threshold.\n" + " \"replaceable\" (boolean, optional, default true) Whether the new transaction should still be\n" + " marked bip-125 replaceable. If true, the sequence numbers in the transaction will\n" + " be left unchanged from the original. If false, any input sequence numbers in the\n" + " original transaction that were less than 0xfffffffe will be increased to 0xfffffffe\n" + " so the new transaction will not be explicitly bip-125 replaceable (though it may\n" + " still be replaceable in practice, for example if it has unconfirmed ancestors which\n" + " are replaceable).\n" + " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" + " }\n" + "\nResult:\n" + "{\n" + " \"txid\": \"value\", (string) The id of the new transaction\n" + " \"origfee\": n, (numeric) Fee of the replaced transaction\n" + " \"fee\": n, (numeric) Fee of the new transaction\n" + " \"errors\": [ str... ] (json array of strings) Errors encountered during processing (may be empty)\n" + "}\n" + "\nExamples:\n" + "\nBump the fee, get the new transaction\'s txid\n" + + HelpExampleCli("bumpfee", "<txid>")); + } + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); + uint256 hash; + hash.SetHex(request.params[0].get_str()); + + // optional parameters + CAmount totalFee = 0; + CCoinControl coin_control; + coin_control.signalRbf = true; + if (!request.params[1].isNull()) { + UniValue options = request.params[1]; + RPCTypeCheckObj(options, + { + {"confTarget", UniValueType(UniValue::VNUM)}, + {"totalFee", UniValueType(UniValue::VNUM)}, + {"replaceable", UniValueType(UniValue::VBOOL)}, + {"estimate_mode", UniValueType(UniValue::VSTR)}, + }, + true, true); + + if (options.exists("confTarget") && options.exists("totalFee")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget and totalFee options should not both be set. Please provide either a confirmation target for fee estimation or an explicit total fee for the transaction."); + } else if (options.exists("confTarget")) { // TODO: alias this to conf_target + coin_control.m_confirm_target = ParseConfirmTarget(options["confTarget"]); + } else if (options.exists("totalFee")) { + totalFee = options["totalFee"].get_int64(); + if (totalFee <= 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid totalFee %s (must be greater than 0)", FormatMoney(totalFee))); + } + } + + if (options.exists("replaceable")) { + coin_control.signalRbf = options["replaceable"].get_bool(); + } + if (options.exists("estimate_mode")) { + if (!FeeModeFromString(options["estimate_mode"].get_str(), coin_control.m_fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } + } + } + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK2(cs_main, pwallet->cs_wallet); + EnsureWalletIsUnlocked(pwallet); + + + std::vector<std::string> errors; + CAmount old_fee; + CAmount new_fee; + CMutableTransaction mtx; + feebumper::Result res = feebumper::CreateTransaction(pwallet, hash, coin_control, totalFee, errors, old_fee, new_fee, mtx); + if (res != feebumper::Result::OK) { + switch(res) { + case feebumper::Result::INVALID_ADDRESS_OR_KEY: + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errors[0]); + break; + case feebumper::Result::INVALID_REQUEST: + throw JSONRPCError(RPC_INVALID_REQUEST, errors[0]); + break; + case feebumper::Result::INVALID_PARAMETER: + throw JSONRPCError(RPC_INVALID_PARAMETER, errors[0]); + break; + case feebumper::Result::WALLET_ERROR: + throw JSONRPCError(RPC_WALLET_ERROR, errors[0]); + break; + default: + throw JSONRPCError(RPC_MISC_ERROR, errors[0]); + break; + } + } + + // sign bumped transaction + if (!feebumper::SignTransaction(pwallet, mtx)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction."); + } + // commit the bumped transaction + uint256 txid; + if (feebumper::CommitTransaction(pwallet, hash, std::move(mtx), errors, txid) != feebumper::Result::OK) { + throw JSONRPCError(RPC_WALLET_ERROR, errors[0]); + } + UniValue result(UniValue::VOBJ); + result.pushKV("txid", txid.GetHex()); + result.pushKV("origfee", ValueFromAmount(old_fee)); + result.pushKV("fee", ValueFromAmount(new_fee)); + UniValue result_errors(UniValue::VARR); + for (const std::string& error : errors) { + result_errors.push_back(error); + } + result.pushKV("errors", result_errors); + + return result; +} + +UniValue generate(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { + throw std::runtime_error( + "generate nblocks ( maxtries )\n" + "\nMine up to nblocks blocks immediately (before the RPC call returns) to an address in the wallet.\n" + "\nArguments:\n" + "1. nblocks (numeric, required) How many blocks are generated immediately.\n" + "2. maxtries (numeric, optional) How many iterations to try (default = 1000000).\n" + "\nResult:\n" + "[ blockhashes ] (array) hashes of blocks generated\n" + "\nExamples:\n" + "\nGenerate 11 blocks\n" + + HelpExampleCli("generate", "11") + ); + } + + int num_generate = request.params[0].get_int(); + uint64_t max_tries = 1000000; + if (!request.params[1].isNull()) { + max_tries = request.params[1].get_int(); + } + + std::shared_ptr<CReserveScript> coinbase_script; + pwallet->GetScriptForMining(coinbase_script); + + // If the keypool is exhausted, no script is returned at all. Catch this. + if (!coinbase_script) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + } + + //throw an error if no script was provided + if (coinbase_script->reserveScript.empty()) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "No coinbase script available"); + } + + return generateBlocks(coinbase_script, num_generate, max_tries, true); +} + +UniValue rescanblockchain(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 2) { + throw std::runtime_error( + "rescanblockchain (\"start_height\") (\"stop_height\")\n" + "\nRescan the local blockchain for wallet related transactions.\n" + "\nArguments:\n" + "1. \"start_height\" (numeric, optional) block height where the rescan should start\n" + "2. \"stop_height\" (numeric, optional) the last block height that should be scanned\n" + "\nResult:\n" + "{\n" + " \"start_height\" (numeric) The block height where the rescan has started. If omitted, rescan started from the genesis block.\n" + " \"stop_height\" (numeric) The height of the last rescanned block. If omitted, rescan stopped at the chain tip.\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("rescanblockchain", "100000 120000") + + HelpExampleRpc("rescanblockchain", "100000, 120000") + ); + } + + WalletRescanReserver reserver(pwallet); + if (!reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + + CBlockIndex *pindexStart = nullptr; + CBlockIndex *pindexStop = nullptr; + CBlockIndex *pChainTip = nullptr; + { + LOCK(cs_main); + pindexStart = chainActive.Genesis(); + pChainTip = chainActive.Tip(); + + if (!request.params[0].isNull()) { + pindexStart = chainActive[request.params[0].get_int()]; + if (!pindexStart) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); + } + } + + if (!request.params[1].isNull()) { + pindexStop = chainActive[request.params[1].get_int()]; + if (!pindexStop) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); + } + else if (pindexStop->nHeight < pindexStart->nHeight) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height"); + } + } + } + + // We can't rescan beyond non-pruned blocks, stop and throw an error + if (fPruneMode) { + LOCK(cs_main); + CBlockIndex *block = pindexStop ? pindexStop : pChainTip; + while (block && block->nHeight >= pindexStart->nHeight) { + if (!(block->nStatus & BLOCK_HAVE_DATA)) { + throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height."); + } + block = block->pprev; + } + } + + CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, true); + if (!stopBlock) { + if (pwallet->IsAbortingRescan()) { + throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted."); + } + // if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex + stopBlock = pindexStop ? pindexStop : pChainTip; + } + else { + throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files."); + } + UniValue response(UniValue::VOBJ); + response.pushKV("start_height", pindexStart->nHeight); + response.pushKV("stop_height", stopBlock->nHeight); + return response; +} + +extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp +extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp +extern UniValue importprivkey(const JSONRPCRequest& request); +extern UniValue importaddress(const JSONRPCRequest& request); +extern UniValue importpubkey(const JSONRPCRequest& request); +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); +extern UniValue rescanblockchain(const JSONRPCRequest& request); + +static const CRPCCommand commands[] = +{ // category name actor (function) argNames + // --------------------- ------------------------ ----------------------- ---------- + { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} }, + { "hidden", "resendwallettransactions", &resendwallettransactions, {} }, + { "wallet", "abandontransaction", &abandontransaction, {"txid"} }, + { "wallet", "abortrescan", &abortrescan, {} }, + { "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","account","address_type"} }, + { "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} }, + { "wallet", "backupwallet", &backupwallet, {"destination"} }, + { "wallet", "bumpfee", &bumpfee, {"txid", "options"} }, + { "wallet", "dumpprivkey", &dumpprivkey, {"address"} }, + { "wallet", "dumpwallet", &dumpwallet, {"filename"} }, + { "wallet", "encryptwallet", &encryptwallet, {"passphrase"} }, + { "wallet", "getaccountaddress", &getaccountaddress, {"account"} }, + { "wallet", "getaccount", &getaccount, {"address"} }, + { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} }, + { "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} }, + { "wallet", "getnewaddress", &getnewaddress, {"account","address_type"} }, + { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} }, + { "wallet", "getreceivedbyaccount", &getreceivedbyaccount, {"account","minconf"} }, + { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} }, + { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} }, + { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} }, + { "wallet", "getwalletinfo", &getwalletinfo, {} }, + { "wallet", "importmulti", &importmulti, {"requests","options"} }, + { "wallet", "importprivkey", &importprivkey, {"privkey","label","rescan"} }, + { "wallet", "importwallet", &importwallet, {"filename"} }, + { "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} }, + { "wallet", "importprunedfunds", &importprunedfunds, {"rawtransaction","txoutproof"} }, + { "wallet", "importpubkey", &importpubkey, {"pubkey","label","rescan"} }, + { "wallet", "keypoolrefill", &keypoolrefill, {"newsize"} }, + { "wallet", "listaccounts", &listaccounts, {"minconf","include_watchonly"} }, + { "wallet", "listaddressgroupings", &listaddressgroupings, {} }, + { "wallet", "listlockunspent", &listlockunspent, {} }, + { "wallet", "listreceivedbyaccount", &listreceivedbyaccount, {"minconf","include_empty","include_watchonly"} }, + { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly"} }, + { "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} }, + { "wallet", "listtransactions", &listtransactions, {"account","count","skip","include_watchonly"} }, + { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, + { "wallet", "listwallets", &listwallets, {} }, + { "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} }, + { "wallet", "move", &movecmd, {"fromaccount","toaccount","amount","minconf","comment"} }, + { "wallet", "sendfrom", &sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, + { "wallet", "sendmany", &sendmany, {"fromaccount","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} }, + { "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} }, + { "wallet", "setaccount", &setaccount, {"address","account"} }, + { "wallet", "settxfee", &settxfee, {"amount"} }, + { "wallet", "signmessage", &signmessage, {"address","message"} }, + { "wallet", "walletlock", &walletlock, {} }, + { "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} }, + { "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} }, + { "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} }, + { "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} }, + + { "generating", "generate", &generate, {"nblocks","maxtries"} }, +}; + +void RegisterWalletRPCCommands(CRPCTable &t) +{ + for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) + t.appendCommand(commands[vcidx].name, &commands[vcidx]); +} diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h new file mode 100644 index 0000000000..77f7b42b23 --- /dev/null +++ b/src/wallet/rpcwallet.h @@ -0,0 +1,28 @@ +// Copyright (c) 2016-2017 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_RPCWALLET_H +#define BITCOIN_WALLET_RPCWALLET_H + +#include <string> + +class CRPCTable; +class CWallet; +class JSONRPCRequest; + +void RegisterWalletRPCCommands(CRPCTable &t); + +/** + * Figures out what wallet, if any, to use for a JSONRPCRequest. + * + * @param[in] request JSONRPCRequest that wishes to access a wallet + * @return nullptr if no wallet should be used, or a pointer to the CWallet + */ +CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request); + +std::string HelpRequiringPassphrase(CWallet *); +void EnsureWalletIsUnlocked(CWallet *); +bool EnsureWalletIsAvailable(CWallet *, bool avoidException); + +#endif //BITCOIN_WALLET_RPCWALLET_H diff --git a/src/wallet/test/accounting_tests.cpp b/src/wallet/test/accounting_tests.cpp new file mode 100644 index 0000000000..cafd69d075 --- /dev/null +++ b/src/wallet/test/accounting_tests.cpp @@ -0,0 +1,136 @@ +// Copyright (c) 2012-2017 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 <wallet/wallet.h> + +#include <wallet/test/wallet_test_fixture.h> + +#include <stdint.h> + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(accounting_tests, WalletTestingSetup) + +static void +GetResults(CWallet *wallet, std::map<CAmount, CAccountingEntry>& results) +{ + std::list<CAccountingEntry> aes; + + results.clear(); + BOOST_CHECK(wallet->ReorderTransactions() == DB_LOAD_OK); + wallet->ListAccountCreditDebit("", aes); + for (CAccountingEntry& ae : aes) + { + results[ae.nOrderPos] = ae; + } +} + +BOOST_AUTO_TEST_CASE(acc_orderupgrade) +{ + std::vector<CWalletTx*> vpwtx; + CWalletTx wtx; + CAccountingEntry ae; + std::map<CAmount, CAccountingEntry> results; + + LOCK(pwalletMain->cs_wallet); + + ae.strAccount = ""; + ae.nCreditDebit = 1; + ae.nTime = 1333333333; + ae.strOtherAccount = "b"; + ae.strComment = ""; + pwalletMain->AddAccountingEntry(ae); + + wtx.mapValue["comment"] = "z"; + pwalletMain->AddToWallet(wtx); + vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]); + vpwtx[0]->nTimeReceived = (unsigned int)1333333335; + vpwtx[0]->nOrderPos = -1; + + ae.nTime = 1333333336; + ae.strOtherAccount = "c"; + pwalletMain->AddAccountingEntry(ae); + + GetResults(pwalletMain.get(), results); + + BOOST_CHECK(pwalletMain->nOrderPosNext == 3); + BOOST_CHECK(2 == results.size()); + BOOST_CHECK(results[0].nTime == 1333333333); + BOOST_CHECK(results[0].strComment.empty()); + BOOST_CHECK(1 == vpwtx[0]->nOrderPos); + BOOST_CHECK(results[2].nTime == 1333333336); + BOOST_CHECK(results[2].strOtherAccount == "c"); + + + ae.nTime = 1333333330; + ae.strOtherAccount = "d"; + ae.nOrderPos = pwalletMain->IncOrderPosNext(); + pwalletMain->AddAccountingEntry(ae); + + GetResults(pwalletMain.get(), results); + + BOOST_CHECK(results.size() == 3); + BOOST_CHECK(pwalletMain->nOrderPosNext == 4); + BOOST_CHECK(results[0].nTime == 1333333333); + BOOST_CHECK(1 == vpwtx[0]->nOrderPos); + BOOST_CHECK(results[2].nTime == 1333333336); + BOOST_CHECK(results[3].nTime == 1333333330); + BOOST_CHECK(results[3].strComment.empty()); + + + wtx.mapValue["comment"] = "y"; + { + CMutableTransaction tx(*wtx.tx); + --tx.nLockTime; // Just to change the hash :) + wtx.SetTx(MakeTransactionRef(std::move(tx))); + } + pwalletMain->AddToWallet(wtx); + vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]); + vpwtx[1]->nTimeReceived = (unsigned int)1333333336; + + wtx.mapValue["comment"] = "x"; + { + CMutableTransaction tx(*wtx.tx); + --tx.nLockTime; // Just to change the hash :) + wtx.SetTx(MakeTransactionRef(std::move(tx))); + } + pwalletMain->AddToWallet(wtx); + vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]); + vpwtx[2]->nTimeReceived = (unsigned int)1333333329; + vpwtx[2]->nOrderPos = -1; + + GetResults(pwalletMain.get(), results); + + BOOST_CHECK(results.size() == 3); + BOOST_CHECK(pwalletMain->nOrderPosNext == 6); + BOOST_CHECK(0 == vpwtx[2]->nOrderPos); + BOOST_CHECK(results[1].nTime == 1333333333); + BOOST_CHECK(2 == vpwtx[0]->nOrderPos); + BOOST_CHECK(results[3].nTime == 1333333336); + BOOST_CHECK(results[4].nTime == 1333333330); + BOOST_CHECK(results[4].strComment.empty()); + BOOST_CHECK(5 == vpwtx[1]->nOrderPos); + + + ae.nTime = 1333333334; + ae.strOtherAccount = "e"; + ae.nOrderPos = -1; + pwalletMain->AddAccountingEntry(ae); + + GetResults(pwalletMain.get(), results); + + BOOST_CHECK(results.size() == 4); + BOOST_CHECK(pwalletMain->nOrderPosNext == 7); + BOOST_CHECK(0 == vpwtx[2]->nOrderPos); + BOOST_CHECK(results[1].nTime == 1333333333); + BOOST_CHECK(2 == vpwtx[0]->nOrderPos); + BOOST_CHECK(results[3].nTime == 1333333336); + BOOST_CHECK(results[3].strComment.empty()); + BOOST_CHECK(results[4].nTime == 1333333330); + BOOST_CHECK(results[4].strComment.empty()); + BOOST_CHECK(results[5].nTime == 1333333334); + BOOST_CHECK(6 == vpwtx[1]->nOrderPos); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/test/crypto_tests.cpp b/src/wallet/test/crypto_tests.cpp new file mode 100644 index 0000000000..89b2c4e796 --- /dev/null +++ b/src/wallet/test/crypto_tests.cpp @@ -0,0 +1,126 @@ +// Copyright (c) 2014-2017 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 <test/test_bitcoin.h> +#include <utilstrencodings.h> +#include <wallet/crypter.h> + +#include <vector> + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(wallet_crypto, BasicTestingSetup) + +class TestCrypter +{ +public: +static void TestPassphraseSingle(const std::vector<unsigned char>& vchSalt, const SecureString& passphrase, uint32_t rounds, + const std::vector<unsigned char>& correctKey = std::vector<unsigned char>(), + const std::vector<unsigned char>& correctIV=std::vector<unsigned char>()) +{ + CCrypter crypt; + crypt.SetKeyFromPassphrase(passphrase, vchSalt, rounds, 0); + + if(!correctKey.empty()) + BOOST_CHECK_MESSAGE(memcmp(crypt.vchKey.data(), correctKey.data(), crypt.vchKey.size()) == 0, \ + HexStr(crypt.vchKey.begin(), crypt.vchKey.end()) + std::string(" != ") + HexStr(correctKey.begin(), correctKey.end())); + if(!correctIV.empty()) + BOOST_CHECK_MESSAGE(memcmp(crypt.vchIV.data(), correctIV.data(), crypt.vchIV.size()) == 0, + HexStr(crypt.vchIV.begin(), crypt.vchIV.end()) + std::string(" != ") + HexStr(correctIV.begin(), correctIV.end())); +} + +static void TestPassphrase(const std::vector<unsigned char>& vchSalt, const SecureString& passphrase, uint32_t rounds, + const std::vector<unsigned char>& correctKey = std::vector<unsigned char>(), + const std::vector<unsigned char>& correctIV=std::vector<unsigned char>()) +{ + TestPassphraseSingle(vchSalt, passphrase, rounds, correctKey, correctIV); + for(SecureString::const_iterator i(passphrase.begin()); i != passphrase.end(); ++i) + TestPassphraseSingle(vchSalt, SecureString(i, passphrase.end()), rounds); +} + +static void TestDecrypt(const CCrypter& crypt, const std::vector<unsigned char>& vchCiphertext, \ + const std::vector<unsigned char>& vchPlaintext = std::vector<unsigned char>()) +{ + CKeyingMaterial vchDecrypted; + crypt.Decrypt(vchCiphertext, vchDecrypted); + if (vchPlaintext.size()) + BOOST_CHECK(CKeyingMaterial(vchPlaintext.begin(), vchPlaintext.end()) == vchDecrypted); +} + +static void TestEncryptSingle(const CCrypter& crypt, const CKeyingMaterial& vchPlaintext, + const std::vector<unsigned char>& vchCiphertextCorrect = std::vector<unsigned char>()) +{ + std::vector<unsigned char> vchCiphertext; + crypt.Encrypt(vchPlaintext, vchCiphertext); + + if (!vchCiphertextCorrect.empty()) + BOOST_CHECK(vchCiphertext == vchCiphertextCorrect); + + const std::vector<unsigned char> vchPlaintext2(vchPlaintext.begin(), vchPlaintext.end()); + TestDecrypt(crypt, vchCiphertext, vchPlaintext2); +} + +static void TestEncrypt(const CCrypter& crypt, const std::vector<unsigned char>& vchPlaintextIn, \ + const std::vector<unsigned char>& vchCiphertextCorrect = std::vector<unsigned char>()) +{ + TestEncryptSingle(crypt, CKeyingMaterial(vchPlaintextIn.begin(), vchPlaintextIn.end()), vchCiphertextCorrect); + for(std::vector<unsigned char>::const_iterator i(vchPlaintextIn.begin()); i != vchPlaintextIn.end(); ++i) + TestEncryptSingle(crypt, CKeyingMaterial(i, vchPlaintextIn.end())); +} + +}; + +BOOST_AUTO_TEST_CASE(passphrase) { + // These are expensive. + + TestCrypter::TestPassphrase(ParseHex("0000deadbeef0000"), "test", 25000, \ + ParseHex("fc7aba077ad5f4c3a0988d8daa4810d0d4a0e3bcb53af662998898f33df0556a"), \ + ParseHex("cf2f2691526dd1aa220896fb8bf7c369")); + + std::string hash(GetRandHash().ToString()); + std::vector<unsigned char> vchSalt(8); + GetRandBytes(vchSalt.data(), vchSalt.size()); + uint32_t rounds = InsecureRand32(); + if (rounds > 30000) + rounds = 30000; + TestCrypter::TestPassphrase(vchSalt, SecureString(hash.begin(), hash.end()), rounds); +} + +BOOST_AUTO_TEST_CASE(encrypt) { + std::vector<unsigned char> vchSalt = ParseHex("0000deadbeef0000"); + BOOST_CHECK(vchSalt.size() == WALLET_CRYPTO_SALT_SIZE); + CCrypter crypt; + crypt.SetKeyFromPassphrase("passphrase", vchSalt, 25000, 0); + TestCrypter::TestEncrypt(crypt, ParseHex("22bcade09ac03ff6386914359cfe885cfeb5f77ff0d670f102f619687453b29d")); + + for (int i = 0; i != 100; i++) + { + uint256 hash(GetRandHash()); + TestCrypter::TestEncrypt(crypt, std::vector<unsigned char>(hash.begin(), hash.end())); + } + +} + +BOOST_AUTO_TEST_CASE(decrypt) { + std::vector<unsigned char> vchSalt = ParseHex("0000deadbeef0000"); + BOOST_CHECK(vchSalt.size() == WALLET_CRYPTO_SALT_SIZE); + CCrypter crypt; + crypt.SetKeyFromPassphrase("passphrase", vchSalt, 25000, 0); + + // Some corner cases the came up while testing + TestCrypter::TestDecrypt(crypt,ParseHex("795643ce39d736088367822cdc50535ec6f103715e3e48f4f3b1a60a08ef59ca")); + TestCrypter::TestDecrypt(crypt,ParseHex("de096f4a8f9bd97db012aa9d90d74de8cdea779c3ee8bc7633d8b5d6da703486")); + TestCrypter::TestDecrypt(crypt,ParseHex("32d0a8974e3afd9c6c3ebf4d66aa4e6419f8c173de25947f98cf8b7ace49449c")); + TestCrypter::TestDecrypt(crypt,ParseHex("e7c055cca2faa78cb9ac22c9357a90b4778ded9b2cc220a14cea49f931e596ea")); + TestCrypter::TestDecrypt(crypt,ParseHex("b88efddd668a6801d19516d6830da4ae9811988ccbaf40df8fbb72f3f4d335fd")); + TestCrypter::TestDecrypt(crypt,ParseHex("8cae76aa6a43694e961ebcb28c8ca8f8540b84153d72865e8561ddd93fa7bfa9")); + + for (int i = 0; i != 100; i++) + { + uint256 hash(GetRandHash()); + TestCrypter::TestDecrypt(crypt, std::vector<unsigned char>(hash.begin(), hash.end())); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp new file mode 100644 index 0000000000..7797f85f07 --- /dev/null +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -0,0 +1,32 @@ +// Copyright (c) 2016-2017 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 <wallet/test/wallet_test_fixture.h> + +#include <rpc/server.h> +#include <wallet/db.h> + +WalletTestingSetup::WalletTestingSetup(const std::string& chainName): + TestingSetup(chainName) +{ + bitdb.MakeMock(); + + bool fFirstRun; + g_address_type = OUTPUT_TYPE_DEFAULT; + g_change_type = OUTPUT_TYPE_DEFAULT; + std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat")); + pwalletMain = MakeUnique<CWallet>(std::move(dbw)); + pwalletMain->LoadWallet(fFirstRun); + RegisterValidationInterface(pwalletMain.get()); + + RegisterWalletRPCCommands(tableRPC); +} + +WalletTestingSetup::~WalletTestingSetup() +{ + UnregisterValidationInterface(pwalletMain.get()); + + bitdb.Flush(true); + bitdb.Reset(); +} diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h new file mode 100644 index 0000000000..c03aec7f87 --- /dev/null +++ b/src/wallet/test/wallet_test_fixture.h @@ -0,0 +1,22 @@ +// Copyright (c) 2016-2017 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_TEST_FIXTURE_H +#define BITCOIN_WALLET_TEST_FIXTURE_H + +#include <test/test_bitcoin.h> + +#include <wallet/wallet.h> + +/** Testing setup and teardown for wallet. + */ +struct WalletTestingSetup: public TestingSetup { + explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); + ~WalletTestingSetup(); + + std::unique_ptr<CWallet> pwalletMain; +}; + +#endif + diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp new file mode 100644 index 0000000000..161372784b --- /dev/null +++ b/src/wallet/test/wallet_tests.cpp @@ -0,0 +1,705 @@ +// Copyright (c) 2012-2017 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 <wallet/wallet.h> + +#include <set> +#include <stdint.h> +#include <utility> +#include <vector> + +#include <consensus/validation.h> +#include <rpc/server.h> +#include <test/test_bitcoin.h> +#include <validation.h> +#include <wallet/coincontrol.h> +#include <wallet/test/wallet_test_fixture.h> + +#include <boost/test/unit_test.hpp> +#include <univalue.h> + +extern UniValue importmulti(const JSONRPCRequest& request); +extern UniValue dumpwallet(const JSONRPCRequest& request); +extern UniValue importwallet(const JSONRPCRequest& request); + +// how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles +#define RUN_TESTS 100 + +// some tests fail 1% of the time due to bad luck. +// we repeat those tests this many times and only complain if all iterations of the test fail +#define RANDOM_REPEATS 5 + +std::vector<std::unique_ptr<CWalletTx>> wtxn; + +typedef std::set<CInputCoin> CoinSet; + +BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup) + +static const CWallet testWallet; +static std::vector<COutput> vCoins; + +static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0) +{ + static int nextLockTime = 0; + CMutableTransaction tx; + tx.nLockTime = nextLockTime++; // so all transactions get different hashes + tx.vout.resize(nInput+1); + tx.vout[nInput].nValue = nValue; + if (fIsFromMe) { + // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(), + // so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe() + tx.vin.resize(1); + } + std::unique_ptr<CWalletTx> wtx(new CWalletTx(&testWallet, MakeTransactionRef(std::move(tx)))); + if (fIsFromMe) + { + wtx->fDebitCached = true; + wtx->nDebitCached = 1; + } + COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); + vCoins.push_back(output); + wtxn.emplace_back(std::move(wtx)); +} + +static void empty_wallet(void) +{ + vCoins.clear(); + wtxn.clear(); +} + +static bool equal_sets(CoinSet a, CoinSet b) +{ + std::pair<CoinSet::iterator, CoinSet::iterator> ret = mismatch(a.begin(), a.end(), b.begin()); + return ret.first == a.end() && ret.second == b.end(); +} + +BOOST_AUTO_TEST_CASE(coin_selection_tests) +{ + CoinSet setCoinsRet, setCoinsRet2; + CAmount nValueRet; + + LOCK(testWallet.cs_wallet); + + // test multiple times to allow for differences in the shuffle order + for (int i = 0; i < RUN_TESTS; i++) + { + empty_wallet(); + + // with an empty wallet we can't even pay one cent + BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + + add_coin(1*CENT, 4); // add a new 1 cent coin + + // with a new 1 cent coin, we still can't find a mature 1 cent + BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + + // but we can find a new 1 cent + BOOST_CHECK( testWallet.SelectCoinsMinConf( 1 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); + + add_coin(2*CENT); // add a mature 2 cent coin + + // we can't make 3 cents of mature coins + BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + + // we can make 3 cents of new coins + BOOST_CHECK( testWallet.SelectCoinsMinConf( 3 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 3 * CENT); + + add_coin(5*CENT); // add a mature 5 cent coin, + add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses + add_coin(20*CENT); // and a mature 20 cent coin + + // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38 + + // we can't make 38 cents only if we disallow new coins: + BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + // we can't even make 37 cents if we don't allow new coins even if they're from us + BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, 6, 6, 0, vCoins, setCoinsRet, nValueRet)); + // but we can make 37 cents if we accept new coins from ourself + BOOST_CHECK( testWallet.SelectCoinsMinConf(37 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 37 * CENT); + // and we can make 38 cents if we accept all new coins + BOOST_CHECK( testWallet.SelectCoinsMinConf(38 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 38 * CENT); + + // try making 34 cents from 1,2,5,10,20 - we can't do it exactly + BOOST_CHECK( testWallet.SelectCoinsMinConf(34 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest + BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible) + + // when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5 + BOOST_CHECK( testWallet.SelectCoinsMinConf( 7 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 7 * CENT); + BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); + + // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough. + BOOST_CHECK( testWallet.SelectCoinsMinConf( 8 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(nValueRet == 8 * CENT); + BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); + + // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10) + BOOST_CHECK( testWallet.SelectCoinsMinConf( 9 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 10 * CENT); + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); + + // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin + empty_wallet(); + + add_coin( 6*CENT); + add_coin( 7*CENT); + add_coin( 8*CENT); + add_coin(20*CENT); + add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total + + // check that we have 71 and not 72 + BOOST_CHECK( testWallet.SelectCoinsMinConf(71 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!testWallet.SelectCoinsMinConf(72 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + + // now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20 + BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); + + add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total + + // now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20 + BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins + BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); + + add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30 + + // and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18 + BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins + + // now try making 11 cents. we should get 5+6 + BOOST_CHECK( testWallet.SelectCoinsMinConf(11 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 11 * CENT); + BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); + + // check that the smallest bigger coin is used + add_coin( 1*COIN); + add_coin( 2*COIN); + add_coin( 3*COIN); + add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents + BOOST_CHECK( testWallet.SelectCoinsMinConf(95 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); + + BOOST_CHECK( testWallet.SelectCoinsMinConf(195 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); + + // empty the wallet and start again, now with fractions of a cent, to test small change avoidance + + empty_wallet(); + add_coin(MIN_CHANGE * 1 / 10); + add_coin(MIN_CHANGE * 2 / 10); + add_coin(MIN_CHANGE * 3 / 10); + add_coin(MIN_CHANGE * 4 / 10); + add_coin(MIN_CHANGE * 5 / 10); + + // try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE + // we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly + BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); + + // but if we add a bigger coin, small change is avoided + add_coin(1111*MIN_CHANGE); + + // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 + BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount + + // if we add more small coins: + add_coin(MIN_CHANGE * 6 / 10); + add_coin(MIN_CHANGE * 7 / 10); + + // and try again to make 1.0 * MIN_CHANGE + BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount + + // run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf) + // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change + empty_wallet(); + for (int j = 0; j < 20; j++) + add_coin(50000 * COIN); + + BOOST_CHECK( testWallet.SelectCoinsMinConf(500000 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount + BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins + + // if there's not enough in the smaller coins to make at least 1 * MIN_CHANGE change (0.5+0.6+0.7 < 1.0+1.0), + // we need to try finding an exact subset anyway + + // sometimes it will fail, and so we use the next biggest coin: + empty_wallet(); + add_coin(MIN_CHANGE * 5 / 10); + add_coin(MIN_CHANGE * 6 / 10); + add_coin(MIN_CHANGE * 7 / 10); + add_coin(1111 * MIN_CHANGE); + BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); + + // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0) + empty_wallet(); + add_coin(MIN_CHANGE * 4 / 10); + add_coin(MIN_CHANGE * 6 / 10); + add_coin(MIN_CHANGE * 8 / 10); + add_coin(1111 * MIN_CHANGE); + BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount + BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6 + + // test avoiding small change + empty_wallet(); + add_coin(MIN_CHANGE * 5 / 100); + add_coin(MIN_CHANGE * 1); + add_coin(MIN_CHANGE * 100); + + // trying to make 100.01 from these three coins + BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all coins + BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); + + // but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change + BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 9990 / 100, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE); + BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); + + // test with many inputs + for (CAmount amt=1500; amt < COIN; amt*=10) { + empty_wallet(); + // Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 bytes per input) + for (uint16_t j = 0; j < 676; j++) + add_coin(amt); + BOOST_CHECK(testWallet.SelectCoinsMinConf(2000, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + if (amt - 2000 < MIN_CHANGE) { + // needs more than one input: + uint16_t returnSize = std::ceil((2000.0 + MIN_CHANGE)/amt); + CAmount returnValue = amt * returnSize; + BOOST_CHECK_EQUAL(nValueRet, returnValue); + BOOST_CHECK_EQUAL(setCoinsRet.size(), returnSize); + } else { + // one input is sufficient: + BOOST_CHECK_EQUAL(nValueRet, amt); + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); + } + } + + // test randomness + { + empty_wallet(); + for (int i2 = 0; i2 < 100; i2++) + add_coin(COIN); + + // picking 50 from 100 coins doesn't depend on the shuffle, + // but does depend on randomness in the stochastic approximation code + BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, 1, 6, 0, vCoins, setCoinsRet , nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, 1, 6, 0, vCoins, setCoinsRet2, nValueRet)); + BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2)); + + int fails = 0; + for (int j = 0; j < RANDOM_REPEATS; j++) + { + // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time + // run the test RANDOM_REPEATS times and only complain if all of them fail + BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, 1, 6, 0, vCoins, setCoinsRet , nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, 1, 6, 0, vCoins, setCoinsRet2, nValueRet)); + if (equal_sets(setCoinsRet, setCoinsRet2)) + fails++; + } + BOOST_CHECK_NE(fails, RANDOM_REPEATS); + + // add 75 cents in small change. not enough to make 90 cents, + // then try making 90 cents. there are multiple competing "smallest bigger" coins, + // one of which should be picked at random + add_coin(5 * CENT); + add_coin(10 * CENT); + add_coin(15 * CENT); + add_coin(20 * CENT); + add_coin(25 * CENT); + + fails = 0; + for (int j = 0; j < RANDOM_REPEATS; j++) + { + // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time + // run the test RANDOM_REPEATS times and only complain if all of them fail + BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, 1, 6, 0, vCoins, setCoinsRet , nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, 1, 6, 0, vCoins, setCoinsRet2, nValueRet)); + if (equal_sets(setCoinsRet, setCoinsRet2)) + fails++; + } + BOOST_CHECK_NE(fails, RANDOM_REPEATS); + } + } + empty_wallet(); +} + +BOOST_AUTO_TEST_CASE(ApproximateBestSubset) +{ + CoinSet setCoinsRet; + CAmount nValueRet; + + LOCK(testWallet.cs_wallet); + + empty_wallet(); + + // Test vValue sort order + for (int i = 0; i < 1000; i++) + add_coin(1000 * COIN); + add_coin(3 * COIN); + + BOOST_CHECK(testWallet.SelectCoinsMinConf(1003 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN); + BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); + + empty_wallet(); +} + +static void AddKey(CWallet& wallet, const CKey& key) +{ + LOCK(wallet.cs_wallet); + wallet.AddKeyPubKey(key, key.GetPubKey()); +} + +BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) +{ + // Cap last block file size, and mine new block in a new block file. + CBlockIndex* const nullBlock = nullptr; + CBlockIndex* oldTip = chainActive.Tip(); + GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; + CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); + CBlockIndex* newTip = chainActive.Tip(); + + LOCK(cs_main); + + // Verify ScanForWalletTransactions picks up transactions in both the old + // and new block files. + { + CWallet wallet; + AddKey(wallet, coinbaseKey); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver)); + BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); + } + + // Prune the older block file. + PruneOneBlockFile(oldTip->GetBlockPos().nFile); + UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); + + // Verify ScanForWalletTransactions only picks transactions in the new block + // file. + { + CWallet wallet; + AddKey(wallet, coinbaseKey); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver)); + BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); + } + + // Verify importmulti RPC returns failure for a key whose creation time is + // before the missing block, and success for a key whose creation time is + // after. + { + CWallet wallet; + vpwallets.insert(vpwallets.begin(), &wallet); + UniValue keys; + keys.setArray(); + UniValue key; + key.setObject(); + key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey()))); + key.pushKV("timestamp", 0); + key.pushKV("internal", UniValue(true)); + keys.push_back(key); + key.clear(); + key.setObject(); + CKey futureKey; + futureKey.MakeNewKey(true); + key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey()))); + key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1); + key.pushKV("internal", UniValue(true)); + keys.push_back(key); + JSONRPCRequest request; + request.params.setArray(); + request.params.push_back(keys); + + UniValue response = importmulti(request); + BOOST_CHECK_EQUAL(response.write(), + strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":\"Rescan failed for key with creation " + "timestamp %d. There was an error reading a block from time %d, which is after or within %d " + "seconds of key creation, and could contain transactions pertaining to the key. As a result, " + "transactions and coins using this key may not appear in the wallet. This error could be caused " + "by pruning or data corruption (see bitcoind log for details) and could be dealt with by " + "downloading and rescanning the relevant blocks (see -reindex and -rescan " + "options).\"}},{\"success\":true}]", + 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW)); + vpwallets.erase(vpwallets.begin()); + } +} + +// Verify importwallet RPC starts rescan at earliest block with timestamp +// greater or equal than key birthday. Previously there was a bug where +// importwallet RPC would start the scan at the latest block with timestamp less +// than or equal to key birthday. +BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) +{ + // Create two blocks with same timestamp to verify that importwallet rescan + // will pick up both blocks, not just the first. + const int64_t BLOCK_TIME = chainActive.Tip()->GetBlockTimeMax() + 5; + SetMockTime(BLOCK_TIME); + coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); + coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); + + // Set key birthday to block time increased by the timestamp window, so + // rescan will start at the block time. + const int64_t KEY_TIME = BLOCK_TIME + TIMESTAMP_WINDOW; + SetMockTime(KEY_TIME); + coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); + + LOCK(cs_main); + + // Import key into wallet and call dumpwallet to create backup file. + { + CWallet wallet; + LOCK(wallet.cs_wallet); + wallet.mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; + wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); + + JSONRPCRequest request; + request.params.setArray(); + request.params.push_back((pathTemp / "wallet.backup").string()); + vpwallets.insert(vpwallets.begin(), &wallet); + ::dumpwallet(request); + } + + // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME + // were scanned, and no prior blocks were scanned. + { + CWallet wallet; + + JSONRPCRequest request; + request.params.setArray(); + request.params.push_back((pathTemp / "wallet.backup").string()); + vpwallets[0] = &wallet; + ::importwallet(request); + + LOCK(wallet.cs_wallet); + BOOST_CHECK_EQUAL(wallet.mapWallet.size(), 3); + BOOST_CHECK_EQUAL(coinbaseTxns.size(), 103); + for (size_t i = 0; i < coinbaseTxns.size(); ++i) { + bool found = wallet.GetWalletTx(coinbaseTxns[i].GetHash()); + bool expected = i >= 100; + BOOST_CHECK_EQUAL(found, expected); + } + } + + SetMockTime(0); + vpwallets.erase(vpwallets.begin()); +} + +// Check that GetImmatureCredit() returns a newly calculated value instead of +// the cached value after a MarkDirty() call. +// +// This is a regression test written to verify a bugfix for the immature credit +// function. Similar tests probably should be written for the other credit and +// debit functions. +BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) +{ + CWallet wallet; + CWalletTx wtx(&wallet, MakeTransactionRef(coinbaseTxns.back())); + LOCK2(cs_main, wallet.cs_wallet); + wtx.hashBlock = chainActive.Tip()->GetBlockHash(); + wtx.nIndex = 0; + + // Call GetImmatureCredit() once before adding the key to the wallet to + // cache the current immature credit amount, which is 0. + BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 0); + + // Invalidate the cached value, add the key, and make sure a new immature + // credit amount is calculated. + wtx.MarkDirty(); + wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); + BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50*COIN); +} + +static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) +{ + CMutableTransaction tx; + tx.nLockTime = lockTime; + SetMockTime(mockTime); + CBlockIndex* block = nullptr; + if (blockTime > 0) { + LOCK(cs_main); + auto inserted = mapBlockIndex.emplace(GetRandHash(), new CBlockIndex); + assert(inserted.second); + const uint256& hash = inserted.first->first; + block = inserted.first->second; + block->nTime = blockTime; + block->phashBlock = &hash; + } + + CWalletTx wtx(&wallet, MakeTransactionRef(tx)); + if (block) { + wtx.SetMerkleBranch(block, 0); + } + wallet.AddToWallet(wtx); + LOCK(wallet.cs_wallet); + return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart; +} + +// Simple test to verify assignment of CWalletTx::nSmartTime value. Could be +// expanded to cover more corner cases of smart time logic. +BOOST_AUTO_TEST_CASE(ComputeTimeSmart) +{ + CWallet wallet; + + // New transaction should use clock time if lower than block time. + BOOST_CHECK_EQUAL(AddTx(wallet, 1, 100, 120), 100); + + // Test that updating existing transaction does not change smart time. + BOOST_CHECK_EQUAL(AddTx(wallet, 1, 200, 220), 100); + + // New transaction should use clock time if there's no block time. + BOOST_CHECK_EQUAL(AddTx(wallet, 2, 300, 0), 300); + + // New transaction should use block time if lower than clock time. + BOOST_CHECK_EQUAL(AddTx(wallet, 3, 420, 400), 400); + + // New transaction should use latest entry time if higher than + // min(block time, clock time). + BOOST_CHECK_EQUAL(AddTx(wallet, 4, 500, 390), 400); + + // If there are future entries, new transaction should use time of the + // newest entry that is no more than 300 seconds ahead of the clock time. + BOOST_CHECK_EQUAL(AddTx(wallet, 5, 50, 600), 300); + + // Reset mock time for other tests. + SetMockTime(0); +} + +BOOST_AUTO_TEST_CASE(LoadReceiveRequests) +{ + CTxDestination dest = CKeyID(); + LOCK(pwalletMain->cs_wallet); + pwalletMain->AddDestData(dest, "misc", "val_misc"); + pwalletMain->AddDestData(dest, "rr0", "val_rr0"); + pwalletMain->AddDestData(dest, "rr1", "val_rr1"); + + auto values = pwalletMain->GetDestValues("rr"); + BOOST_CHECK_EQUAL(values.size(), 2); + BOOST_CHECK_EQUAL(values[0], "val_rr0"); + BOOST_CHECK_EQUAL(values[1], "val_rr1"); +} + +class ListCoinsTestingSetup : public TestChain100Setup +{ +public: + ListCoinsTestingSetup() + { + CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); + ::bitdb.MakeMock(); + g_address_type = OUTPUT_TYPE_DEFAULT; + g_change_type = OUTPUT_TYPE_DEFAULT; + wallet.reset(new CWallet(std::unique_ptr<CWalletDBWrapper>(new CWalletDBWrapper(&bitdb, "wallet_test.dat")))); + bool firstRun; + wallet->LoadWallet(firstRun); + AddKey(*wallet, coinbaseKey); + WalletRescanReserver reserver(wallet.get()); + reserver.reserve(); + wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver); + } + + ~ListCoinsTestingSetup() + { + wallet.reset(); + ::bitdb.Flush(true); + ::bitdb.Reset(); + } + + CWalletTx& AddTx(CRecipient recipient) + { + CWalletTx wtx; + CReserveKey reservekey(wallet.get()); + CAmount fee; + int changePos = -1; + std::string error; + CCoinControl dummy; + BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error, dummy)); + CValidationState state; + BOOST_CHECK(wallet->CommitTransaction(wtx, reservekey, nullptr, state)); + CMutableTransaction blocktx; + { + LOCK(wallet->cs_wallet); + blocktx = CMutableTransaction(*wallet->mapWallet.at(wtx.GetHash()).tx); + } + CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); + LOCK(wallet->cs_wallet); + auto it = wallet->mapWallet.find(wtx.GetHash()); + BOOST_CHECK(it != wallet->mapWallet.end()); + it->second.SetMerkleBranch(chainActive.Tip(), 1); + return it->second; + } + + std::unique_ptr<CWallet> wallet; +}; + +BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) +{ + std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString(); + + // Confirm ListCoins initially returns 1 coin grouped under coinbaseKey + // address. + auto list = wallet->ListCoins(); + BOOST_CHECK_EQUAL(list.size(), 1); + BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); + BOOST_CHECK_EQUAL(list.begin()->second.size(), 1); + + // Check initial balance from one mature coinbase transaction. + BOOST_CHECK_EQUAL(50 * COIN, wallet->GetAvailableBalance()); + + // Add a transaction creating a change address, and confirm ListCoins still + // returns the coin associated with the change address underneath the + // coinbaseKey pubkey, even though the change address has a different + // pubkey. + AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */}); + list = wallet->ListCoins(); + BOOST_CHECK_EQUAL(list.size(), 1); + BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); + BOOST_CHECK_EQUAL(list.begin()->second.size(), 2); + + // Lock both coins. Confirm number of available coins drops to 0. + { + LOCK2(cs_main, wallet->cs_wallet); + std::vector<COutput> available; + wallet->AvailableCoins(available); + BOOST_CHECK_EQUAL(available.size(), 2); + } + for (const auto& group : list) { + for (const auto& coin : group.second) { + LOCK(wallet->cs_wallet); + wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i)); + } + } + { + LOCK2(cs_main, wallet->cs_wallet); + std::vector<COutput> available; + wallet->AvailableCoins(available); + BOOST_CHECK_EQUAL(available.size(), 0); + } + // Confirm ListCoins still returns same result as before, despite coins + // being locked. + list = wallet->ListCoins(); + BOOST_CHECK_EQUAL(list.size(), 1); + BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); + BOOST_CHECK_EQUAL(list.begin()->second.size(), 2); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp new file mode 100644 index 0000000000..513819606b --- /dev/null +++ b/src/wallet/wallet.cpp @@ -0,0 +1,4299 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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 <wallet/wallet.h> + +#include <base58.h> +#include <checkpoints.h> +#include <chain.h> +#include <wallet/coincontrol.h> +#include <consensus/consensus.h> +#include <consensus/validation.h> +#include <fs.h> +#include <wallet/init.h> +#include <key.h> +#include <keystore.h> +#include <validation.h> +#include <net.h> +#include <policy/fees.h> +#include <policy/policy.h> +#include <policy/rbf.h> +#include <primitives/block.h> +#include <primitives/transaction.h> +#include <script/script.h> +#include <scheduler.h> +#include <timedata.h> +#include <txmempool.h> +#include <util.h> +#include <utilmoneystr.h> +#include <wallet/fees.h> + +#include <assert.h> +#include <future> + +#include <boost/algorithm/string/replace.hpp> + +std::vector<CWalletRef> vpwallets; +/** Transaction fee set by the user */ +CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); +unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET; +bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE; +bool fWalletRbf = DEFAULT_WALLET_RBF; +OutputType g_address_type = OUTPUT_TYPE_NONE; +OutputType g_change_type = OUTPUT_TYPE_NONE; + +const char * DEFAULT_WALLET_DAT = "wallet.dat"; +const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; + +/** + * Fees smaller than this (in satoshi) are considered zero fee (for transaction creation) + * Override with -mintxfee + */ +CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE); +/** + * If fee estimation does not have enough data to provide estimates, use this fee instead. + * Has no effect if not using fee estimation + * Override with -fallbackfee + */ +CFeeRate CWallet::fallbackFee = CFeeRate(DEFAULT_FALLBACK_FEE); + +CFeeRate CWallet::m_discard_rate = CFeeRate(DEFAULT_DISCARD_FEE); + +const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); + +/** @defgroup mapWallet + * + * @{ + */ + +struct CompareValueOnly +{ + bool operator()(const CInputCoin& t1, + const CInputCoin& t2) const + { + return t1.txout.nValue < t2.txout.nValue; + } +}; + +std::string COutput::ToString() const +{ + return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); +} + +class CAffectedKeysVisitor : public boost::static_visitor<void> { +private: + const CKeyStore &keystore; + std::vector<CKeyID> &vKeys; + +public: + CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector<CKeyID> &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {} + + void Process(const CScript &script) { + txnouttype type; + std::vector<CTxDestination> vDest; + int nRequired; + if (ExtractDestinations(script, type, vDest, nRequired)) { + for (const CTxDestination &dest : vDest) + boost::apply_visitor(*this, dest); + } + } + + void operator()(const CKeyID &keyId) { + if (keystore.HaveKey(keyId)) + vKeys.push_back(keyId); + } + + void operator()(const CScriptID &scriptId) { + CScript script; + if (keystore.GetCScript(scriptId, script)) + Process(script); + } + + void operator()(const WitnessV0ScriptHash& scriptID) + { + CScriptID id; + CRIPEMD160().Write(scriptID.begin(), 32).Finalize(id.begin()); + CScript script; + if (keystore.GetCScript(id, script)) { + Process(script); + } + } + + void operator()(const WitnessV0KeyHash& keyid) + { + CKeyID id(keyid); + if (keystore.HaveKey(id)) { + vKeys.push_back(id); + } + } + + template<typename X> + void operator()(const X &none) {} +}; + +const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const +{ + LOCK(cs_wallet); + std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(hash); + if (it == mapWallet.end()) + return nullptr; + return &(it->second); +} + +CPubKey CWallet::GenerateNewKey(CWalletDB &walletdb, bool internal) +{ + AssertLockHeld(cs_wallet); // mapKeyMetadata + bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets + + CKey secret; + + // Create new metadata + int64_t nCreationTime = GetTime(); + CKeyMetadata metadata(nCreationTime); + + // use HD key derivation if HD was enabled during wallet creation + if (IsHDEnabled()) { + DeriveNewChildKey(walletdb, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false)); + } else { + secret.MakeNewKey(fCompressed); + } + + // Compressed public keys were introduced in version 0.6.0 + if (fCompressed) { + SetMinVersion(FEATURE_COMPRPUBKEY); + } + + CPubKey pubkey = secret.GetPubKey(); + assert(secret.VerifyPubKey(pubkey)); + + mapKeyMetadata[pubkey.GetID()] = metadata; + UpdateTimeFirstKey(nCreationTime); + + if (!AddKeyPubKeyWithDB(walletdb, secret, pubkey)) { + throw std::runtime_error(std::string(__func__) + ": AddKey failed"); + } + return pubkey; +} + +void CWallet::DeriveNewChildKey(CWalletDB &walletdb, CKeyMetadata& metadata, CKey& secret, bool internal) +{ + // for now we use a fixed keypath scheme of m/0'/0'/k + CKey key; //master key seed (256bit) + CExtKey masterKey; //hd master key + CExtKey accountKey; //key at m/0' + CExtKey chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal) + CExtKey childKey; //key at m/0'/0'/<n>' + + // try to get the master key + if (!GetKey(hdChain.masterKeyID, key)) + throw std::runtime_error(std::string(__func__) + ": Master key not found"); + + masterKey.SetMaster(key.begin(), key.size()); + + // derive m/0' + // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) + masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT); + + // derive m/0'/0' (external chain) OR m/0'/1' (internal chain) + assert(internal ? CanSupportFeature(FEATURE_HD_SPLIT) : true); + accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0)); + + // derive child key at next index, skip keys already known to the wallet + do { + // always derive hardened keys + // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range + // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649 + if (internal) { + chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); + metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'"; + hdChain.nInternalChainCounter++; + } + else { + chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); + metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'"; + hdChain.nExternalChainCounter++; + } + } while (HaveKey(childKey.key.GetPubKey().GetID())); + secret = childKey.key; + metadata.hdMasterKeyID = hdChain.masterKeyID; + // update the chain model in the database + if (!walletdb.WriteHDChain(hdChain)) + throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); +} + +bool CWallet::AddKeyPubKeyWithDB(CWalletDB &walletdb, const CKey& secret, const CPubKey &pubkey) +{ + AssertLockHeld(cs_wallet); // mapKeyMetadata + + // CCryptoKeyStore has no concept of wallet databases, but calls AddCryptedKey + // which is overridden below. To avoid flushes, the database handle is + // tunneled through to it. + bool needsDB = !pwalletdbEncryption; + if (needsDB) { + pwalletdbEncryption = &walletdb; + } + if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) { + if (needsDB) pwalletdbEncryption = nullptr; + return false; + } + if (needsDB) pwalletdbEncryption = nullptr; + + // check if we need to remove from watch-only + CScript script; + script = GetScriptForDestination(pubkey.GetID()); + if (HaveWatchOnly(script)) { + RemoveWatchOnly(script); + } + script = GetScriptForRawPubKey(pubkey); + if (HaveWatchOnly(script)) { + RemoveWatchOnly(script); + } + + if (!IsCrypted()) { + return walletdb.WriteKey(pubkey, + secret.GetPrivKey(), + mapKeyMetadata[pubkey.GetID()]); + } + return true; +} + +bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) +{ + CWalletDB walletdb(*dbw); + return CWallet::AddKeyPubKeyWithDB(walletdb, secret, pubkey); +} + +bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, + const std::vector<unsigned char> &vchCryptedSecret) +{ + if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) + return false; + { + LOCK(cs_wallet); + if (pwalletdbEncryption) + return pwalletdbEncryption->WriteCryptedKey(vchPubKey, + vchCryptedSecret, + mapKeyMetadata[vchPubKey.GetID()]); + else + return CWalletDB(*dbw).WriteCryptedKey(vchPubKey, + vchCryptedSecret, + mapKeyMetadata[vchPubKey.GetID()]); + } +} + +bool CWallet::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &meta) +{ + AssertLockHeld(cs_wallet); // mapKeyMetadata + UpdateTimeFirstKey(meta.nCreateTime); + mapKeyMetadata[keyID] = meta; + return true; +} + +bool CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &meta) +{ + AssertLockHeld(cs_wallet); // m_script_metadata + UpdateTimeFirstKey(meta.nCreateTime); + m_script_metadata[script_id] = meta; + return true; +} + +bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) +{ + return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); +} + +/** + * Update wallet first key creation time. This should be called whenever keys + * are added to the wallet, with the oldest key creation time. + */ +void CWallet::UpdateTimeFirstKey(int64_t nCreateTime) +{ + AssertLockHeld(cs_wallet); + if (nCreateTime <= 1) { + // Cannot determine birthday information, so set the wallet birthday to + // the beginning of time. + nTimeFirstKey = 1; + } else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) { + nTimeFirstKey = nCreateTime; + } +} + +bool CWallet::AddCScript(const CScript& redeemScript) +{ + if (!CCryptoKeyStore::AddCScript(redeemScript)) + return false; + return CWalletDB(*dbw).WriteCScript(Hash160(redeemScript), redeemScript); +} + +bool CWallet::LoadCScript(const CScript& redeemScript) +{ + /* A sanity check was added in pull #3843 to avoid adding redeemScripts + * that never can be redeemed. However, old wallets may still contain + * these. Do not add them to the wallet and warn. */ + if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) + { + std::string strAddr = EncodeDestination(CScriptID(redeemScript)); + LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", + __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); + return true; + } + + return CCryptoKeyStore::AddCScript(redeemScript); +} + +bool CWallet::AddWatchOnly(const CScript& dest) +{ + if (!CCryptoKeyStore::AddWatchOnly(dest)) + return false; + const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)]; + UpdateTimeFirstKey(meta.nCreateTime); + NotifyWatchonlyChanged(true); + return CWalletDB(*dbw).WriteWatchOnly(dest, meta); +} + +bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime) +{ + m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime; + return AddWatchOnly(dest); +} + +bool CWallet::RemoveWatchOnly(const CScript &dest) +{ + AssertLockHeld(cs_wallet); + if (!CCryptoKeyStore::RemoveWatchOnly(dest)) + return false; + if (!HaveWatchOnly()) + NotifyWatchonlyChanged(false); + if (!CWalletDB(*dbw).EraseWatchOnly(dest)) + return false; + + return true; +} + +bool CWallet::LoadWatchOnly(const CScript &dest) +{ + return CCryptoKeyStore::AddWatchOnly(dest); +} + +bool CWallet::Unlock(const SecureString& strWalletPassphrase) +{ + CCrypter crypter; + CKeyingMaterial _vMasterKey; + + { + LOCK(cs_wallet); + for (const MasterKeyMap::value_type& pMasterKey : mapMasterKeys) + { + if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + return false; + if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) + continue; // try another master key + if (CCryptoKeyStore::Unlock(_vMasterKey)) + return true; + } + } + return false; +} + +bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase) +{ + bool fWasLocked = IsLocked(); + + { + LOCK(cs_wallet); + Lock(); + + CCrypter crypter; + CKeyingMaterial _vMasterKey; + for (MasterKeyMap::value_type& pMasterKey : mapMasterKeys) + { + if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + return false; + if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) + return false; + if (CCryptoKeyStore::Unlock(_vMasterKey)) + { + int64_t nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); + pMasterKey.second.nDeriveIterations = static_cast<unsigned int>(pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime)))); + + nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); + pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + static_cast<unsigned int>(pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime)))) / 2; + + if (pMasterKey.second.nDeriveIterations < 25000) + pMasterKey.second.nDeriveIterations = 25000; + + LogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); + + if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + return false; + if (!crypter.Encrypt(_vMasterKey, pMasterKey.second.vchCryptedKey)) + return false; + CWalletDB(*dbw).WriteMasterKey(pMasterKey.first, pMasterKey.second); + if (fWasLocked) + Lock(); + return true; + } + } + } + + return false; +} + +void CWallet::SetBestChain(const CBlockLocator& loc) +{ + CWalletDB walletdb(*dbw); + walletdb.WriteBestBlock(loc); +} + +bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, bool fExplicit) +{ + LOCK(cs_wallet); // nWalletVersion + if (nWalletVersion >= nVersion) + return true; + + // when doing an explicit upgrade, if we pass the max version permitted, upgrade all the way + if (fExplicit && nVersion > nWalletMaxVersion) + nVersion = FEATURE_LATEST; + + nWalletVersion = nVersion; + + if (nVersion > nWalletMaxVersion) + nWalletMaxVersion = nVersion; + + { + CWalletDB* pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(*dbw); + if (nWalletVersion > 40000) + pwalletdb->WriteMinVersion(nWalletVersion); + if (!pwalletdbIn) + delete pwalletdb; + } + + return true; +} + +bool CWallet::SetMaxVersion(int nVersion) +{ + LOCK(cs_wallet); // nWalletVersion, nWalletMaxVersion + // cannot downgrade below current version + if (nWalletVersion > nVersion) + return false; + + nWalletMaxVersion = nVersion; + + return true; +} + +std::set<uint256> CWallet::GetConflicts(const uint256& txid) const +{ + std::set<uint256> result; + AssertLockHeld(cs_wallet); + + std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(txid); + if (it == mapWallet.end()) + return result; + const CWalletTx& wtx = it->second; + + std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range; + + for (const CTxIn& txin : wtx.tx->vin) + { + if (mapTxSpends.count(txin.prevout) <= 1) + continue; // No conflict if zero or one spends + range = mapTxSpends.equal_range(txin.prevout); + for (TxSpends::const_iterator _it = range.first; _it != range.second; ++_it) + result.insert(_it->second); + } + return result; +} + +bool CWallet::HasWalletSpend(const uint256& txid) const +{ + AssertLockHeld(cs_wallet); + auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0)); + return (iter != mapTxSpends.end() && iter->first.hash == txid); +} + +void CWallet::Flush(bool shutdown) +{ + dbw->Flush(shutdown); +} + +void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> range) +{ + // We want all the wallet transactions in range to have the same metadata as + // the oldest (smallest nOrderPos). + // So: find smallest nOrderPos: + + int nMinOrderPos = std::numeric_limits<int>::max(); + const CWalletTx* copyFrom = nullptr; + for (TxSpends::iterator it = range.first; it != range.second; ++it) { + const CWalletTx* wtx = &mapWallet[it->second]; + if (wtx->nOrderPos < nMinOrderPos) { + nMinOrderPos = wtx->nOrderPos;; + copyFrom = wtx; + } + } + + assert(copyFrom); + + // Now copy data from copyFrom to rest: + for (TxSpends::iterator it = range.first; it != range.second; ++it) + { + const uint256& hash = it->second; + CWalletTx* copyTo = &mapWallet[hash]; + if (copyFrom == copyTo) continue; + assert(copyFrom && "Oldest wallet transaction in range assumed to have been found."); + if (!copyFrom->IsEquivalentTo(*copyTo)) continue; + copyTo->mapValue = copyFrom->mapValue; + copyTo->vOrderForm = copyFrom->vOrderForm; + // fTimeReceivedIsTxTime not copied on purpose + // nTimeReceived not copied on purpose + copyTo->nTimeSmart = copyFrom->nTimeSmart; + copyTo->fFromMe = copyFrom->fFromMe; + copyTo->strFromAccount = copyFrom->strFromAccount; + // nOrderPos not copied on purpose + // cached members not copied on purpose + } +} + +/** + * Outpoint is spent if any non-conflicted transaction + * spends it: + */ +bool CWallet::IsSpent(const uint256& hash, unsigned int n) const +{ + const COutPoint outpoint(hash, n); + std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range; + range = mapTxSpends.equal_range(outpoint); + + for (TxSpends::const_iterator it = range.first; it != range.second; ++it) + { + const uint256& wtxid = it->second; + std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); + if (mit != mapWallet.end()) { + int depth = mit->second.GetDepthInMainChain(); + if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) + return true; // Spent + } + } + return false; +} + +void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid) +{ + mapTxSpends.insert(std::make_pair(outpoint, wtxid)); + + std::pair<TxSpends::iterator, TxSpends::iterator> range; + range = mapTxSpends.equal_range(outpoint); + SyncMetaData(range); +} + + +void CWallet::AddToSpends(const uint256& wtxid) +{ + auto it = mapWallet.find(wtxid); + assert(it != mapWallet.end()); + CWalletTx& thisTx = it->second; + if (thisTx.IsCoinBase()) // Coinbases don't spend anything! + return; + + for (const CTxIn& txin : thisTx.tx->vin) + AddToSpends(txin.prevout, wtxid); +} + +bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) +{ + if (IsCrypted()) + return false; + + CKeyingMaterial _vMasterKey; + + _vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); + GetStrongRandBytes(&_vMasterKey[0], WALLET_CRYPTO_KEY_SIZE); + + CMasterKey kMasterKey; + + kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE); + GetStrongRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE); + + CCrypter crypter; + int64_t nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod); + kMasterKey.nDeriveIterations = static_cast<unsigned int>(2500000 / ((double)(GetTimeMillis() - nStartTime))); + + nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod); + kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + static_cast<unsigned int>(kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime)))) / 2; + + if (kMasterKey.nDeriveIterations < 25000) + kMasterKey.nDeriveIterations = 25000; + + LogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); + + if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod)) + return false; + if (!crypter.Encrypt(_vMasterKey, kMasterKey.vchCryptedKey)) + return false; + + { + LOCK(cs_wallet); + mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; + assert(!pwalletdbEncryption); + pwalletdbEncryption = new CWalletDB(*dbw); + if (!pwalletdbEncryption->TxnBegin()) { + delete pwalletdbEncryption; + pwalletdbEncryption = nullptr; + return false; + } + pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey); + + if (!EncryptKeys(_vMasterKey)) + { + pwalletdbEncryption->TxnAbort(); + delete pwalletdbEncryption; + // We now probably have half of our keys encrypted in memory, and half not... + // die and let the user reload the unencrypted wallet. + assert(false); + } + + // Encryption was introduced in version 0.4.0 + SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true); + + if (!pwalletdbEncryption->TxnCommit()) { + delete pwalletdbEncryption; + // We now have keys encrypted in memory, but not on disk... + // die to avoid confusion and let the user reload the unencrypted wallet. + assert(false); + } + + delete pwalletdbEncryption; + pwalletdbEncryption = nullptr; + + Lock(); + Unlock(strWalletPassphrase); + + // if we are using HD, replace the HD master key (seed) with a new one + if (IsHDEnabled()) { + if (!SetHDMasterKey(GenerateNewHDMasterKey())) { + return false; + } + } + + NewKeyPool(); + Lock(); + + // Need to completely rewrite the wallet file; if we don't, bdb might keep + // bits of the unencrypted private key in slack space in the database file. + dbw->Rewrite(); + + } + NotifyStatusChanged(this); + + return true; +} + +DBErrors CWallet::ReorderTransactions() +{ + LOCK(cs_wallet); + CWalletDB walletdb(*dbw); + + // 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 std::pair<CWalletTx*, CAccountingEntry*> TxPair; + typedef std::multimap<int64_t, TxPair > TxItems; + TxItems txByTime; + + for (auto& entry : mapWallet) + { + CWalletTx* wtx = &entry.second; + txByTime.insert(std::make_pair(wtx->nTimeReceived, TxPair(wtx, nullptr))); + } + std::list<CAccountingEntry> acentries; + walletdb.ListAccountCreditDebit("", acentries); + for (CAccountingEntry& entry : acentries) + { + txByTime.insert(std::make_pair(entry.nTime, TxPair(nullptr, &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 != nullptr) ? 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; + for (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) +{ + AssertLockHeld(cs_wallet); // nOrderPosNext + int64_t nRet = nOrderPosNext++; + if (pwalletdb) { + pwalletdb->WriteOrderPosNext(nOrderPosNext); + } else { + CWalletDB(*dbw).WriteOrderPosNext(nOrderPosNext); + } + return nRet; +} + +bool CWallet::AccountMove(std::string strFrom, std::string strTo, CAmount nAmount, std::string strComment) +{ + CWalletDB walletdb(*dbw); + if (!walletdb.TxnBegin()) + return false; + + int64_t nNow = GetAdjustedTime(); + + // Debit + CAccountingEntry debit; + debit.nOrderPos = IncOrderPosNext(&walletdb); + debit.strAccount = strFrom; + debit.nCreditDebit = -nAmount; + debit.nTime = nNow; + debit.strOtherAccount = strTo; + debit.strComment = strComment; + AddAccountingEntry(debit, &walletdb); + + // Credit + CAccountingEntry credit; + credit.nOrderPos = IncOrderPosNext(&walletdb); + credit.strAccount = strTo; + credit.nCreditDebit = nAmount; + credit.nTime = nNow; + credit.strOtherAccount = strFrom; + credit.strComment = strComment; + AddAccountingEntry(credit, &walletdb); + + if (!walletdb.TxnCommit()) + return false; + + return true; +} + +bool CWallet::GetAccountDestination(CTxDestination &dest, std::string strAccount, bool bForceNew) +{ + CWalletDB walletdb(*dbw); + + CAccount account; + walletdb.ReadAccount(strAccount, account); + + if (!bForceNew) { + if (!account.vchPubKey.IsValid()) + bForceNew = true; + else { + // Check if the current key has been used (TODO: check other addresses with the same key) + CScript scriptPubKey = GetScriptForDestination(GetDestinationForKey(account.vchPubKey, g_address_type)); + for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin(); + it != mapWallet.end() && account.vchPubKey.IsValid(); + ++it) + for (const CTxOut& txout : (*it).second.tx->vout) + if (txout.scriptPubKey == scriptPubKey) { + bForceNew = true; + break; + } + } + } + + // Generate a new key + if (bForceNew) { + if (!GetKeyFromPool(account.vchPubKey, false)) + return false; + + LearnRelatedScripts(account.vchPubKey, g_address_type); + dest = GetDestinationForKey(account.vchPubKey, g_address_type); + SetAddressBook(dest, strAccount, "receive"); + walletdb.WriteAccount(strAccount, account); + } else { + dest = GetDestinationForKey(account.vchPubKey, g_address_type); + } + + return true; +} + +void CWallet::MarkDirty() +{ + { + LOCK(cs_wallet); + for (std::pair<const uint256, CWalletTx>& item : mapWallet) + item.second.MarkDirty(); + } +} + +bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash) +{ + LOCK(cs_wallet); + + auto mi = mapWallet.find(originalHash); + + // There is a bug if MarkReplaced is not called on an existing wallet transaction. + assert(mi != mapWallet.end()); + + CWalletTx& wtx = (*mi).second; + + // Ensure for now that we're not overwriting data + assert(wtx.mapValue.count("replaced_by_txid") == 0); + + wtx.mapValue["replaced_by_txid"] = newHash.ToString(); + + CWalletDB walletdb(*dbw, "r+"); + + bool success = true; + if (!walletdb.WriteTx(wtx)) { + LogPrintf("%s: Updating walletdb tx %s failed", __func__, wtx.GetHash().ToString()); + success = false; + } + + NotifyTransactionChanged(this, originalHash, CT_UPDATED); + + return success; +} + +bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) +{ + LOCK(cs_wallet); + + CWalletDB walletdb(*dbw, "r+", fFlushOnClose); + + uint256 hash = wtxIn.GetHash(); + + // Inserts only if not already there, returns tx inserted or tx found + std::pair<std::map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.insert(std::make_pair(hash, wtxIn)); + CWalletTx& wtx = (*ret.first).second; + wtx.BindWallet(this); + bool fInsertedNew = ret.second; + if (fInsertedNew) + { + wtx.nTimeReceived = GetAdjustedTime(); + wtx.nOrderPos = IncOrderPosNext(&walletdb); + wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr))); + wtx.nTimeSmart = ComputeTimeSmart(wtx); + AddToSpends(hash); + } + + bool fUpdated = false; + if (!fInsertedNew) + { + // Merge + if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock) + { + wtx.hashBlock = wtxIn.hashBlock; + fUpdated = true; + } + // If no longer abandoned, update + if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned()) + { + wtx.hashBlock = wtxIn.hashBlock; + fUpdated = true; + } + if (wtxIn.nIndex != -1 && (wtxIn.nIndex != wtx.nIndex)) + { + wtx.nIndex = wtxIn.nIndex; + fUpdated = true; + } + if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) + { + wtx.fFromMe = wtxIn.fFromMe; + fUpdated = true; + } + // If we have a witness-stripped version of this transaction, and we + // see a new version with a witness, then we must be upgrading a pre-segwit + // wallet. Store the new version of the transaction with the witness, + // as the stripped-version must be invalid. + // TODO: Store all versions of the transaction, instead of just one. + if (wtxIn.tx->HasWitness() && !wtx.tx->HasWitness()) { + wtx.SetTx(wtxIn.tx); + fUpdated = true; + } + } + + //// debug print + LogPrintf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : "")); + + // Write to disk + if (fInsertedNew || fUpdated) + if (!walletdb.WriteTx(wtx)) + return false; + + // Break debit/credit balance caches: + wtx.MarkDirty(); + + // Notify UI of new or updated transaction + NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED); + + // notify an external script when a wallet transaction comes in or is updated + std::string strCmd = gArgs.GetArg("-walletnotify", ""); + + if (!strCmd.empty()) + { + boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex()); + std::thread t(runCommand, strCmd); + t.detach(); // thread runs free + } + + return true; +} + +bool CWallet::LoadToWallet(const CWalletTx& wtxIn) +{ + uint256 hash = wtxIn.GetHash(); + CWalletTx& wtx = mapWallet.emplace(hash, wtxIn).first->second; + wtx.BindWallet(this); + wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr))); + AddToSpends(hash); + for (const CTxIn& txin : wtx.tx->vin) { + auto it = mapWallet.find(txin.prevout.hash); + if (it != mapWallet.end()) { + CWalletTx& prevtx = it->second; + if (prevtx.nIndex == -1 && !prevtx.hashUnset()) { + MarkConflicted(prevtx.hashBlock, wtx.GetHash()); + } + } + } + + return true; +} + +/** + * Add a transaction to the wallet, or update it. pIndex and posInBlock should + * be set when the transaction was known to be included in a block. When + * pIndex == nullptr, then wallet state is not updated in AddToWallet, but + * notifications happen and cached balances are marked dirty. + * + * If fUpdate is true, existing transactions will be updated. + * TODO: One exception to this is that the abandoned state is cleared under the + * assumption that any further notification of a transaction that was considered + * abandoned is an indication that it is not safe to be considered abandoned. + * Abandoned state should probably be more carefully tracked via different + * posInBlock signals or by checking mempool presence when necessary. + */ +bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate) +{ + const CTransaction& tx = *ptx; + { + AssertLockHeld(cs_wallet); + + if (pIndex != nullptr) { + for (const CTxIn& txin : tx.vin) { + std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout); + while (range.first != range.second) { + if (range.first->second != tx.GetHash()) { + LogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), pIndex->GetBlockHash().ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); + MarkConflicted(pIndex->GetBlockHash(), range.first->second); + } + range.first++; + } + } + } + + bool fExisted = mapWallet.count(tx.GetHash()) != 0; + if (fExisted && !fUpdate) return false; + if (fExisted || IsMine(tx) || IsFromMe(tx)) + { + /* Check if any keys in the wallet keypool that were supposed to be unused + * have appeared in a new transaction. If so, remove those keys from the keypool. + * This can happen when restoring an old wallet backup that does not contain + * the mostly recently created transactions from newer versions of the wallet. + */ + + // loop though all outputs + for (const CTxOut& txout: tx.vout) { + // extract addresses and check if they match with an unused keypool key + std::vector<CKeyID> vAffected; + CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey); + for (const CKeyID &keyid : vAffected) { + std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid); + if (mi != m_pool_key_to_index.end()) { + LogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__); + MarkReserveKeysAsUsed(mi->second); + + if (!TopUpKeyPool()) { + LogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__); + } + } + } + } + + CWalletTx wtx(this, ptx); + + // Get merkle branch if transaction was found in a block + if (pIndex != nullptr) + wtx.SetMerkleBranch(pIndex, posInBlock); + + return AddToWallet(wtx, false); + } + } + return false; +} + +bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const +{ + LOCK2(cs_main, cs_wallet); + const CWalletTx* wtx = GetWalletTx(hashTx); + return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool(); +} + +bool CWallet::AbandonTransaction(const uint256& hashTx) +{ + LOCK2(cs_main, cs_wallet); + + CWalletDB walletdb(*dbw, "r+"); + + std::set<uint256> todo; + std::set<uint256> done; + + // Can't mark abandoned if confirmed or in mempool + auto it = mapWallet.find(hashTx); + assert(it != mapWallet.end()); + CWalletTx& origtx = it->second; + if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) { + return false; + } + + todo.insert(hashTx); + + while (!todo.empty()) { + uint256 now = *todo.begin(); + todo.erase(now); + done.insert(now); + auto it = mapWallet.find(now); + assert(it != mapWallet.end()); + CWalletTx& wtx = it->second; + int currentconfirm = wtx.GetDepthInMainChain(); + // If the orig tx was not in block, none of its spends can be + assert(currentconfirm <= 0); + // if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon} + if (currentconfirm == 0 && !wtx.isAbandoned()) { + // If the orig tx was not in block/mempool, none of its spends can be in mempool + assert(!wtx.InMempool()); + wtx.nIndex = -1; + wtx.setAbandoned(); + wtx.MarkDirty(); + walletdb.WriteTx(wtx); + NotifyTransactionChanged(this, wtx.GetHash(), CT_UPDATED); + // Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too + TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(hashTx, 0)); + while (iter != mapTxSpends.end() && iter->first.hash == now) { + if (!done.count(iter->second)) { + todo.insert(iter->second); + } + iter++; + } + // If a transaction changes 'conflicted' state, that changes the balance + // available of the outputs it spends. So force those to be recomputed + for (const CTxIn& txin : wtx.tx->vin) + { + auto it = mapWallet.find(txin.prevout.hash); + if (it != mapWallet.end()) { + it->second.MarkDirty(); + } + } + } + } + + return true; +} + +void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) +{ + LOCK2(cs_main, cs_wallet); + + int conflictconfirms = 0; + if (mapBlockIndex.count(hashBlock)) { + CBlockIndex* pindex = mapBlockIndex[hashBlock]; + if (chainActive.Contains(pindex)) { + conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1); + } + } + // If number of conflict confirms cannot be determined, this means + // that the block is still unknown or not yet part of the main chain, + // for example when loading the wallet during a reindex. Do nothing in that + // case. + if (conflictconfirms >= 0) + return; + + // Do not flush the wallet here for performance reasons + CWalletDB walletdb(*dbw, "r+", false); + + std::set<uint256> todo; + std::set<uint256> done; + + todo.insert(hashTx); + + while (!todo.empty()) { + uint256 now = *todo.begin(); + todo.erase(now); + done.insert(now); + auto it = mapWallet.find(now); + assert(it != mapWallet.end()); + CWalletTx& wtx = it->second; + int currentconfirm = wtx.GetDepthInMainChain(); + if (conflictconfirms < currentconfirm) { + // Block is 'more conflicted' than current confirm; update. + // Mark transaction as conflicted with this block. + wtx.nIndex = -1; + wtx.hashBlock = hashBlock; + wtx.MarkDirty(); + walletdb.WriteTx(wtx); + // Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too + TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); + while (iter != mapTxSpends.end() && iter->first.hash == now) { + if (!done.count(iter->second)) { + todo.insert(iter->second); + } + iter++; + } + // If a transaction changes 'conflicted' state, that changes the balance + // available of the outputs it spends. So force those to be recomputed + for (const CTxIn& txin : wtx.tx->vin) { + auto it = mapWallet.find(txin.prevout.hash); + if (it != mapWallet.end()) { + it->second.MarkDirty(); + } + } + } + } +} + +void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pindex, int posInBlock) { + const CTransaction& tx = *ptx; + + if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, true)) + return; // Not one of ours + + // If a transaction changes 'conflicted' state, that changes the balance + // available of the outputs it spends. So force those to be + // recomputed, also: + for (const CTxIn& txin : tx.vin) { + auto it = mapWallet.find(txin.prevout.hash); + if (it != mapWallet.end()) { + it->second.MarkDirty(); + } + } +} + +void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) { + LOCK2(cs_main, cs_wallet); + SyncTransaction(ptx); + + auto it = mapWallet.find(ptx->GetHash()); + if (it != mapWallet.end()) { + it->second.fInMempool = true; + } +} + +void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) { + LOCK(cs_wallet); + auto it = mapWallet.find(ptx->GetHash()); + if (it != mapWallet.end()) { + it->second.fInMempool = false; + } +} + +void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) { + LOCK2(cs_main, cs_wallet); + // TODO: Temporarily ensure that mempool removals are notified before + // connected transactions. This shouldn't matter, but the abandoned + // state of transactions in our wallet is currently cleared when we + // receive another notification and there is a race condition where + // notification of a connected conflict might cause an outside process + // to abandon a transaction and then have it inadvertently cleared by + // the notification that the conflicted transaction was evicted. + + for (const CTransactionRef& ptx : vtxConflicted) { + SyncTransaction(ptx); + TransactionRemovedFromMempool(ptx); + } + for (size_t i = 0; i < pblock->vtx.size(); i++) { + SyncTransaction(pblock->vtx[i], pindex, i); + TransactionRemovedFromMempool(pblock->vtx[i]); + } + + m_last_block_processed = pindex; +} + +void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) { + LOCK2(cs_main, cs_wallet); + + for (const CTransactionRef& ptx : pblock->vtx) { + SyncTransaction(ptx); + } +} + + + +void CWallet::BlockUntilSyncedToCurrentChain() { + AssertLockNotHeld(cs_main); + AssertLockNotHeld(cs_wallet); + + { + // Skip the queue-draining stuff if we know we're caught up with + // chainActive.Tip()... + // We could also take cs_wallet here, and call m_last_block_processed + // protected by cs_wallet instead of cs_main, but as long as we need + // cs_main here anyway, its easier to just call it cs_main-protected. + LOCK(cs_main); + const CBlockIndex* initialChainTip = chainActive.Tip(); + + if (m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) { + return; + } + } + + // ...otherwise put a callback in the validation interface queue and wait + // for the queue to drain enough to execute it (indicating we are caught up + // at least with the time we entered this function). + SyncWithValidationInterfaceQueue(); +} + + +isminetype CWallet::IsMine(const CTxIn &txin) const +{ + { + LOCK(cs_wallet); + std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash); + if (mi != mapWallet.end()) + { + const CWalletTx& prev = (*mi).second; + if (txin.prevout.n < prev.tx->vout.size()) + return IsMine(prev.tx->vout[txin.prevout.n]); + } + } + return ISMINE_NO; +} + +// Note that this function doesn't distinguish between a 0-valued input, +// and a not-"is mine" (according to the filter) input. +CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const +{ + { + LOCK(cs_wallet); + std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash); + if (mi != mapWallet.end()) + { + const CWalletTx& prev = (*mi).second; + if (txin.prevout.n < prev.tx->vout.size()) + if (IsMine(prev.tx->vout[txin.prevout.n]) & filter) + return prev.tx->vout[txin.prevout.n].nValue; + } + } + return 0; +} + +isminetype CWallet::IsMine(const CTxOut& txout) const +{ + return ::IsMine(*this, txout.scriptPubKey); +} + +CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) const +{ + if (!MoneyRange(txout.nValue)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + return ((IsMine(txout) & filter) ? txout.nValue : 0); +} + +bool CWallet::IsChange(const CTxOut& txout) const +{ + // TODO: fix handling of 'change' outputs. The assumption is that any + // payment to a script that is ours, but is not in the address book + // is change. That assumption is likely to break when we implement multisignature + // wallets that return change back into a multi-signature-protected address; + // a better way of identifying which outputs are 'the send' and which are + // 'the change' will need to be implemented (maybe extend CWalletTx to remember + // which output, if any, was change). + if (::IsMine(*this, txout.scriptPubKey)) + { + CTxDestination address; + if (!ExtractDestination(txout.scriptPubKey, address)) + return true; + + LOCK(cs_wallet); + if (!mapAddressBook.count(address)) + return true; + } + return false; +} + +CAmount CWallet::GetChange(const CTxOut& txout) const +{ + if (!MoneyRange(txout.nValue)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + return (IsChange(txout) ? txout.nValue : 0); +} + +bool CWallet::IsMine(const CTransaction& tx) const +{ + for (const CTxOut& txout : tx.vout) + if (IsMine(txout)) + return true; + return false; +} + +bool CWallet::IsFromMe(const CTransaction& tx) const +{ + return (GetDebit(tx, ISMINE_ALL) > 0); +} + +CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) const +{ + CAmount nDebit = 0; + for (const CTxIn& txin : tx.vin) + { + nDebit += GetDebit(txin, filter); + if (!MoneyRange(nDebit)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + } + return nDebit; +} + +bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const +{ + LOCK(cs_wallet); + + for (const CTxIn& txin : tx.vin) + { + auto mi = mapWallet.find(txin.prevout.hash); + if (mi == mapWallet.end()) + return false; // any unknown inputs can't be from us + + const CWalletTx& prev = (*mi).second; + + if (txin.prevout.n >= prev.tx->vout.size()) + return false; // invalid input! + + if (!(IsMine(prev.tx->vout[txin.prevout.n]) & filter)) + return false; + } + return true; +} + +CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const +{ + CAmount nCredit = 0; + for (const CTxOut& txout : tx.vout) + { + nCredit += GetCredit(txout, filter); + if (!MoneyRange(nCredit)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + } + return nCredit; +} + +CAmount CWallet::GetChange(const CTransaction& tx) const +{ + CAmount nChange = 0; + for (const CTxOut& txout : tx.vout) + { + nChange += GetChange(txout); + if (!MoneyRange(nChange)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + } + return nChange; +} + +CPubKey CWallet::GenerateNewHDMasterKey() +{ + CKey key; + key.MakeNewKey(true); + + int64_t nCreationTime = GetTime(); + CKeyMetadata metadata(nCreationTime); + + // calculate the pubkey + CPubKey pubkey = key.GetPubKey(); + assert(key.VerifyPubKey(pubkey)); + + // set the hd keypath to "m" -> Master, refers the masterkeyid to itself + metadata.hdKeypath = "m"; + metadata.hdMasterKeyID = pubkey.GetID(); + + { + LOCK(cs_wallet); + + // mem store the metadata + mapKeyMetadata[pubkey.GetID()] = metadata; + + // write the key&metadata to the database + if (!AddKeyPubKey(key, pubkey)) + throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed"); + } + + return pubkey; +} + +bool CWallet::SetHDMasterKey(const CPubKey& pubkey) +{ + LOCK(cs_wallet); + // store the keyid (hash160) together with + // the child index counter in the database + // as a hdchain object + CHDChain newHdChain; + newHdChain.nVersion = CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE; + newHdChain.masterKeyID = pubkey.GetID(); + SetHDChain(newHdChain, false); + + return true; +} + +bool CWallet::SetHDChain(const CHDChain& chain, bool memonly) +{ + LOCK(cs_wallet); + if (!memonly && !CWalletDB(*dbw).WriteHDChain(chain)) + throw std::runtime_error(std::string(__func__) + ": writing chain failed"); + + hdChain = chain; + return true; +} + +bool CWallet::IsHDEnabled() const +{ + return !hdChain.masterKeyID.IsNull(); +} + +int64_t CWalletTx::GetTxTime() const +{ + int64_t n = nTimeSmart; + return n ? n : nTimeReceived; +} + +int CWalletTx::GetRequestCount() const +{ + // Returns -1 if it wasn't being tracked + int nRequests = -1; + { + LOCK(pwallet->cs_wallet); + if (IsCoinBase()) + { + // Generated block + if (!hashUnset()) + { + std::map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock); + if (mi != pwallet->mapRequestCount.end()) + nRequests = (*mi).second; + } + } + else + { + // Did anyone request this transaction? + std::map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(GetHash()); + if (mi != pwallet->mapRequestCount.end()) + { + nRequests = (*mi).second; + + // How about the block it's in? + if (nRequests == 0 && !hashUnset()) + { + std::map<uint256, int>::const_iterator _mi = pwallet->mapRequestCount.find(hashBlock); + if (_mi != pwallet->mapRequestCount.end()) + nRequests = (*_mi).second; + else + nRequests = 1; // If it's in someone else's block it must have got out + } + } + } + } + return nRequests; +} + +void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived, + std::list<COutputEntry>& listSent, CAmount& nFee, std::string& strSentAccount, const isminefilter& filter) const +{ + nFee = 0; + listReceived.clear(); + listSent.clear(); + strSentAccount = strFromAccount; + + // Compute fee: + CAmount nDebit = GetDebit(filter); + if (nDebit > 0) // debit>0 means we signed/sent this transaction + { + CAmount nValueOut = tx->GetValueOut(); + nFee = nDebit - nValueOut; + } + + // Sent/received. + for (unsigned int i = 0; i < tx->vout.size(); ++i) + { + const CTxOut& txout = tx->vout[i]; + isminetype fIsMine = pwallet->IsMine(txout); + // Only need to handle txouts if AT LEAST one of these is true: + // 1) they debit from us (sent) + // 2) the output is to us (received) + if (nDebit > 0) + { + // Don't report 'change' txouts + if (pwallet->IsChange(txout)) + continue; + } + else if (!(fIsMine & filter)) + continue; + + // In either case, we need to get the destination address + CTxDestination address; + + if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable()) + { + LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", + this->GetHash().ToString()); + address = CNoDestination(); + } + + COutputEntry output = {address, txout.nValue, (int)i}; + + // If we are debited by the transaction, add the output as a "sent" entry + if (nDebit > 0) + listSent.push_back(output); + + // If we are receiving the output, add it as a "received" entry + if (fIsMine & filter) + listReceived.push_back(output); + } + +} + +/** + * Scan active chain for relevant transactions after importing keys. This should + * be called whenever new keys are added to the wallet, with the oldest key + * creation time. + * + * @return Earliest timestamp that could be successfully scanned from. Timestamp + * returned will be higher than startTime if relevant blocks could not be read. + */ +int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update) +{ + // Find starting block. May be null if nCreateTime is greater than the + // highest blockchain timestamp, in which case there is nothing that needs + // to be scanned. + CBlockIndex* startBlock = nullptr; + { + LOCK(cs_main); + startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); + LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); + } + + if (startBlock) { + const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, reserver, update); + if (failedBlock) { + return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1; + } + } + return startTime; +} + +/** + * Scan the block chain (starting in pindexStart) for transactions + * from or to us. If fUpdate is true, found transactions that already + * exist in the wallet will be updated. + * + * Returns null if scan was successful. Otherwise, if a complete rescan was not + * possible (due to pruning or corruption), returns pointer to the most recent + * block that could not be scanned. + * + * If pindexStop is not a nullptr, the scan will stop at the block-index + * defined by pindexStop + * + * Caller needs to make sure pindexStop (and the optional pindexStart) are on + * the main chain after to the addition of any new keys you want to detect + * transactions for. + */ +CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver &reserver, bool fUpdate) +{ + int64_t nNow = GetTime(); + const CChainParams& chainParams = Params(); + + assert(reserver.isReserved()); + if (pindexStop) { + assert(pindexStop->nHeight >= pindexStart->nHeight); + } + + CBlockIndex* pindex = pindexStart; + CBlockIndex* ret = nullptr; + { + fAbortRescan = false; + ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup + CBlockIndex* tip = nullptr; + double dProgressStart; + double dProgressTip; + { + LOCK(cs_main); + tip = chainActive.Tip(); + dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex); + dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip); + } + while (pindex && !fAbortRescan) + { + if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) { + double gvp = 0; + { + LOCK(cs_main); + gvp = GuessVerificationProgress(chainParams.TxData(), pindex); + } + ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((gvp - dProgressStart) / (dProgressTip - dProgressStart) * 100)))); + } + if (GetTime() >= nNow + 60) { + nNow = GetTime(); + LOCK(cs_main); + LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex)); + } + + CBlock block; + if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) { + LOCK2(cs_main, cs_wallet); + if (pindex && !chainActive.Contains(pindex)) { + // Abort scan if current block is no longer active, to prevent + // marking transactions as coming from the wrong block. + ret = pindex; + break; + } + for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { + AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate); + } + } else { + ret = pindex; + } + if (pindex == pindexStop) { + break; + } + { + LOCK(cs_main); + pindex = chainActive.Next(pindex); + if (tip != chainActive.Tip()) { + tip = chainActive.Tip(); + // in case the tip has changed, update progress max + dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip); + } + } + } + if (pindex && fAbortRescan) { + LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex)); + } + ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI + } + return ret; +} + +void CWallet::ReacceptWalletTransactions() +{ + // If transactions aren't being broadcasted, don't let them into local mempool either + if (!fBroadcastTransactions) + return; + LOCK2(cs_main, cs_wallet); + std::map<int64_t, CWalletTx*> mapSorted; + + // Sort pending wallet transactions based on their initial wallet insertion order + for (std::pair<const uint256, CWalletTx>& item : mapWallet) + { + const uint256& wtxid = item.first; + CWalletTx& wtx = item.second; + assert(wtx.GetHash() == wtxid); + + int nDepth = wtx.GetDepthInMainChain(); + + if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { + mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); + } + } + + // Try to add wallet transactions to memory pool + for (std::pair<const int64_t, CWalletTx*>& item : mapSorted) { + CWalletTx& wtx = *(item.second); + CValidationState state; + wtx.AcceptToMemoryPool(maxTxFee, state); + } +} + +bool CWalletTx::RelayWalletTransaction(CConnman* connman) +{ + assert(pwallet->GetBroadcastTransactions()); + if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain() == 0) + { + CValidationState state; + /* GetDepthInMainChain already catches known conflicts. */ + if (InMempool() || AcceptToMemoryPool(maxTxFee, state)) { + LogPrintf("Relaying wtx %s\n", GetHash().ToString()); + if (connman) { + CInv inv(MSG_TX, GetHash()); + connman->ForEachNode([&inv](CNode* pnode) + { + pnode->PushInventory(inv); + }); + return true; + } + } + } + return false; +} + +std::set<uint256> CWalletTx::GetConflicts() const +{ + std::set<uint256> result; + if (pwallet != nullptr) + { + uint256 myHash = GetHash(); + result = pwallet->GetConflicts(myHash); + result.erase(myHash); + } + return result; +} + +CAmount CWalletTx::GetDebit(const isminefilter& filter) const +{ + if (tx->vin.empty()) + return 0; + + CAmount debit = 0; + if(filter & ISMINE_SPENDABLE) + { + if (fDebitCached) + debit += nDebitCached; + else + { + nDebitCached = pwallet->GetDebit(*tx, ISMINE_SPENDABLE); + fDebitCached = true; + debit += nDebitCached; + } + } + if(filter & ISMINE_WATCH_ONLY) + { + if(fWatchDebitCached) + debit += nWatchDebitCached; + else + { + nWatchDebitCached = pwallet->GetDebit(*tx, ISMINE_WATCH_ONLY); + fWatchDebitCached = true; + debit += nWatchDebitCached; + } + } + return debit; +} + +CAmount CWalletTx::GetCredit(const isminefilter& filter) const +{ + // Must wait until coinbase is safely deep enough in the chain before valuing it + if (IsCoinBase() && GetBlocksToMaturity() > 0) + return 0; + + CAmount credit = 0; + if (filter & ISMINE_SPENDABLE) + { + // GetBalance can assume transactions in mapWallet won't change + if (fCreditCached) + credit += nCreditCached; + else + { + nCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE); + fCreditCached = true; + credit += nCreditCached; + } + } + if (filter & ISMINE_WATCH_ONLY) + { + if (fWatchCreditCached) + credit += nWatchCreditCached; + else + { + nWatchCreditCached = pwallet->GetCredit(*tx, ISMINE_WATCH_ONLY); + fWatchCreditCached = true; + credit += nWatchCreditCached; + } + } + return credit; +} + +CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const +{ + if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain()) + { + if (fUseCache && fImmatureCreditCached) + return nImmatureCreditCached; + nImmatureCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE); + fImmatureCreditCached = true; + return nImmatureCreditCached; + } + + return 0; +} + +CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const +{ + if (pwallet == nullptr) + return 0; + + // Must wait until coinbase is safely deep enough in the chain before valuing it + if (IsCoinBase() && GetBlocksToMaturity() > 0) + return 0; + + if (fUseCache && fAvailableCreditCached) + return nAvailableCreditCached; + + CAmount nCredit = 0; + uint256 hashTx = GetHash(); + for (unsigned int i = 0; i < tx->vout.size(); i++) + { + if (!pwallet->IsSpent(hashTx, i)) + { + const CTxOut &txout = tx->vout[i]; + nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); + if (!MoneyRange(nCredit)) + throw std::runtime_error(std::string(__func__) + " : value out of range"); + } + } + + nAvailableCreditCached = nCredit; + fAvailableCreditCached = true; + return nCredit; +} + +CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const +{ + if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain()) + { + if (fUseCache && fImmatureWatchCreditCached) + return nImmatureWatchCreditCached; + nImmatureWatchCreditCached = pwallet->GetCredit(*tx, ISMINE_WATCH_ONLY); + fImmatureWatchCreditCached = true; + return nImmatureWatchCreditCached; + } + + return 0; +} + +CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool fUseCache) const +{ + if (pwallet == nullptr) + return 0; + + // Must wait until coinbase is safely deep enough in the chain before valuing it + if (IsCoinBase() && GetBlocksToMaturity() > 0) + return 0; + + if (fUseCache && fAvailableWatchCreditCached) + return nAvailableWatchCreditCached; + + CAmount nCredit = 0; + for (unsigned int i = 0; i < tx->vout.size(); i++) + { + if (!pwallet->IsSpent(GetHash(), i)) + { + const CTxOut &txout = tx->vout[i]; + nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY); + if (!MoneyRange(nCredit)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + } + } + + nAvailableWatchCreditCached = nCredit; + fAvailableWatchCreditCached = true; + return nCredit; +} + +CAmount CWalletTx::GetChange() const +{ + if (fChangeCached) + return nChangeCached; + nChangeCached = pwallet->GetChange(*tx); + fChangeCached = true; + return nChangeCached; +} + +bool CWalletTx::InMempool() const +{ + return fInMempool; +} + +bool CWalletTx::IsTrusted() const +{ + // Quick answer in most cases + if (!CheckFinalTx(*tx)) + return false; + int nDepth = GetDepthInMainChain(); + if (nDepth >= 1) + return true; + if (nDepth < 0) + return false; + if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit + return false; + + // Don't trust unconfirmed transactions from us unless they are in the mempool. + if (!InMempool()) + return false; + + // Trusted if all inputs are from us and are in the mempool: + for (const CTxIn& txin : tx->vin) + { + // Transactions not sent by us: not trusted + const CWalletTx* parent = pwallet->GetWalletTx(txin.prevout.hash); + if (parent == nullptr) + return false; + const CTxOut& parentOut = parent->tx->vout[txin.prevout.n]; + if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE) + return false; + } + return true; +} + +bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const +{ + CMutableTransaction tx1 = *this->tx; + CMutableTransaction tx2 = *_tx.tx; + for (auto& txin : tx1.vin) txin.scriptSig = CScript(); + for (auto& txin : tx2.vin) txin.scriptSig = CScript(); + return CTransaction(tx1) == CTransaction(tx2); +} + +std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman) +{ + std::vector<uint256> result; + + LOCK(cs_wallet); + + // Sort them in chronological order + std::multimap<unsigned int, CWalletTx*> mapSorted; + for (std::pair<const uint256, CWalletTx>& item : mapWallet) + { + CWalletTx& wtx = item.second; + // Don't rebroadcast if newer than nTime: + if (wtx.nTimeReceived > nTime) + continue; + mapSorted.insert(std::make_pair(wtx.nTimeReceived, &wtx)); + } + for (std::pair<const unsigned int, CWalletTx*>& item : mapSorted) + { + CWalletTx& wtx = *item.second; + if (wtx.RelayWalletTransaction(connman)) + result.push_back(wtx.GetHash()); + } + return result; +} + +void CWallet::ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) +{ + // Do this infrequently and randomly to avoid giving away + // that these are our transactions. + if (GetTime() < nNextResend || !fBroadcastTransactions) + return; + bool fFirst = (nNextResend == 0); + nNextResend = GetTime() + GetRand(30 * 60); + if (fFirst) + return; + + // Only do it if there's been a new block since last time + if (nBestBlockTime < nLastResend) + return; + nLastResend = GetTime(); + + // Rebroadcast unconfirmed txes older than 5 minutes before the last + // block was found: + std::vector<uint256> relayed = ResendWalletTransactionsBefore(nBestBlockTime-5*60, connman); + if (!relayed.empty()) + LogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size()); +} + +/** @} */ // end of mapWallet + + + + +/** @defgroup Actions + * + * @{ + */ + + +CAmount CWallet::GetBalance() const +{ + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (const auto& entry : mapWallet) + { + const CWalletTx* pcoin = &entry.second; + if (pcoin->IsTrusted()) + nTotal += pcoin->GetAvailableCredit(); + } + } + + return nTotal; +} + +CAmount CWallet::GetUnconfirmedBalance() const +{ + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (const auto& entry : mapWallet) + { + const CWalletTx* pcoin = &entry.second; + if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) + nTotal += pcoin->GetAvailableCredit(); + } + } + return nTotal; +} + +CAmount CWallet::GetImmatureBalance() const +{ + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (const auto& entry : mapWallet) + { + const CWalletTx* pcoin = &entry.second; + nTotal += pcoin->GetImmatureCredit(); + } + } + return nTotal; +} + +CAmount CWallet::GetWatchOnlyBalance() const +{ + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (const auto& entry : mapWallet) + { + const CWalletTx* pcoin = &entry.second; + if (pcoin->IsTrusted()) + nTotal += pcoin->GetAvailableWatchOnlyCredit(); + } + } + + return nTotal; +} + +CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const +{ + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (const auto& entry : mapWallet) + { + const CWalletTx* pcoin = &entry.second; + if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) + nTotal += pcoin->GetAvailableWatchOnlyCredit(); + } + } + return nTotal; +} + +CAmount CWallet::GetImmatureWatchOnlyBalance() const +{ + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (const auto& entry : mapWallet) + { + const CWalletTx* pcoin = &entry.second; + nTotal += pcoin->GetImmatureWatchOnlyCredit(); + } + } + return nTotal; +} + +// Calculate total balance in a different way from GetBalance. The biggest +// difference is that GetBalance sums up all unspent TxOuts paying to the +// wallet, while this sums up both spent and unspent TxOuts paying to the +// wallet, and then subtracts the values of TxIns spending from the wallet. This +// also has fewer restrictions on which unconfirmed transactions are considered +// trusted. +CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const +{ + LOCK2(cs_main, cs_wallet); + + CAmount balance = 0; + for (const auto& entry : mapWallet) { + const CWalletTx& wtx = entry.second; + const int depth = wtx.GetDepthInMainChain(); + if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.GetBlocksToMaturity() > 0) { + continue; + } + + // Loop through tx outputs and add incoming payments. For outgoing txs, + // treat change outputs specially, as part of the amount debited. + CAmount debit = wtx.GetDebit(filter); + const bool outgoing = debit > 0; + for (const CTxOut& out : wtx.tx->vout) { + if (outgoing && IsChange(out)) { + debit -= out.nValue; + } else if (IsMine(out) & filter && depth >= minDepth && (!account || *account == GetAccountName(out.scriptPubKey))) { + balance += out.nValue; + } + } + + // For outgoing txs, subtract amount debited. + if (outgoing && (!account || *account == wtx.strFromAccount)) { + balance -= debit; + } + } + + if (account) { + balance += CWalletDB(*dbw).GetAccountCreditDebit(*account); + } + + return balance; +} + +CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const +{ + LOCK2(cs_main, cs_wallet); + + CAmount balance = 0; + std::vector<COutput> vCoins; + AvailableCoins(vCoins, true, coinControl); + for (const COutput& out : vCoins) { + if (out.fSpendable) { + balance += out.tx->tx->vout[out.i].nValue; + } + } + return balance; +} + +void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const +{ + AssertLockHeld(cs_main); + AssertLockHeld(cs_wallet); + + vCoins.clear(); + CAmount nTotal = 0; + + for (const auto& entry : mapWallet) + { + const uint256& wtxid = entry.first; + const CWalletTx* pcoin = &entry.second; + + if (!CheckFinalTx(*pcoin->tx)) + continue; + + if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) + continue; + + int nDepth = pcoin->GetDepthInMainChain(); + if (nDepth < 0) + continue; + + // We should not consider coins which aren't at least in our mempool + // It's possible for these to be conflicted via ancestors which we may never be able to detect + if (nDepth == 0 && !pcoin->InMempool()) + continue; + + bool safeTx = pcoin->IsTrusted(); + + // We should not consider coins from transactions that are replacing + // other transactions. + // + // Example: There is a transaction A which is replaced by bumpfee + // transaction B. In this case, we want to prevent creation of + // a transaction B' which spends an output of B. + // + // Reason: If transaction A were initially confirmed, transactions B + // and B' would no longer be valid, so the user would have to create + // a new transaction C to replace B'. However, in the case of a + // one-block reorg, transactions B' and C might BOTH be accepted, + // when the user only wanted one of them. Specifically, there could + // be a 1-block reorg away from the chain where transactions A and C + // were accepted to another chain where B, B', and C were all + // accepted. + if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) { + safeTx = false; + } + + // Similarly, we should not consider coins from transactions that + // have been replaced. In the example above, we would want to prevent + // creation of a transaction A' spending an output of A, because if + // transaction B were initially confirmed, conflicting with A and + // A', we wouldn't want to the user to create a transaction D + // intending to replace A', but potentially resulting in a scenario + // where A, A', and D could all be accepted (instead of just B and + // D, or just A and A' like the user would want). + if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) { + safeTx = false; + } + + if (fOnlySafe && !safeTx) { + continue; + } + + if (nDepth < nMinDepth || nDepth > nMaxDepth) + continue; + + for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) { + if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount) + continue; + + if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i))) + continue; + + if (IsLockedCoin(entry.first, i)) + continue; + + if (IsSpent(wtxid, i)) + continue; + + isminetype mine = IsMine(pcoin->tx->vout[i]); + + if (mine == ISMINE_NO) { + continue; + } + + bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO); + bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO; + + vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx)); + + // Checks the sum amount of all UTXO's. + if (nMinimumSumAmount != MAX_MONEY) { + nTotal += pcoin->tx->vout[i].nValue; + + if (nTotal >= nMinimumSumAmount) { + return; + } + } + + // Checks the maximum number of UTXO's. + if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) { + return; + } + } + } +} + +std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const +{ + // TODO: Add AssertLockHeld(cs_wallet) here. + // + // Because the return value from this function contains pointers to + // CWalletTx objects, callers to this function really should acquire the + // cs_wallet lock before calling it. However, the current caller doesn't + // acquire this lock yet. There was an attempt to add the missing lock in + // https://github.com/bitcoin/bitcoin/pull/10340, but that change has been + // postponed until after https://github.com/bitcoin/bitcoin/pull/10244 to + // avoid adding some extra complexity to the Qt code. + + std::map<CTxDestination, std::vector<COutput>> result; + std::vector<COutput> availableCoins; + + LOCK2(cs_main, cs_wallet); + AvailableCoins(availableCoins); + + for (auto& coin : availableCoins) { + CTxDestination address; + if (coin.fSpendable && + ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) { + result[address].emplace_back(std::move(coin)); + } + } + + std::vector<COutPoint> lockedCoins; + ListLockedCoins(lockedCoins); + for (const auto& output : lockedCoins) { + auto it = mapWallet.find(output.hash); + if (it != mapWallet.end()) { + int depth = it->second.GetDepthInMainChain(); + if (depth >= 0 && output.n < it->second.tx->vout.size() && + IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) { + CTxDestination address; + if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { + result[address].emplace_back( + &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); + } + } + } + } + + return result; +} + +const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int output) const +{ + const CTransaction* ptx = &tx; + int n = output; + while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) { + const COutPoint& prevout = ptx->vin[0].prevout; + auto it = mapWallet.find(prevout.hash); + if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.n || + !IsMine(it->second.tx->vout[prevout.n])) { + break; + } + ptx = it->second.tx.get(); + n = prevout.n; + } + return ptx->vout[n]; +} + +static void ApproximateBestSubset(const std::vector<CInputCoin>& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue, + std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000) +{ + std::vector<char> vfIncluded; + + vfBest.assign(vValue.size(), true); + nBest = nTotalLower; + + FastRandomContext insecure_rand; + + for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++) + { + vfIncluded.assign(vValue.size(), false); + CAmount nTotal = 0; + bool fReachedTarget = false; + for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) + { + for (unsigned int i = 0; i < vValue.size(); i++) + { + //The solver here uses a randomized algorithm, + //the randomness serves no real security purpose but is just + //needed to prevent degenerate behavior and it is important + //that the rng is fast. We do not use a constant random sequence, + //because there may be some privacy improvement by making + //the selection random. + if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i]) + { + nTotal += vValue[i].txout.nValue; + vfIncluded[i] = true; + if (nTotal >= nTargetValue) + { + fReachedTarget = true; + if (nTotal < nBest) + { + nBest = nTotal; + vfBest = vfIncluded; + } + nTotal -= vValue[i].txout.nValue; + vfIncluded[i] = false; + } + } + } + } + } +} + +bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors, std::vector<COutput> vCoins, + std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet) const +{ + setCoinsRet.clear(); + nValueRet = 0; + + // List of values less than target + boost::optional<CInputCoin> coinLowestLarger; + std::vector<CInputCoin> vValue; + CAmount nTotalLower = 0; + + random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt); + + for (const COutput &output : vCoins) + { + if (!output.fSpendable) + continue; + + const CWalletTx *pcoin = output.tx; + + if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs)) + continue; + + if (!mempool.TransactionWithinChainLimit(pcoin->GetHash(), nMaxAncestors)) + continue; + + int i = output.i; + + CInputCoin coin = CInputCoin(pcoin, i); + + if (coin.txout.nValue == nTargetValue) + { + setCoinsRet.insert(coin); + nValueRet += coin.txout.nValue; + return true; + } + else if (coin.txout.nValue < nTargetValue + MIN_CHANGE) + { + vValue.push_back(coin); + nTotalLower += coin.txout.nValue; + } + else if (!coinLowestLarger || coin.txout.nValue < coinLowestLarger->txout.nValue) + { + coinLowestLarger = coin; + } + } + + if (nTotalLower == nTargetValue) + { + for (const auto& input : vValue) + { + setCoinsRet.insert(input); + nValueRet += input.txout.nValue; + } + return true; + } + + if (nTotalLower < nTargetValue) + { + if (!coinLowestLarger) + return false; + setCoinsRet.insert(coinLowestLarger.get()); + nValueRet += coinLowestLarger->txout.nValue; + return true; + } + + // Solve subset sum by stochastic approximation + std::sort(vValue.begin(), vValue.end(), CompareValueOnly()); + std::reverse(vValue.begin(), vValue.end()); + std::vector<char> vfBest; + CAmount nBest; + + ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest); + if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) + ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest); + + // If we have a bigger coin and (either the stochastic approximation didn't find a good solution, + // or the next bigger coin is closer), return the bigger coin + if (coinLowestLarger && + ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger->txout.nValue <= nBest)) + { + setCoinsRet.insert(coinLowestLarger.get()); + nValueRet += coinLowestLarger->txout.nValue; + } + else { + for (unsigned int i = 0; i < vValue.size(); i++) + if (vfBest[i]) + { + setCoinsRet.insert(vValue[i]); + nValueRet += vValue[i].txout.nValue; + } + + if (LogAcceptCategory(BCLog::SELECTCOINS)) { + LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: "); + for (unsigned int i = 0; i < vValue.size(); i++) { + if (vfBest[i]) { + LogPrint(BCLog::SELECTCOINS, "%s ", FormatMoney(vValue[i].txout.nValue)); + } + } + LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest)); + } + } + + return true; +} + +bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const +{ + std::vector<COutput> vCoins(vAvailableCoins); + + // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) + if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs) + { + for (const COutput& out : vCoins) + { + if (!out.fSpendable) + continue; + nValueRet += out.tx->tx->vout[out.i].nValue; + setCoinsRet.insert(CInputCoin(out.tx, out.i)); + } + return (nValueRet >= nTargetValue); + } + + // calculate value from preset inputs and store them + std::set<CInputCoin> setPresetCoins; + CAmount nValueFromPresetInputs = 0; + + std::vector<COutPoint> vPresetInputs; + if (coinControl) + coinControl->ListSelected(vPresetInputs); + for (const COutPoint& outpoint : vPresetInputs) + { + std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash); + if (it != mapWallet.end()) + { + const CWalletTx* pcoin = &it->second; + // Clearly invalid input, fail + if (pcoin->tx->vout.size() <= outpoint.n) + return false; + nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue; + setPresetCoins.insert(CInputCoin(pcoin, outpoint.n)); + } else + return false; // TODO: Allow non-wallet inputs + } + + // remove preset inputs from vCoins + for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();) + { + if (setPresetCoins.count(CInputCoin(it->tx, it->i))) + it = vCoins.erase(it); + else + ++it; + } + + size_t nMaxChainLength = std::min(gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT), gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)); + bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); + + bool res = nTargetValue <= nValueFromPresetInputs || + SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 6, 0, vCoins, setCoinsRet, nValueRet) || + SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 1, 0, vCoins, setCoinsRet, nValueRet) || + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, 2, vCoins, setCoinsRet, nValueRet)) || + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, std::min((size_t)4, nMaxChainLength/3), vCoins, setCoinsRet, nValueRet)) || + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nMaxChainLength/2, vCoins, setCoinsRet, nValueRet)) || + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nMaxChainLength, vCoins, setCoinsRet, nValueRet)) || + (bSpendZeroConfChange && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, std::numeric_limits<uint64_t>::max(), vCoins, setCoinsRet, nValueRet)); + + // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset + setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end()); + + // add preset inputs to the total value selected + nValueRet += nValueFromPresetInputs; + + return res; +} + +bool CWallet::SignTransaction(CMutableTransaction &tx) +{ + AssertLockHeld(cs_wallet); // mapWallet + + // sign the new tx + CTransaction txNewConst(tx); + int nIn = 0; + for (const auto& input : tx.vin) { + std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash); + if(mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) { + return false; + } + const CScript& scriptPubKey = mi->second.tx->vout[input.prevout.n].scriptPubKey; + const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue; + SignatureData sigdata; + if (!ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) { + return false; + } + UpdateTransaction(tx, nIn, sigdata); + nIn++; + } + return true; +} + +bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) +{ + std::vector<CRecipient> vecSend; + + // Turn the txout set into a CRecipient vector. + for (size_t idx = 0; idx < tx.vout.size(); idx++) { + const CTxOut& txOut = tx.vout[idx]; + CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1}; + vecSend.push_back(recipient); + } + + coinControl.fAllowOtherInputs = true; + + for (const CTxIn& txin : tx.vin) { + coinControl.Select(txin.prevout); + } + + // Acquire the locks to prevent races to the new locked unspents between the + // CreateTransaction call and LockCoin calls (when lockUnspents is true). + LOCK2(cs_main, cs_wallet); + + CReserveKey reservekey(this); + CWalletTx wtx; + if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { + return false; + } + + if (nChangePosInOut != -1) { + tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]); + // We don't have the normal Create/Commit cycle, and don't want to risk + // reusing change, so just remove the key from the keypool here. + reservekey.KeepKey(); + } + + // Copy output sizes from new transaction; they may have had the fee + // subtracted from them. + for (unsigned int idx = 0; idx < tx.vout.size(); idx++) { + tx.vout[idx].nValue = wtx.tx->vout[idx].nValue; + } + + // Add new txins while keeping original txin scriptSig/order. + for (const CTxIn& txin : wtx.tx->vin) { + if (!coinControl.IsSelected(txin.prevout)) { + tx.vin.push_back(txin); + + if (lockUnspents) { + LockCoin(txin.prevout); + } + } + } + + return true; +} + +OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend) +{ + // If -changetype is specified, always use that change type. + if (change_type != OUTPUT_TYPE_NONE) { + return change_type; + } + + // if g_address_type is legacy, use legacy address as change (even + // if some of the outputs are P2WPKH or P2WSH). + if (g_address_type == OUTPUT_TYPE_LEGACY) { + return OUTPUT_TYPE_LEGACY; + } + + // if any destination is P2WPKH or P2WSH, use P2WPKH for the change + // output. + for (const auto& recipient : vecSend) { + // Check if any destination contains a witness program: + int witnessversion = 0; + std::vector<unsigned char> witnessprogram; + if (recipient.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) { + return OUTPUT_TYPE_BECH32; + } + } + + // else use g_address_type for change + return g_address_type; +} + +bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, + int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) +{ + CAmount nValue = 0; + int nChangePosRequest = nChangePosInOut; + unsigned int nSubtractFeeFromAmount = 0; + for (const auto& recipient : vecSend) + { + if (nValue < 0 || recipient.nAmount < 0) + { + strFailReason = _("Transaction amounts must not be negative"); + return false; + } + nValue += recipient.nAmount; + + if (recipient.fSubtractFeeFromAmount) + nSubtractFeeFromAmount++; + } + if (vecSend.empty()) + { + strFailReason = _("Transaction must have at least one recipient"); + return false; + } + + wtxNew.fTimeReceivedIsTxTime = true; + wtxNew.BindWallet(this); + CMutableTransaction txNew; + + // Discourage fee sniping. + // + // For a large miner the value of the transactions in the best block and + // the mempool can exceed the cost of deliberately attempting to mine two + // blocks to orphan the current best block. By setting nLockTime such that + // only the next block can include the transaction, we discourage this + // practice as the height restricted and limited blocksize gives miners + // considering fee sniping fewer options for pulling off this attack. + // + // A simple way to think about this is from the wallet's point of view we + // always want the blockchain to move forward. By setting nLockTime this + // way we're basically making the statement that we only want this + // transaction to appear in the next block; we don't want to potentially + // encourage reorgs by allowing transactions to appear at lower heights + // than the next block in forks of the best chain. + // + // Of course, the subsidy is high enough, and transaction volume low + // enough, that fee sniping isn't a problem yet, but by implementing a fix + // now we ensure code won't be written that makes assumptions about + // nLockTime that preclude a fix later. + txNew.nLockTime = chainActive.Height(); + + // Secondly occasionally randomly pick a nLockTime even further back, so + // that transactions that are delayed after signing for whatever reason, + // e.g. high-latency mix networks and some CoinJoin implementations, have + // better privacy. + if (GetRandInt(10) == 0) + txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100)); + + assert(txNew.nLockTime <= (unsigned int)chainActive.Height()); + assert(txNew.nLockTime < LOCKTIME_THRESHOLD); + FeeCalculation feeCalc; + CAmount nFeeNeeded; + unsigned int nBytes; + { + std::set<CInputCoin> setCoins; + LOCK2(cs_main, cs_wallet); + { + std::vector<COutput> vAvailableCoins; + AvailableCoins(vAvailableCoins, true, &coin_control); + + // Create change script that will be used if we need change + // TODO: pass in scriptChange instead of reservekey so + // change transaction isn't always pay-to-bitcoin-address + CScript scriptChange; + + // coin control: send change to custom address + if (!boost::get<CNoDestination>(&coin_control.destChange)) { + scriptChange = GetScriptForDestination(coin_control.destChange); + } else { // no coin control: send change to newly generated address + // Note: We use a new key here to keep it from being obvious which side is the change. + // The drawback is that by not reusing a previous key, the change may be lost if a + // backup is restored, if the backup doesn't have the new private key for the change. + // If we reused the old key, it would be possible to add code to look for and + // rediscover unknown transactions that were written with keys of ours to recover + // post-backup change. + + // Reserve a new key pair from key pool + CPubKey vchPubKey; + bool ret; + ret = reservekey.GetReservedKey(vchPubKey, true); + if (!ret) + { + strFailReason = _("Keypool ran out, please call keypoolrefill first"); + return false; + } + + const OutputType change_type = TransactionChangeType(coin_control.change_type, vecSend); + + LearnRelatedScripts(vchPubKey, change_type); + scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, change_type)); + } + CTxOut change_prototype_txout(0, scriptChange); + size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0); + + CFeeRate discard_rate = GetDiscardRate(::feeEstimator); + nFeeRet = 0; + bool pick_new_inputs = true; + CAmount nValueIn = 0; + // Start with no fee and loop until there is enough fee + while (true) + { + nChangePosInOut = nChangePosRequest; + txNew.vin.clear(); + txNew.vout.clear(); + wtxNew.fFromMe = true; + bool fFirst = true; + + CAmount nValueToSelect = nValue; + if (nSubtractFeeFromAmount == 0) + nValueToSelect += nFeeRet; + // vouts to the payees + for (const auto& recipient : vecSend) + { + CTxOut txout(recipient.nAmount, recipient.scriptPubKey); + + if (recipient.fSubtractFeeFromAmount) + { + assert(nSubtractFeeFromAmount != 0); + txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient + + if (fFirst) // first receiver pays the remainder not divisible by output count + { + fFirst = false; + txout.nValue -= nFeeRet % nSubtractFeeFromAmount; + } + } + + if (IsDust(txout, ::dustRelayFee)) + { + if (recipient.fSubtractFeeFromAmount && nFeeRet > 0) + { + if (txout.nValue < 0) + strFailReason = _("The transaction amount is too small to pay the fee"); + else + strFailReason = _("The transaction amount is too small to send after the fee has been deducted"); + } + else + strFailReason = _("Transaction amount too small"); + return false; + } + txNew.vout.push_back(txout); + } + + // Choose coins to use + if (pick_new_inputs) { + nValueIn = 0; + setCoins.clear(); + if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, &coin_control)) + { + strFailReason = _("Insufficient funds"); + return false; + } + } + + const CAmount nChange = nValueIn - nValueToSelect; + + if (nChange > 0) + { + // Fill a vout to ourself + CTxOut newTxOut(nChange, scriptChange); + + // Never create dust outputs; if we would, just + // add the dust to the fee. + if (IsDust(newTxOut, discard_rate)) + { + nChangePosInOut = -1; + nFeeRet += nChange; + } + else + { + if (nChangePosInOut == -1) + { + // Insert change txn at random position: + nChangePosInOut = GetRandInt(txNew.vout.size()+1); + } + else if ((unsigned int)nChangePosInOut > txNew.vout.size()) + { + strFailReason = _("Change index out of range"); + return false; + } + + std::vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosInOut; + txNew.vout.insert(position, newTxOut); + } + } else { + nChangePosInOut = -1; + } + + // Fill vin + // + // Note how the sequence number is set to non-maxint so that + // the nLockTime set above actually works. + // + // BIP125 defines opt-in RBF as any nSequence < maxint-1, so + // we use the highest possible value in that range (maxint-2) + // to avoid conflicting with other possible uses of nSequence, + // and in the spirit of "smallest possible change from prior + // behavior." + const uint32_t nSequence = coin_control.signalRbf ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1); + for (const auto& coin : setCoins) + txNew.vin.push_back(CTxIn(coin.outpoint,CScript(), + nSequence)); + + // Fill in dummy signatures for fee calculation. + if (!DummySignTx(txNew, setCoins)) { + strFailReason = _("Signing transaction failed"); + return false; + } + + nBytes = GetVirtualTransactionSize(txNew); + + // Remove scriptSigs to eliminate the fee calculation dummy signatures + for (auto& vin : txNew.vin) { + vin.scriptSig = CScript(); + vin.scriptWitness.SetNull(); + } + + nFeeNeeded = GetMinimumFee(nBytes, coin_control, ::mempool, ::feeEstimator, &feeCalc); + + // If we made it here and we aren't even able to meet the relay fee on the next pass, give up + // because we must be at the maximum allowed fee. + if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) + { + strFailReason = _("Transaction too large for fee policy"); + return false; + } + + if (nFeeRet >= nFeeNeeded) { + // Reduce fee to only the needed amount if possible. This + // prevents potential overpayment in fees if the coins + // selected to meet nFeeNeeded result in a transaction that + // requires less fee than the prior iteration. + + // If we have no change and a big enough excess fee, then + // try to construct transaction again only without picking + // new inputs. We now know we only need the smaller fee + // (because of reduced tx size) and so we should add a + // change output. Only try this once. + if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) { + unsigned int tx_size_with_change = nBytes + change_prototype_size + 2; // Add 2 as a buffer in case increasing # of outputs changes compact size + CAmount fee_needed_with_change = GetMinimumFee(tx_size_with_change, coin_control, ::mempool, ::feeEstimator, nullptr); + CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, discard_rate); + if (nFeeRet >= fee_needed_with_change + minimum_value_for_change) { + pick_new_inputs = false; + nFeeRet = fee_needed_with_change; + continue; + } + } + + // If we have change output already, just increase it + if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { + CAmount extraFeePaid = nFeeRet - nFeeNeeded; + std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut; + change_position->nValue += extraFeePaid; + nFeeRet -= extraFeePaid; + } + break; // Done, enough fee included. + } + else if (!pick_new_inputs) { + // This shouldn't happen, we should have had enough excess + // fee to pay for the new output and still meet nFeeNeeded + // Or we should have just subtracted fee from recipients and + // nFeeNeeded should not have changed + strFailReason = _("Transaction fee and change calculation failed"); + return false; + } + + // Try to reduce change to include necessary fee + if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { + CAmount additionalFeeNeeded = nFeeNeeded - nFeeRet; + std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut; + // Only reduce change if remaining amount is still a large enough output. + if (change_position->nValue >= MIN_FINAL_CHANGE + additionalFeeNeeded) { + change_position->nValue -= additionalFeeNeeded; + nFeeRet += additionalFeeNeeded; + break; // Done, able to increase fee from change + } + } + + // If subtracting fee from recipients, we now know what fee we + // need to subtract, we have no reason to reselect inputs + if (nSubtractFeeFromAmount > 0) { + pick_new_inputs = false; + } + + // Include more fee and try again. + nFeeRet = nFeeNeeded; + continue; + } + } + + if (nChangePosInOut == -1) reservekey.ReturnKey(); // Return any reserved key if we don't have change + + if (sign) + { + CTransaction txNewConst(txNew); + int nIn = 0; + for (const auto& coin : setCoins) + { + const CScript& scriptPubKey = coin.txout.scriptPubKey; + SignatureData sigdata; + + if (!ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) + { + strFailReason = _("Signing transaction failed"); + return false; + } else { + UpdateTransaction(txNew, nIn, sigdata); + } + + nIn++; + } + } + + // Embed the constructed transaction data in wtxNew. + wtxNew.SetTx(MakeTransactionRef(std::move(txNew))); + + // Limit size + if (GetTransactionWeight(*wtxNew.tx) >= MAX_STANDARD_TX_WEIGHT) + { + strFailReason = _("Transaction too large"); + return false; + } + } + + if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { + // Lastly, ensure this tx will pass the mempool's chain limits + LockPoints lp; + CTxMemPoolEntry entry(wtxNew.tx, 0, 0, 0, false, 0, lp); + CTxMemPool::setEntries setAncestors; + size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); + size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000; + size_t nLimitDescendants = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); + size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000; + std::string errString; + if (!mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { + strFailReason = _("Transaction has too long of a mempool chain"); + return false; + } + } + + LogPrintf("Fee Calculation: Fee:%d Bytes:%u Needed:%d Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", + nFeeRet, nBytes, nFeeNeeded, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay, + feeCalc.est.pass.start, feeCalc.est.pass.end, + 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool), + feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool, + feeCalc.est.fail.start, feeCalc.est.fail.end, + 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool), + feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool); + return true; +} + +/** + * Call after CreateTransaction unless you want to abort + */ +bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state) +{ + { + LOCK2(cs_main, cs_wallet); + LogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString()); + { + // Take key pair from key pool so it won't be used again + reservekey.KeepKey(); + + // Add tx to wallet, because if it has change it's also ours, + // otherwise just for transaction history. + AddToWallet(wtxNew); + + // Notify that old coins are spent + for (const CTxIn& txin : wtxNew.tx->vin) + { + CWalletTx &coin = mapWallet[txin.prevout.hash]; + coin.BindWallet(this); + NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED); + } + } + + // Track how many getdata requests our transaction gets + mapRequestCount[wtxNew.GetHash()] = 0; + + // Get the inserted-CWalletTx from mapWallet so that the + // fInMempool flag is cached properly + CWalletTx& wtx = mapWallet[wtxNew.GetHash()]; + + if (fBroadcastTransactions) + { + // Broadcast + if (!wtx.AcceptToMemoryPool(maxTxFee, state)) { + LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", state.GetRejectReason()); + // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. + } else { + wtx.RelayWalletTransaction(connman); + } + } + } + return true; +} + +void CWallet::ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries) { + CWalletDB walletdb(*dbw); + return walletdb.ListAccountCreditDebit(strAccount, entries); +} + +bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry) +{ + CWalletDB walletdb(*dbw); + + return AddAccountingEntry(acentry, &walletdb); +} + +bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry, CWalletDB *pwalletdb) +{ + if (!pwalletdb->WriteAccountingEntry(++nAccountingEntryNumber, acentry)) { + return false; + } + + laccentries.push_back(acentry); + CAccountingEntry & entry = laccentries.back(); + wtxOrdered.insert(std::make_pair(entry.nOrderPos, TxPair(nullptr, &entry))); + + return true; +} + +DBErrors CWallet::LoadWallet(bool& fFirstRunRet) +{ + LOCK2(cs_main, cs_wallet); + + fFirstRunRet = false; + DBErrors nLoadWalletRet = CWalletDB(*dbw,"cr+").LoadWallet(this); + if (nLoadWalletRet == DB_NEED_REWRITE) + { + if (dbw->Rewrite("\x04pool")) + { + setInternalKeyPool.clear(); + setExternalKeyPool.clear(); + m_pool_key_to_index.clear(); + // Note: can't top-up keypool here, because wallet is locked. + // User will be prompted to unlock wallet the next operation + // that requires a new key. + } + } + + // This wallet is in its first run if all of these are empty + fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty(); + + if (nLoadWalletRet != DB_LOAD_OK) + return nLoadWalletRet; + + uiInterface.LoadWallet(this); + + return DB_LOAD_OK; +} + +DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut) +{ + AssertLockHeld(cs_wallet); // mapWallet + DBErrors nZapSelectTxRet = CWalletDB(*dbw,"cr+").ZapSelectTx(vHashIn, vHashOut); + for (uint256 hash : vHashOut) + mapWallet.erase(hash); + + if (nZapSelectTxRet == DB_NEED_REWRITE) + { + if (dbw->Rewrite("\x04pool")) + { + setInternalKeyPool.clear(); + setExternalKeyPool.clear(); + m_pool_key_to_index.clear(); + // Note: can't top-up keypool here, because wallet is locked. + // User will be prompted to unlock wallet the next operation + // that requires a new key. + } + } + + if (nZapSelectTxRet != DB_LOAD_OK) + return nZapSelectTxRet; + + MarkDirty(); + + return DB_LOAD_OK; + +} + +DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx) +{ + DBErrors nZapWalletTxRet = CWalletDB(*dbw,"cr+").ZapWalletTx(vWtx); + if (nZapWalletTxRet == DB_NEED_REWRITE) + { + if (dbw->Rewrite("\x04pool")) + { + LOCK(cs_wallet); + setInternalKeyPool.clear(); + setExternalKeyPool.clear(); + m_pool_key_to_index.clear(); + // Note: can't top-up keypool here, because wallet is locked. + // User will be prompted to unlock wallet the next operation + // that requires a new key. + } + } + + if (nZapWalletTxRet != DB_LOAD_OK) + return nZapWalletTxRet; + + return DB_LOAD_OK; +} + + +bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& strPurpose) +{ + bool fUpdated = false; + { + LOCK(cs_wallet); // mapAddressBook + std::map<CTxDestination, CAddressBookData>::iterator mi = mapAddressBook.find(address); + fUpdated = mi != mapAddressBook.end(); + mapAddressBook[address].name = strName; + if (!strPurpose.empty()) /* update purpose only if requested */ + mapAddressBook[address].purpose = strPurpose; + } + NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO, + strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) ); + if (!strPurpose.empty() && !CWalletDB(*dbw).WritePurpose(EncodeDestination(address), strPurpose)) + return false; + return CWalletDB(*dbw).WriteName(EncodeDestination(address), strName); +} + +bool CWallet::DelAddressBook(const CTxDestination& address) +{ + { + LOCK(cs_wallet); // mapAddressBook + + // Delete destdata tuples associated with address + std::string strAddress = EncodeDestination(address); + for (const std::pair<std::string, std::string> &item : mapAddressBook[address].destdata) + { + CWalletDB(*dbw).EraseDestData(strAddress, item.first); + } + mapAddressBook.erase(address); + } + + NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address) != ISMINE_NO, "", CT_DELETED); + + CWalletDB(*dbw).ErasePurpose(EncodeDestination(address)); + return CWalletDB(*dbw).EraseName(EncodeDestination(address)); +} + +const std::string& CWallet::GetAccountName(const CScript& scriptPubKey) const +{ + CTxDestination address; + if (ExtractDestination(scriptPubKey, address) && !scriptPubKey.IsUnspendable()) { + auto mi = mapAddressBook.find(address); + if (mi != mapAddressBook.end()) { + return mi->second.name; + } + } + // A scriptPubKey that doesn't have an entry in the address book is + // associated with the default account (""). + const static std::string DEFAULT_ACCOUNT_NAME; + return DEFAULT_ACCOUNT_NAME; +} + +/** + * Mark old keypool keys as used, + * and generate all new keys + */ +bool CWallet::NewKeyPool() +{ + { + LOCK(cs_wallet); + CWalletDB walletdb(*dbw); + + for (int64_t nIndex : setInternalKeyPool) { + walletdb.ErasePool(nIndex); + } + setInternalKeyPool.clear(); + + for (int64_t nIndex : setExternalKeyPool) { + walletdb.ErasePool(nIndex); + } + setExternalKeyPool.clear(); + + m_pool_key_to_index.clear(); + + if (!TopUpKeyPool()) { + return false; + } + LogPrintf("CWallet::NewKeyPool rewrote keypool\n"); + } + return true; +} + +size_t CWallet::KeypoolCountExternalKeys() +{ + AssertLockHeld(cs_wallet); // setExternalKeyPool + return setExternalKeyPool.size(); +} + +void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) +{ + AssertLockHeld(cs_wallet); + if (keypool.fInternal) { + setInternalKeyPool.insert(nIndex); + } else { + setExternalKeyPool.insert(nIndex); + } + m_max_keypool_index = std::max(m_max_keypool_index, nIndex); + m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex; + + // If no metadata exists yet, create a default with the pool key's + // creation time. Note that this may be overwritten by actually + // stored metadata for that key later, which is fine. + CKeyID keyid = keypool.vchPubKey.GetID(); + if (mapKeyMetadata.count(keyid) == 0) + mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); +} + +bool CWallet::TopUpKeyPool(unsigned int kpSize) +{ + { + LOCK(cs_wallet); + + if (IsLocked()) + return false; + + // Top up key pool + unsigned int nTargetSize; + if (kpSize > 0) + nTargetSize = kpSize; + else + nTargetSize = std::max(gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0); + + // count amount of available keys (internal, external) + // make sure the keypool of external and internal keys fits the user selected target (-keypool) + int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setExternalKeyPool.size(), (int64_t) 0); + int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setInternalKeyPool.size(), (int64_t) 0); + + if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT)) + { + // don't create extra internal keys + missingInternal = 0; + } + bool internal = false; + CWalletDB walletdb(*dbw); + for (int64_t i = missingInternal + missingExternal; i--;) + { + if (i < missingInternal) { + internal = true; + } + + assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys? + int64_t index = ++m_max_keypool_index; + + CPubKey pubkey(GenerateNewKey(walletdb, internal)); + if (!walletdb.WritePool(index, CKeyPool(pubkey, internal))) { + throw std::runtime_error(std::string(__func__) + ": writing generated key failed"); + } + + if (internal) { + setInternalKeyPool.insert(index); + } else { + setExternalKeyPool.insert(index); + } + m_pool_key_to_index[pubkey.GetID()] = index; + } + if (missingInternal + missingExternal > 0) { + LogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size(), setInternalKeyPool.size()); + } + } + return true; +} + +void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal) +{ + nIndex = -1; + keypool.vchPubKey = CPubKey(); + { + LOCK(cs_wallet); + + if (!IsLocked()) + TopUpKeyPool(); + + bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal; + std::set<int64_t>& setKeyPool = fReturningInternal ? setInternalKeyPool : setExternalKeyPool; + + // Get the oldest key + if(setKeyPool.empty()) + return; + + CWalletDB walletdb(*dbw); + + auto it = setKeyPool.begin(); + nIndex = *it; + setKeyPool.erase(it); + if (!walletdb.ReadPool(nIndex, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read failed"); + } + if (!HaveKey(keypool.vchPubKey.GetID())) { + throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); + } + if (keypool.fInternal != fReturningInternal) { + throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); + } + + assert(keypool.vchPubKey.IsValid()); + m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); + LogPrintf("keypool reserve %d\n", nIndex); + } +} + +void CWallet::KeepKey(int64_t nIndex) +{ + // Remove from key pool + CWalletDB walletdb(*dbw); + walletdb.ErasePool(nIndex); + LogPrintf("keypool keep %d\n", nIndex); +} + +void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) +{ + // Return to key pool + { + LOCK(cs_wallet); + if (fInternal) { + setInternalKeyPool.insert(nIndex); + } else { + setExternalKeyPool.insert(nIndex); + } + m_pool_key_to_index[pubkey.GetID()] = nIndex; + } + LogPrintf("keypool return %d\n", nIndex); +} + +bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) +{ + CKeyPool keypool; + { + LOCK(cs_wallet); + int64_t nIndex = 0; + ReserveKeyFromKeyPool(nIndex, keypool, internal); + if (nIndex == -1) + { + if (IsLocked()) return false; + CWalletDB walletdb(*dbw); + result = GenerateNewKey(walletdb, internal); + return true; + } + KeepKey(nIndex); + result = keypool.vchPubKey; + } + return true; +} + +static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, CWalletDB& walletdb) { + if (setKeyPool.empty()) { + return GetTime(); + } + + CKeyPool keypool; + int64_t nIndex = *(setKeyPool.begin()); + if (!walletdb.ReadPool(nIndex, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed"); + } + assert(keypool.vchPubKey.IsValid()); + return keypool.nTime; +} + +int64_t CWallet::GetOldestKeyPoolTime() +{ + LOCK(cs_wallet); + + CWalletDB walletdb(*dbw); + + // load oldest key from keypool, get time and return + int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, walletdb); + if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) { + oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, walletdb), oldestKey); + } + + return oldestKey; +} + +std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() +{ + std::map<CTxDestination, CAmount> balances; + + { + LOCK(cs_wallet); + for (const auto& walletEntry : mapWallet) + { + const CWalletTx *pcoin = &walletEntry.second; + + if (!pcoin->IsTrusted()) + continue; + + if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) + continue; + + int nDepth = pcoin->GetDepthInMainChain(); + if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1)) + continue; + + for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) + { + CTxDestination addr; + if (!IsMine(pcoin->tx->vout[i])) + continue; + if(!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, addr)) + continue; + + CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->tx->vout[i].nValue; + + if (!balances.count(addr)) + balances[addr] = 0; + balances[addr] += n; + } + } + } + + return balances; +} + +std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() +{ + AssertLockHeld(cs_wallet); // mapWallet + std::set< std::set<CTxDestination> > groupings; + std::set<CTxDestination> grouping; + + for (const auto& walletEntry : mapWallet) + { + const CWalletTx *pcoin = &walletEntry.second; + + if (pcoin->tx->vin.size() > 0) + { + bool any_mine = false; + // group all input addresses with each other + for (CTxIn txin : pcoin->tx->vin) + { + CTxDestination address; + if(!IsMine(txin)) /* If this input isn't mine, ignore it */ + continue; + if(!ExtractDestination(mapWallet[txin.prevout.hash].tx->vout[txin.prevout.n].scriptPubKey, address)) + continue; + grouping.insert(address); + any_mine = true; + } + + // group change with input addresses + if (any_mine) + { + for (CTxOut txout : pcoin->tx->vout) + if (IsChange(txout)) + { + CTxDestination txoutAddr; + if(!ExtractDestination(txout.scriptPubKey, txoutAddr)) + continue; + grouping.insert(txoutAddr); + } + } + if (grouping.size() > 0) + { + groupings.insert(grouping); + grouping.clear(); + } + } + + // group lone addrs by themselves + for (const auto& txout : pcoin->tx->vout) + if (IsMine(txout)) + { + CTxDestination address; + if(!ExtractDestination(txout.scriptPubKey, address)) + continue; + grouping.insert(address); + groupings.insert(grouping); + grouping.clear(); + } + } + + std::set< std::set<CTxDestination>* > uniqueGroupings; // a set of pointers to groups of addresses + std::map< CTxDestination, std::set<CTxDestination>* > setmap; // map addresses to the unique group containing it + for (std::set<CTxDestination> _grouping : groupings) + { + // make a set of all the groups hit by this new group + std::set< std::set<CTxDestination>* > hits; + std::map< CTxDestination, std::set<CTxDestination>* >::iterator it; + for (CTxDestination address : _grouping) + if ((it = setmap.find(address)) != setmap.end()) + hits.insert((*it).second); + + // merge all hit groups into a new single group and delete old groups + std::set<CTxDestination>* merged = new std::set<CTxDestination>(_grouping); + for (std::set<CTxDestination>* hit : hits) + { + merged->insert(hit->begin(), hit->end()); + uniqueGroupings.erase(hit); + delete hit; + } + uniqueGroupings.insert(merged); + + // update setmap + for (CTxDestination element : *merged) + setmap[element] = merged; + } + + std::set< std::set<CTxDestination> > ret; + for (std::set<CTxDestination>* uniqueGrouping : uniqueGroupings) + { + ret.insert(*uniqueGrouping); + delete uniqueGrouping; + } + + return ret; +} + +std::set<CTxDestination> CWallet::GetAccountAddresses(const std::string& strAccount) const +{ + LOCK(cs_wallet); + std::set<CTxDestination> result; + for (const std::pair<CTxDestination, CAddressBookData>& item : mapAddressBook) + { + const CTxDestination& address = item.first; + const std::string& strName = item.second.name; + if (strName == strAccount) + result.insert(address); + } + return result; +} + +bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal) +{ + if (nIndex == -1) + { + CKeyPool keypool; + pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal); + if (nIndex != -1) + vchPubKey = keypool.vchPubKey; + else { + return false; + } + fInternal = keypool.fInternal; + } + assert(vchPubKey.IsValid()); + pubkey = vchPubKey; + return true; +} + +void CReserveKey::KeepKey() +{ + if (nIndex != -1) + pwallet->KeepKey(nIndex); + nIndex = -1; + vchPubKey = CPubKey(); +} + +void CReserveKey::ReturnKey() +{ + if (nIndex != -1) { + pwallet->ReturnKey(nIndex, fInternal, vchPubKey); + } + nIndex = -1; + vchPubKey = CPubKey(); +} + +void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) +{ + AssertLockHeld(cs_wallet); + bool internal = setInternalKeyPool.count(keypool_id); + if (!internal) assert(setExternalKeyPool.count(keypool_id)); + std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : &setExternalKeyPool; + auto it = setKeyPool->begin(); + + CWalletDB walletdb(*dbw); + while (it != std::end(*setKeyPool)) { + const int64_t& index = *(it); + if (index > keypool_id) break; // set*KeyPool is ordered + + CKeyPool keypool; + if (walletdb.ReadPool(index, keypool)) { //TODO: This should be unnecessary + m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); + } + LearnAllRelatedScripts(keypool.vchPubKey); + walletdb.ErasePool(index); + LogPrintf("keypool index %d removed\n", index); + it = setKeyPool->erase(it); + } +} + +void CWallet::GetScriptForMining(std::shared_ptr<CReserveScript> &script) +{ + std::shared_ptr<CReserveKey> rKey = std::make_shared<CReserveKey>(this); + CPubKey pubkey; + if (!rKey->GetReservedKey(pubkey)) + return; + + script = rKey; + script->reserveScript = CScript() << ToByteVector(pubkey) << OP_CHECKSIG; +} + +void CWallet::LockCoin(const COutPoint& output) +{ + AssertLockHeld(cs_wallet); // setLockedCoins + setLockedCoins.insert(output); +} + +void CWallet::UnlockCoin(const COutPoint& output) +{ + AssertLockHeld(cs_wallet); // setLockedCoins + setLockedCoins.erase(output); +} + +void CWallet::UnlockAllCoins() +{ + AssertLockHeld(cs_wallet); // setLockedCoins + setLockedCoins.clear(); +} + +bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const +{ + AssertLockHeld(cs_wallet); // setLockedCoins + COutPoint outpt(hash, n); + + return (setLockedCoins.count(outpt) > 0); +} + +void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const +{ + AssertLockHeld(cs_wallet); // setLockedCoins + for (std::set<COutPoint>::iterator it = setLockedCoins.begin(); + it != setLockedCoins.end(); it++) { + COutPoint outpt = (*it); + vOutpts.push_back(outpt); + } +} + +/** @} */ // end of Actions + +void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const { + AssertLockHeld(cs_wallet); // mapKeyMetadata + mapKeyBirth.clear(); + + // get birth times for keys with metadata + for (const auto& entry : mapKeyMetadata) { + if (entry.second.nCreateTime) { + mapKeyBirth[entry.first] = entry.second.nCreateTime; + } + } + + // map in which we'll infer heights of other keys + CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganized; use a 144-block safety margin + std::map<CKeyID, CBlockIndex*> mapKeyFirstBlock; + for (const CKeyID &keyid : GetKeys()) { + if (mapKeyBirth.count(keyid) == 0) + mapKeyFirstBlock[keyid] = pindexMax; + } + + // if there are no such keys, we're done + if (mapKeyFirstBlock.empty()) + return; + + // find first block that affects those keys, if there are any left + std::vector<CKeyID> vAffected; + for (const auto& entry : mapWallet) { + // iterate over all wallet transactions... + const CWalletTx &wtx = entry.second; + BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock); + if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) { + // ... which are already in a block + int nHeight = blit->second->nHeight; + for (const CTxOut &txout : wtx.tx->vout) { + // iterate over all their outputs + CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey); + for (const CKeyID &keyid : vAffected) { + // ... and all their affected keys + std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid); + if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight) + rit->second = blit->second; + } + vAffected.clear(); + } + } + } + + // Extract block timestamps for those keys + for (const auto& entry : mapKeyFirstBlock) + mapKeyBirth[entry.first] = entry.second->GetBlockTime() - TIMESTAMP_WINDOW; // block times can be 2h off +} + +/** + * Compute smart timestamp for a transaction being added to the wallet. + * + * Logic: + * - If sending a transaction, assign its timestamp to the current time. + * - If receiving a transaction outside a block, assign its timestamp to the + * current time. + * - If receiving a block with a future timestamp, assign all its (not already + * known) transactions' timestamps to the current time. + * - If receiving a block with a past timestamp, before the most recent known + * transaction (that we care about), assign all its (not already known) + * transactions' timestamps to the same timestamp as that most-recent-known + * transaction. + * - If receiving a block with a past timestamp, but after the most recent known + * transaction, assign all its (not already known) transactions' timestamps to + * the block time. + * + * For more information see CWalletTx::nTimeSmart, + * https://bitcointalk.org/?topic=54527, or + * https://github.com/bitcoin/bitcoin/pull/1393. + */ +unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const +{ + unsigned int nTimeSmart = wtx.nTimeReceived; + if (!wtx.hashUnset()) { + if (mapBlockIndex.count(wtx.hashBlock)) { + int64_t latestNow = wtx.nTimeReceived; + int64_t latestEntry = 0; + + // Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future + int64_t latestTolerated = latestNow + 300; + const TxItems& txOrdered = wtxOrdered; + for (auto it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { + CWalletTx* const pwtx = it->second.first; + if (pwtx == &wtx) { + continue; + } + CAccountingEntry* const pacentry = it->second.second; + int64_t nSmartTime; + if (pwtx) { + nSmartTime = pwtx->nTimeSmart; + if (!nSmartTime) { + nSmartTime = pwtx->nTimeReceived; + } + } else { + nSmartTime = pacentry->nTime; + } + if (nSmartTime <= latestTolerated) { + latestEntry = nSmartTime; + if (nSmartTime > latestNow) { + latestNow = nSmartTime; + } + break; + } + } + + int64_t blocktime = mapBlockIndex[wtx.hashBlock]->GetBlockTime(); + nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); + } else { + LogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString()); + } + } + return nTimeSmart; +} + +bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value) +{ + if (boost::get<CNoDestination>(&dest)) + return false; + + mapAddressBook[dest].destdata.insert(std::make_pair(key, value)); + return CWalletDB(*dbw).WriteDestData(EncodeDestination(dest), key, value); +} + +bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key) +{ + if (!mapAddressBook[dest].destdata.erase(key)) + return false; + return CWalletDB(*dbw).EraseDestData(EncodeDestination(dest), key); +} + +bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value) +{ + mapAddressBook[dest].destdata.insert(std::make_pair(key, value)); + return true; +} + +bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const +{ + std::map<CTxDestination, CAddressBookData>::const_iterator i = mapAddressBook.find(dest); + if(i != mapAddressBook.end()) + { + CAddressBookData::StringMap::const_iterator j = i->second.destdata.find(key); + if(j != i->second.destdata.end()) + { + if(value) + *value = j->second; + return true; + } + } + return false; +} + +std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const +{ + LOCK(cs_wallet); + std::vector<std::string> values; + for (const auto& address : mapAddressBook) { + for (const auto& data : address.second.destdata) { + if (!data.first.compare(0, prefix.size(), prefix)) { + values.emplace_back(data.second); + } + } + } + return values; +} + +CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) +{ + // needed to restore wallet transaction meta data after -zapwallettxes + std::vector<CWalletTx> vWtx; + + if (gArgs.GetBoolArg("-zapwallettxes", false)) { + uiInterface.InitMessage(_("Zapping all transactions from wallet...")); + + std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, walletFile)); + std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(std::move(dbw)); + DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); + if (nZapWalletRet != DB_LOAD_OK) { + InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); + return nullptr; + } + } + + uiInterface.InitMessage(_("Loading wallet...")); + + int64_t nStart = GetTimeMillis(); + bool fFirstRun = true; + std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, walletFile)); + CWallet *walletInstance = new CWallet(std::move(dbw)); + DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); + if (nLoadWalletRet != DB_LOAD_OK) + { + if (nLoadWalletRet == DB_CORRUPT) { + InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); + return nullptr; + } + else if (nLoadWalletRet == DB_NONCRITICAL_ERROR) + { + InitWarning(strprintf(_("Error reading %s! All keys read correctly, but transaction data" + " or address book entries might be missing or incorrect."), + walletFile)); + } + else if (nLoadWalletRet == DB_TOO_NEW) { + InitError(strprintf(_("Error loading %s: Wallet requires newer version of %s"), walletFile, _(PACKAGE_NAME))); + return nullptr; + } + else if (nLoadWalletRet == DB_NEED_REWRITE) + { + InitError(strprintf(_("Wallet needed to be rewritten: restart %s to complete"), _(PACKAGE_NAME))); + return nullptr; + } + else { + InitError(strprintf(_("Error loading %s"), walletFile)); + return nullptr; + } + } + + if (gArgs.GetBoolArg("-upgradewallet", fFirstRun)) + { + int nMaxVersion = gArgs.GetArg("-upgradewallet", 0); + if (nMaxVersion == 0) // the -upgradewallet without argument case + { + LogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST); + nMaxVersion = CLIENT_VERSION; + walletInstance->SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately + } + else + LogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); + if (nMaxVersion < walletInstance->GetVersion()) + { + InitError(_("Cannot downgrade wallet")); + return nullptr; + } + walletInstance->SetMaxVersion(nMaxVersion); + } + + if (fFirstRun) + { + // ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key + if (!gArgs.GetBoolArg("-usehd", true)) { + InitError(strprintf(_("Error creating %s: You can't create non-HD wallets with this version."), walletFile)); + return nullptr; + } + walletInstance->SetMinVersion(FEATURE_NO_DEFAULT_KEY); + + // generate a new master key + CPubKey masterPubKey = walletInstance->GenerateNewHDMasterKey(); + if (!walletInstance->SetHDMasterKey(masterPubKey)) + throw std::runtime_error(std::string(__func__) + ": Storing master key failed"); + + // Top up the keypool + if (!walletInstance->TopUpKeyPool()) { + InitError(_("Unable to generate initial keys") += "\n"); + return nullptr; + } + + walletInstance->SetBestChain(chainActive.GetLocator()); + } + else if (gArgs.IsArgSet("-usehd")) { + bool useHD = gArgs.GetBoolArg("-usehd", true); + if (walletInstance->IsHDEnabled() && !useHD) { + InitError(strprintf(_("Error loading %s: You can't disable HD on an already existing HD wallet"), walletFile)); + return nullptr; + } + if (!walletInstance->IsHDEnabled() && useHD) { + InitError(strprintf(_("Error loading %s: You can't enable HD on an already existing non-HD wallet"), walletFile)); + return nullptr; + } + } + + LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); + + // Try to top up keypool. No-op if the wallet is locked. + walletInstance->TopUpKeyPool(); + + CBlockIndex *pindexRescan = chainActive.Genesis(); + if (!gArgs.GetBoolArg("-rescan", false)) + { + CWalletDB walletdb(*walletInstance->dbw); + CBlockLocator locator; + if (walletdb.ReadBestBlock(locator)) + pindexRescan = FindForkInGlobalIndex(chainActive, locator); + } + + walletInstance->m_last_block_processed = chainActive.Tip(); + RegisterValidationInterface(walletInstance); + + if (chainActive.Tip() && chainActive.Tip() != pindexRescan) + { + //We can't rescan beyond non-pruned blocks, stop and throw an error + //this might happen if a user uses an old wallet within a pruned node + // or if he ran -disablewallet for a longer time, then decided to re-enable + if (fPruneMode) + { + CBlockIndex *block = chainActive.Tip(); + while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA) && block->pprev->nTx > 0 && pindexRescan != block) + block = block->pprev; + + if (pindexRescan != block) { + InitError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)")); + return nullptr; + } + } + + uiInterface.InitMessage(_("Rescanning...")); + LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); + + // No need to read and scan block if block was created before + // our wallet birthday (as adjusted for block time variability) + while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) { + pindexRescan = chainActive.Next(pindexRescan); + } + + nStart = GetTimeMillis(); + { + WalletRescanReserver reserver(walletInstance); + if (!reserver.reserve()) { + InitError(_("Failed to rescan the wallet during initialization")); + return nullptr; + } + walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true); + } + LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); + walletInstance->SetBestChain(chainActive.GetLocator()); + walletInstance->dbw->IncrementUpdateCounter(); + + // Restore wallet transaction metadata after -zapwallettxes=1 + if (gArgs.GetBoolArg("-zapwallettxes", false) && gArgs.GetArg("-zapwallettxes", "1") != "2") + { + CWalletDB walletdb(*walletInstance->dbw); + + for (const CWalletTx& wtxOld : vWtx) + { + uint256 hash = wtxOld.GetHash(); + std::map<uint256, CWalletTx>::iterator mi = walletInstance->mapWallet.find(hash); + if (mi != walletInstance->mapWallet.end()) + { + const CWalletTx* copyFrom = &wtxOld; + CWalletTx* copyTo = &mi->second; + copyTo->mapValue = copyFrom->mapValue; + copyTo->vOrderForm = copyFrom->vOrderForm; + copyTo->nTimeReceived = copyFrom->nTimeReceived; + copyTo->nTimeSmart = copyFrom->nTimeSmart; + copyTo->fFromMe = copyFrom->fFromMe; + copyTo->strFromAccount = copyFrom->strFromAccount; + copyTo->nOrderPos = copyFrom->nOrderPos; + walletdb.WriteTx(*copyTo); + } + } + } + } + walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); + + { + LOCK(walletInstance->cs_wallet); + LogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize()); + LogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size()); + LogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size()); + } + + return walletInstance; +} + +std::atomic<bool> CWallet::fFlushScheduled(false); + +void CWallet::postInitProcess(CScheduler& scheduler) +{ + // Add wallet transactions that aren't already in a block to mempool + // Do this here as mempool requires genesis block to be loaded + ReacceptWalletTransactions(); + + // Run a thread to flush wallet periodically + if (!CWallet::fFlushScheduled.exchange(true)) { + scheduler.scheduleEvery(MaybeCompactWalletDB, 500); + } +} + +bool CWallet::BackupWallet(const std::string& strDest) +{ + return dbw->Backup(strDest); +} + +CKeyPool::CKeyPool() +{ + nTime = GetTime(); + fInternal = false; +} + +CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn) +{ + nTime = GetTime(); + vchPubKey = vchPubKeyIn; + fInternal = internalIn; +} + +CWalletKey::CWalletKey(int64_t nExpires) +{ + nTimeCreated = (nExpires ? GetTime() : 0); + nTimeExpires = nExpires; +} + +void CMerkleTx::SetMerkleBranch(const CBlockIndex* pindex, int posInBlock) +{ + // Update the tx's hashBlock + hashBlock = pindex->GetBlockHash(); + + // set the position of the transaction in the block + nIndex = posInBlock; +} + +int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const +{ + if (hashUnset()) + return 0; + + AssertLockHeld(cs_main); + + // Find the block it claims to be in + BlockMap::iterator mi = mapBlockIndex.find(hashBlock); + if (mi == mapBlockIndex.end()) + return 0; + CBlockIndex* pindex = (*mi).second; + if (!pindex || !chainActive.Contains(pindex)) + return 0; + + pindexRet = pindex; + return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1); +} + +int CMerkleTx::GetBlocksToMaturity() const +{ + if (!IsCoinBase()) + return 0; + return std::max(0, (COINBASE_MATURITY+1) - GetDepthInMainChain()); +} + + +bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state) +{ + // Quick check to avoid re-setting fInMempool to false + if (mempool.exists(tx->GetHash())) { + return false; + } + + // We must set fInMempool here - while it will be re-set to true by the + // entered-mempool callback, if we did not there would be a race where a + // user could call sendmoney in a loop and hit spurious out of funds errors + // because we think that the transaction they just generated's change is + // unavailable as we're not yet aware its in mempool. + bool ret = ::AcceptToMemoryPool(mempool, state, tx, nullptr /* pfMissingInputs */, + nullptr /* plTxnReplaced */, false /* bypass_limits */, nAbsurdFee); + fInMempool = ret; + return ret; +} + +static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy"; +static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit"; +static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32"; + +OutputType ParseOutputType(const std::string& type, OutputType default_type) +{ + if (type.empty()) { + return default_type; + } else if (type == OUTPUT_TYPE_STRING_LEGACY) { + return OUTPUT_TYPE_LEGACY; + } else if (type == OUTPUT_TYPE_STRING_P2SH_SEGWIT) { + return OUTPUT_TYPE_P2SH_SEGWIT; + } else if (type == OUTPUT_TYPE_STRING_BECH32) { + return OUTPUT_TYPE_BECH32; + } else { + return OUTPUT_TYPE_NONE; + } +} + +const std::string& FormatOutputType(OutputType type) +{ + switch (type) { + case OUTPUT_TYPE_LEGACY: return OUTPUT_TYPE_STRING_LEGACY; + case OUTPUT_TYPE_P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT; + case OUTPUT_TYPE_BECH32: return OUTPUT_TYPE_STRING_BECH32; + default: assert(false); + } +} + +void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type) +{ + if (key.IsCompressed() && (type == OUTPUT_TYPE_P2SH_SEGWIT || type == OUTPUT_TYPE_BECH32)) { + CTxDestination witdest = WitnessV0KeyHash(key.GetID()); + CScript witprog = GetScriptForDestination(witdest); + // Make sure the resulting program is solvable. + assert(IsSolvable(*this, witprog)); + AddCScript(witprog); + } +} + +void CWallet::LearnAllRelatedScripts(const CPubKey& key) +{ + // OUTPUT_TYPE_P2SH_SEGWIT always adds all necessary scripts for all types. + LearnRelatedScripts(key, OUTPUT_TYPE_P2SH_SEGWIT); +} + +CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type) +{ + switch (type) { + case OUTPUT_TYPE_LEGACY: return key.GetID(); + case OUTPUT_TYPE_P2SH_SEGWIT: + case OUTPUT_TYPE_BECH32: { + if (!key.IsCompressed()) return key.GetID(); + CTxDestination witdest = WitnessV0KeyHash(key.GetID()); + CScript witprog = GetScriptForDestination(witdest); + if (type == OUTPUT_TYPE_P2SH_SEGWIT) { + return CScriptID(witprog); + } else { + return witdest; + } + } + default: assert(false); + } +} + +std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key) +{ + CKeyID keyid = key.GetID(); + if (key.IsCompressed()) { + CTxDestination segwit = WitnessV0KeyHash(keyid); + CTxDestination p2sh = CScriptID(GetScriptForDestination(segwit)); + return std::vector<CTxDestination>{std::move(keyid), std::move(p2sh), std::move(segwit)}; + } else { + return std::vector<CTxDestination>{std::move(keyid)}; + } +} + +CTxDestination CWallet::AddAndGetDestinationForScript(const CScript& script, OutputType type) +{ + // Note that scripts over 520 bytes are not yet supported. + switch (type) { + case OUTPUT_TYPE_LEGACY: + return CScriptID(script); + case OUTPUT_TYPE_P2SH_SEGWIT: + case OUTPUT_TYPE_BECH32: { + WitnessV0ScriptHash hash; + CSHA256().Write(script.data(), script.size()).Finalize(hash.begin()); + CTxDestination witdest = hash; + CScript witprog = GetScriptForDestination(witdest); + // Check if the resulting program is solvable (i.e. doesn't use an uncompressed key) + if (!IsSolvable(*this, witprog)) return CScriptID(script); + // Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours. + AddCScript(witprog); + if (type == OUTPUT_TYPE_BECH32) { + return witdest; + } else { + return CScriptID(witprog); + } + } + default: assert(false); + } +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h new file mode 100644 index 0000000000..fefe415bb1 --- /dev/null +++ b/src/wallet/wallet.h @@ -0,0 +1,1307 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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_WALLET_H +#define BITCOIN_WALLET_WALLET_H + +#include <amount.h> +#include <policy/feerate.h> +#include <streams.h> +#include <tinyformat.h> +#include <ui_interface.h> +#include <utilstrencodings.h> +#include <validationinterface.h> +#include <script/ismine.h> +#include <script/sign.h> +#include <wallet/crypter.h> +#include <wallet/walletdb.h> +#include <wallet/rpcwallet.h> + +#include <algorithm> +#include <atomic> +#include <map> +#include <set> +#include <stdexcept> +#include <stdint.h> +#include <string> +#include <utility> +#include <vector> + +typedef CWallet* CWalletRef; +extern std::vector<CWalletRef> vpwallets; + +/** + * Settings + */ +extern CFeeRate payTxFee; +extern unsigned int nTxConfirmTarget; +extern bool bSpendZeroConfChange; +extern bool fWalletRbf; + +static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; +//! -paytxfee default +static const CAmount DEFAULT_TRANSACTION_FEE = 0; +//! -fallbackfee default +static const CAmount DEFAULT_FALLBACK_FEE = 20000; +//! -m_discard_rate default +static const CAmount DEFAULT_DISCARD_FEE = 10000; +//! -mintxfee default +static const CAmount DEFAULT_TRANSACTION_MINFEE = 1000; +//! minimum recommended increment for BIP 125 replacement txs +static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000; +//! target minimum change amount +static const CAmount MIN_CHANGE = CENT; +//! final minimum change amount after paying for fees +static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2; +//! Default for -spendzeroconfchange +static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true; +//! Default for -walletrejectlongchains +static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false; +//! -txconfirmtarget default +static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6; +//! -walletrbf default +static const bool DEFAULT_WALLET_RBF = false; +static const bool DEFAULT_WALLETBROADCAST = true; +static const bool DEFAULT_DISABLE_WALLET = false; + +extern const char * DEFAULT_WALLET_DAT; + +static const int64_t TIMESTAMP_MIN = 0; + +class CBlockIndex; +class CCoinControl; +class COutput; +class CReserveKey; +class CScript; +class CScheduler; +class CTxMemPool; +class CBlockPolicyEstimator; +class CWalletTx; +struct FeeCalculation; +enum class FeeEstimateMode; + +/** (client) version numbers for particular wallet features */ +enum WalletFeature +{ + FEATURE_BASE = 10500, // the earliest version new wallets supports (only useful for getwalletinfo's clientversion output) + + FEATURE_WALLETCRYPT = 40000, // wallet encryption + FEATURE_COMPRPUBKEY = 60000, // compressed public keys + + FEATURE_HD = 130000, // Hierarchical key derivation after BIP32 (HD Wallet) + + FEATURE_HD_SPLIT = 139900, // Wallet with HD chain split (change outputs will use m/0'/1'/k) + + FEATURE_NO_DEFAULT_KEY = 159900, // Wallet without a default key written + + FEATURE_LATEST = FEATURE_COMPRPUBKEY // HD is optional, use FEATURE_COMPRPUBKEY as latest version +}; + +enum OutputType : int +{ + OUTPUT_TYPE_NONE, + OUTPUT_TYPE_LEGACY, + OUTPUT_TYPE_P2SH_SEGWIT, + OUTPUT_TYPE_BECH32, + + OUTPUT_TYPE_DEFAULT = OUTPUT_TYPE_P2SH_SEGWIT +}; + +extern OutputType g_address_type; +extern OutputType g_change_type; + + +/** A key pool entry */ +class CKeyPool +{ +public: + int64_t nTime; + CPubKey vchPubKey; + bool fInternal; // for change outputs + + CKeyPool(); + CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn); + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + int nVersion = s.GetVersion(); + if (!(s.GetType() & SER_GETHASH)) + READWRITE(nVersion); + READWRITE(nTime); + READWRITE(vchPubKey); + if (ser_action.ForRead()) { + try { + READWRITE(fInternal); + } + catch (std::ios_base::failure&) { + /* flag as external address if we can't read the internal boolean + (this will be the case for any wallet before the HD chain split version) */ + fInternal = false; + } + } + else { + READWRITE(fInternal); + } + } +}; + +/** Address book data */ +class CAddressBookData +{ +public: + std::string name; + std::string purpose; + + CAddressBookData() : purpose("unknown") {} + + typedef std::map<std::string, std::string> StringMap; + StringMap destdata; +}; + +struct CRecipient +{ + CScript scriptPubKey; + CAmount nAmount; + bool fSubtractFeeFromAmount; +}; + +typedef std::map<std::string, std::string> mapValue_t; + + +static inline void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue) +{ + if (!mapValue.count("n")) + { + nOrderPos = -1; // TODO: calculate elsewhere + return; + } + nOrderPos = atoi64(mapValue["n"].c_str()); +} + + +static inline void WriteOrderPos(const int64_t& nOrderPos, mapValue_t& mapValue) +{ + if (nOrderPos == -1) + return; + mapValue["n"] = i64tostr(nOrderPos); +} + +struct COutputEntry +{ + CTxDestination destination; + CAmount amount; + int vout; +}; + +/** A transaction with a merkle branch linking it to the block chain. */ +class CMerkleTx +{ +private: + /** Constant used in hashBlock to indicate tx has been abandoned */ + static const uint256 ABANDON_HASH; + +public: + CTransactionRef tx; + uint256 hashBlock; + + /* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest + * block in the chain we know this or any in-wallet dependency conflicts + * with. Older clients interpret nIndex == -1 as unconfirmed for backward + * compatibility. + */ + int nIndex; + + CMerkleTx() + { + SetTx(MakeTransactionRef()); + Init(); + } + + explicit CMerkleTx(CTransactionRef arg) + { + SetTx(std::move(arg)); + Init(); + } + + void Init() + { + hashBlock = uint256(); + nIndex = -1; + } + + void SetTx(CTransactionRef arg) + { + tx = std::move(arg); + } + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + std::vector<uint256> vMerkleBranch; // For compatibility with older versions. + READWRITE(tx); + READWRITE(hashBlock); + READWRITE(vMerkleBranch); + READWRITE(nIndex); + } + + void SetMerkleBranch(const CBlockIndex* pIndex, int posInBlock); + + /** + * Return depth of transaction in blockchain: + * <0 : conflicts with a transaction this deep in the blockchain + * 0 : in memory pool, waiting to be included in a block + * >=1 : this many blocks deep in the main chain + */ + int GetDepthInMainChain(const CBlockIndex* &pindexRet) const; + int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); } + bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; } + int GetBlocksToMaturity() const; + bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } + bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } + void setAbandoned() { hashBlock = ABANDON_HASH; } + + const uint256& GetHash() const { return tx->GetHash(); } + bool IsCoinBase() const { return tx->IsCoinBase(); } +}; + +/** + * A transaction with a bunch of additional info that only the owner cares about. + * It includes any unrecorded transactions needed to link it back to the block chain. + */ +class CWalletTx : public CMerkleTx +{ +private: + const CWallet* pwallet; + +public: + /** + * Key/value map with information about the transaction. + * + * The following keys can be read and written through the map and are + * serialized in the wallet database: + * + * "comment", "to" - comment strings provided to sendtoaddress, + * sendfrom, sendmany wallet RPCs + * "replaces_txid" - txid (as HexStr) of transaction replaced by + * bumpfee on transaction created by bumpfee + * "replaced_by_txid" - txid (as HexStr) of transaction created by + * bumpfee on transaction replaced by bumpfee + * "from", "message" - obsolete fields that could be set in UI prior to + * 2011 (removed in commit 4d9b223) + * + * The following keys are serialized in the wallet database, but shouldn't + * be read or written through the map (they will be temporarily added and + * removed from the map during serialization): + * + * "fromaccount" - serialized strFromAccount value + * "n" - serialized nOrderPos value + * "timesmart" - serialized nTimeSmart value + * "spent" - serialized vfSpent value that existed prior to + * 2014 (removed in commit 93a18a3) + */ + mapValue_t mapValue; + std::vector<std::pair<std::string, std::string> > vOrderForm; + unsigned int fTimeReceivedIsTxTime; + unsigned int nTimeReceived; //!< time received by this node + /** + * Stable timestamp that never changes, and reflects the order a transaction + * was added to the wallet. Timestamp is based on the block time for a + * transaction added as part of a block, or else the time when the + * transaction was received if it wasn't part of a block, with the timestamp + * adjusted in both cases so timestamp order matches the order transactions + * were added to the wallet. More details can be found in + * CWallet::ComputeTimeSmart(). + */ + unsigned int nTimeSmart; + /** + * From me flag is set to 1 for transactions that were created by the wallet + * on this bitcoin node, and set to 0 for transactions that were created + * externally and came in through the network or sendrawtransaction RPC. + */ + char fFromMe; + std::string strFromAccount; + int64_t nOrderPos; //!< position in ordered transaction list + + // memory only + mutable bool fDebitCached; + mutable bool fCreditCached; + mutable bool fImmatureCreditCached; + mutable bool fAvailableCreditCached; + mutable bool fWatchDebitCached; + mutable bool fWatchCreditCached; + mutable bool fImmatureWatchCreditCached; + mutable bool fAvailableWatchCreditCached; + mutable bool fChangeCached; + mutable bool fInMempool; + mutable CAmount nDebitCached; + mutable CAmount nCreditCached; + mutable CAmount nImmatureCreditCached; + mutable CAmount nAvailableCreditCached; + mutable CAmount nWatchDebitCached; + mutable CAmount nWatchCreditCached; + mutable CAmount nImmatureWatchCreditCached; + mutable CAmount nAvailableWatchCreditCached; + mutable CAmount nChangeCached; + + CWalletTx() + { + Init(nullptr); + } + + CWalletTx(const CWallet* pwalletIn, CTransactionRef arg) : CMerkleTx(std::move(arg)) + { + Init(pwalletIn); + } + + void Init(const CWallet* pwalletIn) + { + pwallet = pwalletIn; + mapValue.clear(); + vOrderForm.clear(); + fTimeReceivedIsTxTime = false; + nTimeReceived = 0; + nTimeSmart = 0; + fFromMe = false; + strFromAccount.clear(); + fDebitCached = false; + fCreditCached = false; + fImmatureCreditCached = false; + fAvailableCreditCached = false; + fWatchDebitCached = false; + fWatchCreditCached = false; + fImmatureWatchCreditCached = false; + fAvailableWatchCreditCached = false; + fChangeCached = false; + fInMempool = false; + nDebitCached = 0; + nCreditCached = 0; + nImmatureCreditCached = 0; + nAvailableCreditCached = 0; + nWatchDebitCached = 0; + nWatchCreditCached = 0; + nAvailableWatchCreditCached = 0; + nImmatureWatchCreditCached = 0; + nChangeCached = 0; + nOrderPos = -1; + } + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + if (ser_action.ForRead()) + Init(nullptr); + char fSpent = false; + + if (!ser_action.ForRead()) + { + mapValue["fromaccount"] = strFromAccount; + + WriteOrderPos(nOrderPos, mapValue); + + if (nTimeSmart) + mapValue["timesmart"] = strprintf("%u", nTimeSmart); + } + + READWRITE(*static_cast<CMerkleTx*>(this)); + std::vector<CMerkleTx> vUnused; //!< Used to be vtxPrev + READWRITE(vUnused); + READWRITE(mapValue); + READWRITE(vOrderForm); + READWRITE(fTimeReceivedIsTxTime); + READWRITE(nTimeReceived); + READWRITE(fFromMe); + READWRITE(fSpent); + + if (ser_action.ForRead()) + { + strFromAccount = mapValue["fromaccount"]; + + ReadOrderPos(nOrderPos, mapValue); + + nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0; + } + + mapValue.erase("fromaccount"); + mapValue.erase("spent"); + mapValue.erase("n"); + mapValue.erase("timesmart"); + } + + //! make sure balances are recalculated + void MarkDirty() + { + fCreditCached = false; + fAvailableCreditCached = false; + fImmatureCreditCached = false; + fWatchDebitCached = false; + fWatchCreditCached = false; + fAvailableWatchCreditCached = false; + fImmatureWatchCreditCached = false; + fDebitCached = false; + fChangeCached = false; + } + + void BindWallet(CWallet *pwalletIn) + { + pwallet = pwalletIn; + MarkDirty(); + } + + //! filter decides which addresses will count towards the debit + CAmount GetDebit(const isminefilter& filter) const; + CAmount GetCredit(const isminefilter& filter) const; + CAmount GetImmatureCredit(bool fUseCache=true) const; + CAmount GetAvailableCredit(bool fUseCache=true) const; + CAmount GetImmatureWatchOnlyCredit(const bool fUseCache=true) const; + CAmount GetAvailableWatchOnlyCredit(const bool fUseCache=true) const; + CAmount GetChange() const; + + void GetAmounts(std::list<COutputEntry>& listReceived, + std::list<COutputEntry>& listSent, CAmount& nFee, std::string& strSentAccount, const isminefilter& filter) const; + + bool IsFromMe(const isminefilter& filter) const + { + return (GetDebit(filter) > 0); + } + + // True if only scriptSigs are different + bool IsEquivalentTo(const CWalletTx& tx) const; + + bool InMempool() const; + bool IsTrusted() const; + + int64_t GetTxTime() const; + int GetRequestCount() const; + + // RelayWalletTransaction may only be called if fBroadcastTransactions! + bool RelayWalletTransaction(CConnman* connman); + + /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */ + bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state); + + std::set<uint256> GetConflicts() const; +}; + + +class CInputCoin { +public: + CInputCoin(const CWalletTx* walletTx, unsigned int i) + { + if (!walletTx) + throw std::invalid_argument("walletTx should not be null"); + if (i >= walletTx->tx->vout.size()) + throw std::out_of_range("The output index is out of range"); + + outpoint = COutPoint(walletTx->GetHash(), i); + txout = walletTx->tx->vout[i]; + } + + COutPoint outpoint; + CTxOut txout; + + bool operator<(const CInputCoin& rhs) const { + return outpoint < rhs.outpoint; + } + + bool operator!=(const CInputCoin& rhs) const { + return outpoint != rhs.outpoint; + } + + bool operator==(const CInputCoin& rhs) const { + return outpoint == rhs.outpoint; + } +}; + +class COutput +{ +public: + const CWalletTx *tx; + int i; + int nDepth; + + /** Whether we have the private keys to spend this output */ + bool fSpendable; + + /** Whether we know how to spend this output, ignoring the lack of keys */ + bool fSolvable; + + /** + * Whether this output is considered safe to spend. Unconfirmed transactions + * from outside keys and unconfirmed replacement transactions are considered + * unsafe and will not be used to fund new spending transactions. + */ + bool fSafe; + + COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn) + { + tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; + } + + std::string ToString() const; +}; + + + + +/** Private key that includes an expiration date in case it never gets used. */ +class CWalletKey +{ +public: + CPrivKey vchPrivKey; + int64_t nTimeCreated; + int64_t nTimeExpires; + std::string strComment; + //! todo: add something to note what created it (user, getnewaddress, change) + //! maybe should have a map<string, string> property map + + explicit CWalletKey(int64_t nExpires=0); + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + int nVersion = s.GetVersion(); + if (!(s.GetType() & SER_GETHASH)) + READWRITE(nVersion); + READWRITE(vchPrivKey); + READWRITE(nTimeCreated); + READWRITE(nTimeExpires); + READWRITE(LIMITED_STRING(strComment, 65536)); + } +}; + +/** + * Internal transfers. + * Database key is acentry<account><counter>. + */ +class CAccountingEntry +{ +public: + std::string strAccount; + CAmount nCreditDebit; + int64_t nTime; + std::string strOtherAccount; + std::string strComment; + mapValue_t mapValue; + int64_t nOrderPos; //!< position in ordered transaction list + uint64_t nEntryNo; + + CAccountingEntry() + { + SetNull(); + } + + void SetNull() + { + nCreditDebit = 0; + nTime = 0; + strAccount.clear(); + strOtherAccount.clear(); + strComment.clear(); + nOrderPos = -1; + nEntryNo = 0; + } + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + int nVersion = s.GetVersion(); + if (!(s.GetType() & SER_GETHASH)) + READWRITE(nVersion); + //! Note: strAccount is serialized as part of the key, not here. + READWRITE(nCreditDebit); + READWRITE(nTime); + READWRITE(LIMITED_STRING(strOtherAccount, 65536)); + + if (!ser_action.ForRead()) + { + WriteOrderPos(nOrderPos, mapValue); + + if (!(mapValue.empty() && _ssExtra.empty())) + { + CDataStream ss(s.GetType(), s.GetVersion()); + ss.insert(ss.begin(), '\0'); + ss << mapValue; + ss.insert(ss.end(), _ssExtra.begin(), _ssExtra.end()); + strComment.append(ss.str()); + } + } + + READWRITE(LIMITED_STRING(strComment, 65536)); + + size_t nSepPos = strComment.find("\0", 0, 1); + if (ser_action.ForRead()) + { + mapValue.clear(); + if (std::string::npos != nSepPos) + { + CDataStream ss(std::vector<char>(strComment.begin() + nSepPos + 1, strComment.end()), s.GetType(), s.GetVersion()); + ss >> mapValue; + _ssExtra = std::vector<char>(ss.begin(), ss.end()); + } + ReadOrderPos(nOrderPos, mapValue); + } + if (std::string::npos != nSepPos) + strComment.erase(nSepPos); + + mapValue.erase("n"); + } + +private: + std::vector<char> _ssExtra; +}; + + +class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime +/** + * A CWallet is an extension of a keystore, which also maintains a set of transactions and balances, + * and provides the ability to create new transactions. + */ +class CWallet final : public CCryptoKeyStore, public CValidationInterface +{ +private: + static std::atomic<bool> fFlushScheduled; + std::atomic<bool> fAbortRescan; + std::atomic<bool> fScanningWallet; //controlled by WalletRescanReserver + std::mutex mutexScanning; + friend class WalletRescanReserver; + + + /** + * Select a set of coins such that nValueRet >= nTargetValue and at least + * all coins from coinControl are selected; Never select unconfirmed coins + * if they are not ours + */ + bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = nullptr) const; + + CWalletDB *pwalletdbEncryption; + + //! the current wallet version: clients below this version are not able to load the wallet + int nWalletVersion; + + //! the maximum wallet format version: memory-only variable that specifies to what version this wallet may be upgraded + int nWalletMaxVersion; + + int64_t nNextResend; + int64_t nLastResend; + bool fBroadcastTransactions; + + /** + * Used to keep track of spent outpoints, and + * detect and report conflicts (double-spends or + * mutated transactions where the mutant gets mined). + */ + typedef std::multimap<COutPoint, uint256> TxSpends; + TxSpends mapTxSpends; + void AddToSpends(const COutPoint& outpoint, const uint256& wtxid); + void AddToSpends(const uint256& wtxid); + + /* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */ + void MarkConflicted(const uint256& hashBlock, const uint256& hashTx); + + void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>); + + /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected. + * Should be called with pindexBlock and posInBlock if this is for a transaction that is included in a block. */ + void SyncTransaction(const CTransactionRef& tx, const CBlockIndex *pindex = nullptr, int posInBlock = 0); + + /* the HD chain data model (external chain counters) */ + CHDChain hdChain; + + /* HD derive new child key (on internal or external chain) */ + void DeriveNewChildKey(CWalletDB &walletdb, CKeyMetadata& metadata, CKey& secret, bool internal = false); + + std::set<int64_t> setInternalKeyPool; + std::set<int64_t> setExternalKeyPool; + int64_t m_max_keypool_index; + std::map<CKeyID, int64_t> m_pool_key_to_index; + + int64_t nTimeFirstKey; + + /** + * Private version of AddWatchOnly method which does not accept a + * timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if + * the watch key did not previously have a timestamp associated with it. + * Because this is an inherited virtual method, it is accessible despite + * being marked private, but it is marked private anyway to encourage use + * of the other AddWatchOnly which accepts a timestamp and sets + * nTimeFirstKey more intelligently for more efficient rescans. + */ + bool AddWatchOnly(const CScript& dest) override; + + std::unique_ptr<CWalletDBWrapper> dbw; + + /** + * The following is used to keep track of how far behind the wallet is + * from the chain sync, and to allow clients to block on us being caught up. + * + * Note that this is *not* how far we've processed, we may need some rescan + * to have seen all transactions in the chain, but is only used to track + * live BlockConnected callbacks. + * + * Protected by cs_main (see BlockUntilSyncedToCurrentChain) + */ + const CBlockIndex* m_last_block_processed; + +public: + /* + * Main wallet lock. + * This lock protects all the fields added by CWallet. + */ + mutable CCriticalSection cs_wallet; + + /** Get database handle used by this wallet. Ideally this function would + * not be necessary. + */ + CWalletDBWrapper& GetDBHandle() + { + return *dbw; + } + + /** Get a name for this wallet for logging/debugging purposes. + */ + std::string GetName() const + { + if (dbw) { + return dbw->GetName(); + } else { + return "dummy"; + } + } + + void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool); + + // Map from Key ID to key metadata. + std::map<CKeyID, CKeyMetadata> mapKeyMetadata; + + // Map from Script ID to key metadata (for watch-only keys). + std::map<CScriptID, CKeyMetadata> m_script_metadata; + + typedef std::map<unsigned int, CMasterKey> MasterKeyMap; + MasterKeyMap mapMasterKeys; + unsigned int nMasterKeyMaxID; + + // Create wallet with dummy database handle + CWallet(): dbw(new CWalletDBWrapper()) + { + SetNull(); + } + + // Create wallet with passed-in database handle + explicit CWallet(std::unique_ptr<CWalletDBWrapper> dbw_in) : dbw(std::move(dbw_in)) + { + SetNull(); + } + + ~CWallet() + { + delete pwalletdbEncryption; + pwalletdbEncryption = nullptr; + } + + void SetNull() + { + nWalletVersion = FEATURE_BASE; + nWalletMaxVersion = FEATURE_BASE; + nMasterKeyMaxID = 0; + pwalletdbEncryption = nullptr; + nOrderPosNext = 0; + nAccountingEntryNumber = 0; + nNextResend = 0; + nLastResend = 0; + m_max_keypool_index = 0; + nTimeFirstKey = 0; + fBroadcastTransactions = false; + nRelockTime = 0; + fAbortRescan = false; + fScanningWallet = false; + } + + std::map<uint256, CWalletTx> mapWallet; + std::list<CAccountingEntry> laccentries; + + typedef std::pair<CWalletTx*, CAccountingEntry*> TxPair; + typedef std::multimap<int64_t, TxPair > TxItems; + TxItems wtxOrdered; + + int64_t nOrderPosNext; + uint64_t nAccountingEntryNumber; + std::map<uint256, int> mapRequestCount; + + std::map<CTxDestination, CAddressBookData> mapAddressBook; + + std::set<COutPoint> setLockedCoins; + + const CWalletTx* GetWalletTx(const uint256& hash) const; + + //! check whether we are allowed to upgrade (or already support) to the named feature + bool CanSupportFeature(enum WalletFeature wf) const { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } + + /** + * populate vCoins with vector of available COutputs. + */ + void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0, const int nMinDepth = 0, const int nMaxDepth = 9999999) const; + + /** + * Return list of available coins and locked coins grouped by non-change output address. + */ + std::map<CTxDestination, std::vector<COutput>> ListCoins() const; + + /** + * Find non-change parent output. + */ + const CTxOut& FindNonChangeParentOutput(const CTransaction& tx, int output) const; + + /** + * Shuffle and select coins until nTargetValue is reached while avoiding + * small change; This method is stochastic for some inputs and upon + * completion the coin set and corresponding actual target value is + * assembled + */ + bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, uint64_t nMaxAncestors, std::vector<COutput> vCoins, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet) const; + + bool IsSpent(const uint256& hash, unsigned int n) const; + + bool IsLockedCoin(uint256 hash, unsigned int n) const; + void LockCoin(const COutPoint& output); + void UnlockCoin(const COutPoint& output); + void UnlockAllCoins(); + void ListLockedCoins(std::vector<COutPoint>& vOutpts) const; + + /* + * Rescan abort properties + */ + void AbortRescan() { fAbortRescan = true; } + bool IsAbortingRescan() { return fAbortRescan; } + bool IsScanning() { return fScanningWallet; } + + /** + * keystore implementation + * Generate a new key + */ + CPubKey GenerateNewKey(CWalletDB& walletdb, bool internal = false); + //! Adds a key to the store, and saves it to disk. + bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; + bool AddKeyPubKeyWithDB(CWalletDB &walletdb,const CKey& key, const CPubKey &pubkey); + //! Adds a key to the store, without saving it to disk (used by LoadWallet) + bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); } + //! Load metadata (used by LoadWallet) + bool LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata); + bool LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata); + + bool LoadMinVersion(int nVersion) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; } + void UpdateTimeFirstKey(int64_t nCreateTime); + + //! Adds an encrypted key to the store, and saves it to disk. + bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) override; + //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) + bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); + bool AddCScript(const CScript& redeemScript) override; + bool LoadCScript(const CScript& redeemScript); + + //! Adds a destination data tuple to the store, and saves it to disk + bool AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value); + //! Erases a destination data tuple in the store and on disk + bool EraseDestData(const CTxDestination &dest, const std::string &key); + //! Adds a destination data tuple to the store, without saving it to disk + bool LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value); + //! Look up a destination data tuple in the store, return true if found false otherwise + bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const; + //! Get all destination values matching a prefix. + std::vector<std::string> GetDestValues(const std::string& prefix) const; + + //! Adds a watch-only address to the store, and saves it to disk. + bool AddWatchOnly(const CScript& dest, int64_t nCreateTime); + bool RemoveWatchOnly(const CScript &dest) override; + //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) + bool LoadWatchOnly(const CScript &dest); + + //! Holds a timestamp at which point the wallet is scheduled (externally) to be relocked. Caller must arrange for actual relocking to occur via Lock(). + int64_t nRelockTime; + + bool Unlock(const SecureString& strWalletPassphrase); + bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); + bool EncryptWallet(const SecureString& strWalletPassphrase); + + void GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const; + unsigned int ComputeTimeSmart(const CWalletTx& wtx) const; + + /** + * Increment the next transaction order id + * @return next transaction order id + */ + int64_t IncOrderPosNext(CWalletDB *pwalletdb = nullptr); + DBErrors ReorderTransactions(); + bool AccountMove(std::string strFrom, std::string strTo, CAmount nAmount, std::string strComment = ""); + bool GetAccountDestination(CTxDestination &dest, std::string strAccount, bool bForceNew = false); + + void MarkDirty(); + bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true); + bool LoadToWallet(const CWalletTx& wtxIn); + void TransactionAddedToMempool(const CTransactionRef& tx) override; + void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) override; + void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override; + bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate); + int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); + CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver& reserver, bool fUpdate = false); + void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; + void ReacceptWalletTransactions(); + void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override; + // ResendWalletTransactionsBefore may only be called if fBroadcastTransactions! + std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman); + CAmount GetBalance() const; + CAmount GetUnconfirmedBalance() const; + CAmount GetImmatureBalance() const; + CAmount GetWatchOnlyBalance() const; + CAmount GetUnconfirmedWatchOnlyBalance() const; + CAmount GetImmatureWatchOnlyBalance() const; + CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const; + CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const; + + OutputType TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend); + + /** + * Insert additional inputs into the transaction by + * calling CreateTransaction(); + */ + bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl); + bool SignTransaction(CMutableTransaction& tx); + + /** + * Create a new transaction paying the recipients with a set of coins + * selected by SelectCoins(); Also create the change output, when needed + * @note passing nChangePosInOut as -1 will result in setting a random position + */ + bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, + std::string& strFailReason, const CCoinControl& coin_control, bool sign = true); + bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state); + + void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries); + bool AddAccountingEntry(const CAccountingEntry&); + bool AddAccountingEntry(const CAccountingEntry&, CWalletDB *pwalletdb); + template <typename ContainerType> + bool DummySignTx(CMutableTransaction &txNew, const ContainerType &coins) const; + + static CFeeRate minTxFee; + static CFeeRate fallbackFee; + static CFeeRate m_discard_rate; + + bool NewKeyPool(); + size_t KeypoolCountExternalKeys(); + bool TopUpKeyPool(unsigned int kpSize = 0); + void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); + void KeepKey(int64_t nIndex); + void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey); + bool GetKeyFromPool(CPubKey &key, bool internal = false); + int64_t GetOldestKeyPoolTime(); + /** + * Marks all keys in the keypool up to and including reserve_key as used. + */ + void MarkReserveKeysAsUsed(int64_t keypool_id); + const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; } + + std::set< std::set<CTxDestination> > GetAddressGroupings(); + std::map<CTxDestination, CAmount> GetAddressBalances(); + + std::set<CTxDestination> GetAccountAddresses(const std::string& strAccount) const; + + isminetype IsMine(const CTxIn& txin) const; + /** + * Returns amount of debit if the input matches the + * filter, otherwise returns 0 + */ + CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const; + isminetype IsMine(const CTxOut& txout) const; + CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const; + bool IsChange(const CTxOut& txout) const; + CAmount GetChange(const CTxOut& txout) const; + bool IsMine(const CTransaction& tx) const; + /** should probably be renamed to IsRelevantToMe */ + bool IsFromMe(const CTransaction& tx) const; + CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const; + /** Returns whether all of the inputs match the filter */ + bool IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const; + CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const; + CAmount GetChange(const CTransaction& tx) const; + void SetBestChain(const CBlockLocator& loc) override; + + DBErrors LoadWallet(bool& fFirstRunRet); + DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx); + DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut); + + bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose); + + bool DelAddressBook(const CTxDestination& address); + + const std::string& GetAccountName(const CScript& scriptPubKey) const; + + void Inventory(const uint256 &hash) override + { + { + LOCK(cs_wallet); + std::map<uint256, int>::iterator mi = mapRequestCount.find(hash); + if (mi != mapRequestCount.end()) + (*mi).second++; + } + } + + void GetScriptForMining(std::shared_ptr<CReserveScript> &script); + + unsigned int GetKeyPoolSize() + { + AssertLockHeld(cs_wallet); // set{Ex,In}ternalKeyPool + return setInternalKeyPool.size() + setExternalKeyPool.size(); + } + + //! signify that a particular wallet feature is now used. this may change nWalletVersion and nWalletMaxVersion if those are lower + bool SetMinVersion(enum WalletFeature, CWalletDB* pwalletdbIn = nullptr, bool fExplicit = false); + + //! change which version we're allowed to upgrade to (note that this does not immediately imply upgrading to that format) + bool SetMaxVersion(int nVersion); + + //! get the current wallet format (the oldest client version guaranteed to understand this wallet) + int GetVersion() { LOCK(cs_wallet); return nWalletVersion; } + + //! Get wallet transactions that conflict with given transaction (spend same outputs) + std::set<uint256> GetConflicts(const uint256& txid) const; + + //! Check if a given transaction has any of its outputs spent by another transaction in the wallet + bool HasWalletSpend(const uint256& txid) const; + + //! Flush wallet (bitdb flush) + void Flush(bool shutdown=false); + + /** + * Address book entry changed. + * @note called with lock cs_wallet held. + */ + boost::signals2::signal<void (CWallet *wallet, const CTxDestination + &address, const std::string &label, bool isMine, + const std::string &purpose, + ChangeType status)> NotifyAddressBookChanged; + + /** + * Wallet transaction added, removed or updated. + * @note called with lock cs_wallet held. + */ + boost::signals2::signal<void (CWallet *wallet, const uint256 &hashTx, + ChangeType status)> NotifyTransactionChanged; + + /** Show progress e.g. for rescan */ + boost::signals2::signal<void (const std::string &title, int nProgress)> ShowProgress; + + /** Watch-only address added */ + boost::signals2::signal<void (bool fHaveWatchOnly)> NotifyWatchonlyChanged; + + /** Inquire whether this wallet broadcasts transactions. */ + bool GetBroadcastTransactions() const { return fBroadcastTransactions; } + /** Set whether this wallet broadcasts transactions. */ + void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; } + + /** Return whether transaction can be abandoned */ + bool TransactionCanBeAbandoned(const uint256& hashTx) const; + + /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */ + bool AbandonTransaction(const uint256& hashTx); + + /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */ + bool MarkReplaced(const uint256& originalHash, const uint256& newHash); + + /* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */ + static CWallet* CreateWalletFromFile(const std::string walletFile); + + /** + * Wallet post-init setup + * Gives the wallet a chance to register repetitive tasks and complete post-init tasks + */ + void postInitProcess(CScheduler& scheduler); + + bool BackupWallet(const std::string& strDest); + + /* Set the HD chain model (chain child index counters) */ + bool SetHDChain(const CHDChain& chain, bool memonly); + const CHDChain& GetHDChain() const { return hdChain; } + + /* Returns true if HD is enabled */ + bool IsHDEnabled() const; + + /* Generates a new HD master key (will not be activated) */ + CPubKey GenerateNewHDMasterKey(); + + /* Set the current HD master key (will reset the chain child index counters) + Sets the master key's version based on the current wallet version (so the + caller must ensure the current wallet version is correct before calling + this function). */ + bool SetHDMasterKey(const CPubKey& key); + + /** + * Blocks until the wallet state is up-to-date to /at least/ the current + * chain at the time this function is entered + * Obviously holding cs_main/cs_wallet when going into this call may cause + * deadlock + */ + void BlockUntilSyncedToCurrentChain(); + + /** + * Explicitly make the wallet learn the related scripts for outputs to the + * given key. This is purely to make the wallet file compatible with older + * software, as CBasicKeyStore automatically does this implicitly for all + * keys now. + */ + void LearnRelatedScripts(const CPubKey& key, OutputType); + + /** + * Same as LearnRelatedScripts, but when the OutputType is not known (and could + * be anything). + */ + void LearnAllRelatedScripts(const CPubKey& key); + + /** + * Get a destination of the requested type (if possible) to the specified script. + * This function will automatically add the necessary scripts to the wallet. + */ + CTxDestination AddAndGetDestinationForScript(const CScript& script, OutputType); +}; + +/** A key allocated from the key pool. */ +class CReserveKey final : public CReserveScript +{ +protected: + CWallet* pwallet; + int64_t nIndex; + CPubKey vchPubKey; + bool fInternal; +public: + explicit CReserveKey(CWallet* pwalletIn) + { + nIndex = -1; + pwallet = pwalletIn; + fInternal = false; + } + + CReserveKey() = default; + CReserveKey(const CReserveKey&) = delete; + CReserveKey& operator=(const CReserveKey&) = delete; + + ~CReserveKey() + { + ReturnKey(); + } + + void ReturnKey(); + bool GetReservedKey(CPubKey &pubkey, bool internal = false); + void KeepKey(); + void KeepScript() override { KeepKey(); } +}; + + +/** + * Account information. + * Stored in wallet with key "acc"+string account name. + */ +class CAccount +{ +public: + CPubKey vchPubKey; + + CAccount() + { + SetNull(); + } + + void SetNull() + { + vchPubKey = CPubKey(); + } + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + int nVersion = s.GetVersion(); + if (!(s.GetType() & SER_GETHASH)) + READWRITE(nVersion); + READWRITE(vchPubKey); + } +}; + +// Helper for producing a bunch of max-sized low-S signatures (eg 72 bytes) +// ContainerType is meant to hold pair<CWalletTx *, int>, and be iterable +// so that each entry corresponds to each vIn, in order. +template <typename ContainerType> +bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins) const +{ + // Fill in dummy signatures for fee calculation. + int nIn = 0; + for (const auto& coin : coins) + { + const CScript& scriptPubKey = coin.txout.scriptPubKey; + SignatureData sigdata; + + if (!ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata)) + { + return false; + } else { + UpdateTransaction(txNew, nIn, sigdata); + } + + nIn++; + } + return true; +} + +OutputType ParseOutputType(const std::string& str, OutputType default_type = OUTPUT_TYPE_DEFAULT); +const std::string& FormatOutputType(OutputType type); + +/** + * Get a destination of the requested type (if possible) to the specified key. + * The caller must make sure LearnRelatedScripts has been called beforehand. + */ +CTxDestination GetDestinationForKey(const CPubKey& key, OutputType); + +/** Get all destinations (potentially) supported by the wallet for the given key. */ +std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key); + +/** RAII object to check and reserve a wallet rescan */ +class WalletRescanReserver +{ +private: + CWalletRef m_wallet; + bool m_could_reserve; +public: + explicit WalletRescanReserver(CWalletRef w) : m_wallet(w), m_could_reserve(false) {} + + bool reserve() + { + assert(!m_could_reserve); + std::lock_guard<std::mutex> lock(m_wallet->mutexScanning); + if (m_wallet->fScanningWallet) { + return false; + } + m_wallet->fScanningWallet = true; + m_could_reserve = true; + return true; + } + + bool isReserved() const + { + return (m_could_reserve && m_wallet->fScanningWallet); + } + + ~WalletRescanReserver() + { + std::lock_guard<std::mutex> lock(m_wallet->mutexScanning); + if (m_could_reserve) { + m_wallet->fScanningWallet = false; + } + } +}; + +#endif // BITCOIN_WALLET_WALLET_H diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp new file mode 100644 index 0000000000..dd6835a06f --- /dev/null +++ b/src/wallet/walletdb.cpp @@ -0,0 +1,858 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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 <wallet/walletdb.h> + +#include <base58.h> +#include <consensus/tx_verify.h> +#include <consensus/validation.h> +#include <fs.h> +#include <protocol.h> +#include <serialize.h> +#include <sync.h> +#include <util.h> +#include <utiltime.h> +#include <wallet/wallet.h> + +#include <atomic> + +#include <boost/thread.hpp> + +// +// CWalletDB +// + +bool CWalletDB::WriteName(const std::string& strAddress, const std::string& strName) +{ + return WriteIC(std::make_pair(std::string("name"), strAddress), strName); +} + +bool CWalletDB::EraseName(const std::string& strAddress) +{ + // This should only be used for sending addresses, never for receiving addresses, + // receiving addresses must always have an address book entry if they're not change return. + return EraseIC(std::make_pair(std::string("name"), strAddress)); +} + +bool CWalletDB::WritePurpose(const std::string& strAddress, const std::string& strPurpose) +{ + return WriteIC(std::make_pair(std::string("purpose"), strAddress), strPurpose); +} + +bool CWalletDB::ErasePurpose(const std::string& strAddress) +{ + return EraseIC(std::make_pair(std::string("purpose"), strAddress)); +} + +bool CWalletDB::WriteTx(const CWalletTx& wtx) +{ + return WriteIC(std::make_pair(std::string("tx"), wtx.GetHash()), wtx); +} + +bool CWalletDB::EraseTx(uint256 hash) +{ + return EraseIC(std::make_pair(std::string("tx"), hash)); +} + +bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta) +{ + if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta, false)) { + return false; + } + + // hash pubkey/privkey to accelerate wallet load + std::vector<unsigned char> vchKey; + vchKey.reserve(vchPubKey.size() + vchPrivKey.size()); + vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end()); + vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end()); + + return WriteIC(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false); +} + +bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey, + const std::vector<unsigned char>& vchCryptedSecret, + const CKeyMetadata &keyMeta) +{ + if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta)) { + return false; + } + + if (!WriteIC(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false)) { + return false; + } + EraseIC(std::make_pair(std::string("key"), vchPubKey)); + EraseIC(std::make_pair(std::string("wkey"), vchPubKey)); + return true; +} + +bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey) +{ + return WriteIC(std::make_pair(std::string("mkey"), nID), kMasterKey, true); +} + +bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript) +{ + return WriteIC(std::make_pair(std::string("cscript"), hash), redeemScript, false); +} + +bool CWalletDB::WriteWatchOnly(const CScript &dest, const CKeyMetadata& keyMeta) +{ + if (!WriteIC(std::make_pair(std::string("watchmeta"), dest), keyMeta)) { + return false; + } + return WriteIC(std::make_pair(std::string("watchs"), dest), '1'); +} + +bool CWalletDB::EraseWatchOnly(const CScript &dest) +{ + if (!EraseIC(std::make_pair(std::string("watchmeta"), dest))) { + return false; + } + return EraseIC(std::make_pair(std::string("watchs"), dest)); +} + +bool CWalletDB::WriteBestBlock(const CBlockLocator& locator) +{ + WriteIC(std::string("bestblock"), CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan + return WriteIC(std::string("bestblock_nomerkle"), locator); +} + +bool CWalletDB::ReadBestBlock(CBlockLocator& locator) +{ + if (batch.Read(std::string("bestblock"), locator) && !locator.vHave.empty()) return true; + return batch.Read(std::string("bestblock_nomerkle"), locator); +} + +bool CWalletDB::WriteOrderPosNext(int64_t nOrderPosNext) +{ + return WriteIC(std::string("orderposnext"), nOrderPosNext); +} + +bool CWalletDB::ReadPool(int64_t nPool, CKeyPool& keypool) +{ + return batch.Read(std::make_pair(std::string("pool"), nPool), keypool); +} + +bool CWalletDB::WritePool(int64_t nPool, const CKeyPool& keypool) +{ + return WriteIC(std::make_pair(std::string("pool"), nPool), keypool); +} + +bool CWalletDB::ErasePool(int64_t nPool) +{ + return EraseIC(std::make_pair(std::string("pool"), nPool)); +} + +bool CWalletDB::WriteMinVersion(int nVersion) +{ + return WriteIC(std::string("minversion"), nVersion); +} + +bool CWalletDB::ReadAccount(const std::string& strAccount, CAccount& account) +{ + account.SetNull(); + return batch.Read(std::make_pair(std::string("acc"), strAccount), account); +} + +bool CWalletDB::WriteAccount(const std::string& strAccount, const CAccount& account) +{ + return WriteIC(std::make_pair(std::string("acc"), strAccount), account); +} + +bool CWalletDB::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry) +{ + return WriteIC(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry); +} + +CAmount CWalletDB::GetAccountCreditDebit(const std::string& strAccount) +{ + std::list<CAccountingEntry> entries; + ListAccountCreditDebit(strAccount, entries); + + CAmount nCreditDebit = 0; + for (const CAccountingEntry& entry : entries) + nCreditDebit += entry.nCreditDebit; + + return nCreditDebit; +} + +void CWalletDB::ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries) +{ + bool fAllAccounts = (strAccount == "*"); + + Dbc* pcursor = batch.GetCursor(); + if (!pcursor) + throw std::runtime_error(std::string(__func__) + ": cannot create DB cursor"); + bool setRange = true; + while (true) + { + // Read next record + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + if (setRange) + ssKey << std::make_pair(std::string("acentry"), std::make_pair((fAllAccounts ? std::string("") : strAccount), uint64_t(0))); + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + int ret = batch.ReadAtCursor(pcursor, ssKey, ssValue, setRange); + setRange = false; + if (ret == DB_NOTFOUND) + break; + else if (ret != 0) + { + pcursor->close(); + throw std::runtime_error(std::string(__func__) + ": error scanning DB"); + } + + // Unserialize + std::string strType; + ssKey >> strType; + if (strType != "acentry") + break; + CAccountingEntry acentry; + ssKey >> acentry.strAccount; + if (!fAllAccounts && acentry.strAccount != strAccount) + break; + + ssValue >> acentry; + ssKey >> acentry.nEntryNo; + entries.push_back(acentry); + } + + pcursor->close(); +} + +class CWalletScanState { +public: + unsigned int nKeys; + unsigned int nCKeys; + unsigned int nWatchKeys; + unsigned int nKeyMeta; + bool fIsEncrypted; + bool fAnyUnordered; + int nFileVersion; + std::vector<uint256> vWalletUpgrade; + + CWalletScanState() { + nKeys = nCKeys = nWatchKeys = nKeyMeta = 0; + fIsEncrypted = false; + fAnyUnordered = false; + nFileVersion = 0; + } +}; + +bool +ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, + CWalletScanState &wss, std::string& strType, std::string& strErr) +{ + try { + // Unserialize + // Taking advantage of the fact that pair serialization + // is just the two items serialized one after the other + ssKey >> strType; + if (strType == "name") + { + std::string strAddress; + ssKey >> strAddress; + ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].name; + } + else if (strType == "purpose") + { + std::string strAddress; + ssKey >> strAddress; + ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].purpose; + } + else if (strType == "tx") + { + uint256 hash; + ssKey >> hash; + CWalletTx wtx; + ssValue >> wtx; + CValidationState state; + if (!(CheckTransaction(*wtx.tx, state) && (wtx.GetHash() == hash) && state.IsValid())) + return false; + + // Undo serialize changes in 31600 + if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703) + { + if (!ssValue.empty()) + { + char fTmp; + char fUnused; + ssValue >> fTmp >> fUnused >> wtx.strFromAccount; + strErr = strprintf("LoadWallet() upgrading tx ver=%d %d '%s' %s", + wtx.fTimeReceivedIsTxTime, fTmp, wtx.strFromAccount, hash.ToString()); + wtx.fTimeReceivedIsTxTime = fTmp; + } + else + { + strErr = strprintf("LoadWallet() repairing tx ver=%d %s", wtx.fTimeReceivedIsTxTime, hash.ToString()); + wtx.fTimeReceivedIsTxTime = 0; + } + wss.vWalletUpgrade.push_back(hash); + } + + if (wtx.nOrderPos == -1) + wss.fAnyUnordered = true; + + pwallet->LoadToWallet(wtx); + } + else if (strType == "acentry") + { + std::string strAccount; + ssKey >> strAccount; + uint64_t nNumber; + ssKey >> nNumber; + if (nNumber > pwallet->nAccountingEntryNumber) { + pwallet->nAccountingEntryNumber = nNumber; + } + + if (!wss.fAnyUnordered) + { + CAccountingEntry acentry; + ssValue >> acentry; + if (acentry.nOrderPos == -1) + wss.fAnyUnordered = true; + } + } + else if (strType == "watchs") + { + wss.nWatchKeys++; + CScript script; + ssKey >> script; + char fYes; + ssValue >> fYes; + if (fYes == '1') + pwallet->LoadWatchOnly(script); + } + else if (strType == "key" || strType == "wkey") + { + CPubKey vchPubKey; + ssKey >> vchPubKey; + if (!vchPubKey.IsValid()) + { + strErr = "Error reading wallet database: CPubKey corrupt"; + return false; + } + CKey key; + CPrivKey pkey; + uint256 hash; + + if (strType == "key") + { + wss.nKeys++; + ssValue >> pkey; + } else { + CWalletKey wkey; + ssValue >> wkey; + pkey = wkey.vchPrivKey; + } + + // Old wallets store keys as "key" [pubkey] => [privkey] + // ... which was slow for wallets with lots of keys, because the public key is re-derived from the private key + // using EC operations as a checksum. + // Newer wallets store keys as "key"[pubkey] => [privkey][hash(pubkey,privkey)], which is much faster while + // remaining backwards-compatible. + try + { + ssValue >> hash; + } + catch (...) {} + + bool fSkipCheck = false; + + if (!hash.IsNull()) + { + // hash pubkey/privkey to accelerate wallet load + std::vector<unsigned char> vchKey; + vchKey.reserve(vchPubKey.size() + pkey.size()); + vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end()); + vchKey.insert(vchKey.end(), pkey.begin(), pkey.end()); + + if (Hash(vchKey.begin(), vchKey.end()) != hash) + { + strErr = "Error reading wallet database: CPubKey/CPrivKey corrupt"; + return false; + } + + fSkipCheck = true; + } + + if (!key.Load(pkey, vchPubKey, fSkipCheck)) + { + strErr = "Error reading wallet database: CPrivKey corrupt"; + return false; + } + if (!pwallet->LoadKey(key, vchPubKey)) + { + strErr = "Error reading wallet database: LoadKey failed"; + return false; + } + } + else if (strType == "mkey") + { + unsigned int nID; + ssKey >> nID; + CMasterKey kMasterKey; + ssValue >> kMasterKey; + if(pwallet->mapMasterKeys.count(nID) != 0) + { + strErr = strprintf("Error reading wallet database: duplicate CMasterKey id %u", nID); + return false; + } + pwallet->mapMasterKeys[nID] = kMasterKey; + if (pwallet->nMasterKeyMaxID < nID) + pwallet->nMasterKeyMaxID = nID; + } + else if (strType == "ckey") + { + CPubKey vchPubKey; + ssKey >> vchPubKey; + if (!vchPubKey.IsValid()) + { + strErr = "Error reading wallet database: CPubKey corrupt"; + return false; + } + std::vector<unsigned char> vchPrivKey; + ssValue >> vchPrivKey; + wss.nCKeys++; + + if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey)) + { + strErr = "Error reading wallet database: LoadCryptedKey failed"; + return false; + } + wss.fIsEncrypted = true; + } + else if (strType == "keymeta") + { + CPubKey vchPubKey; + ssKey >> vchPubKey; + CKeyMetadata keyMeta; + ssValue >> keyMeta; + wss.nKeyMeta++; + pwallet->LoadKeyMetadata(vchPubKey.GetID(), keyMeta); + } + else if (strType == "watchmeta") + { + CScript script; + ssKey >> script; + CKeyMetadata keyMeta; + ssValue >> keyMeta; + wss.nKeyMeta++; + pwallet->LoadScriptMetadata(CScriptID(script), keyMeta); + } + else if (strType == "defaultkey") + { + // We don't want or need the default key, but if there is one set, + // we want to make sure that it is valid so that we can detect corruption + CPubKey vchPubKey; + ssValue >> vchPubKey; + if (!vchPubKey.IsValid()) { + strErr = "Error reading wallet database: Default Key corrupt"; + return false; + } + } + else if (strType == "pool") + { + int64_t nIndex; + ssKey >> nIndex; + CKeyPool keypool; + ssValue >> keypool; + + pwallet->LoadKeyPool(nIndex, keypool); + } + else if (strType == "version") + { + ssValue >> wss.nFileVersion; + if (wss.nFileVersion == 10300) + wss.nFileVersion = 300; + } + else if (strType == "cscript") + { + uint160 hash; + ssKey >> hash; + CScript script; + ssValue >> script; + if (!pwallet->LoadCScript(script)) + { + strErr = "Error reading wallet database: LoadCScript failed"; + return false; + } + } + else if (strType == "orderposnext") + { + ssValue >> pwallet->nOrderPosNext; + } + else if (strType == "destdata") + { + std::string strAddress, strKey, strValue; + ssKey >> strAddress; + ssKey >> strKey; + ssValue >> strValue; + if (!pwallet->LoadDestData(DecodeDestination(strAddress), strKey, strValue)) + { + strErr = "Error reading wallet database: LoadDestData failed"; + return false; + } + } + else if (strType == "hdchain") + { + CHDChain chain; + ssValue >> chain; + if (!pwallet->SetHDChain(chain, true)) + { + strErr = "Error reading wallet database: SetHDChain failed"; + return false; + } + } + } catch (...) + { + return false; + } + return true; +} + +bool CWalletDB::IsKeyType(const std::string& strType) +{ + return (strType== "key" || strType == "wkey" || + strType == "mkey" || strType == "ckey"); +} + +DBErrors CWalletDB::LoadWallet(CWallet* pwallet) +{ + CWalletScanState wss; + bool fNoncriticalErrors = false; + DBErrors result = DB_LOAD_OK; + + LOCK(pwallet->cs_wallet); + try { + int nMinVersion = 0; + if (batch.Read((std::string)"minversion", nMinVersion)) + { + if (nMinVersion > CLIENT_VERSION) + return DB_TOO_NEW; + pwallet->LoadMinVersion(nMinVersion); + } + + // Get cursor + Dbc* pcursor = batch.GetCursor(); + if (!pcursor) + { + LogPrintf("Error getting wallet database cursor\n"); + return DB_CORRUPT; + } + + while (true) + { + // Read next record + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + int ret = batch.ReadAtCursor(pcursor, ssKey, ssValue); + if (ret == DB_NOTFOUND) + break; + else if (ret != 0) + { + LogPrintf("Error reading next record from wallet database\n"); + return DB_CORRUPT; + } + + // Try to be tolerant of single corrupt records: + std::string strType, strErr; + if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr)) + { + // losing keys is considered a catastrophic error, anything else + // we assume the user can live with: + if (IsKeyType(strType) || strType == "defaultkey") + result = DB_CORRUPT; + else + { + // Leave other errors alone, if we try to fix them we might make things worse. + fNoncriticalErrors = true; // ... but do warn the user there is something wrong. + if (strType == "tx") + // Rescan if there is a bad transaction record: + gArgs.SoftSetBoolArg("-rescan", true); + } + } + if (!strErr.empty()) + LogPrintf("%s\n", strErr); + } + pcursor->close(); + } + catch (const boost::thread_interrupted&) { + throw; + } + catch (...) { + result = DB_CORRUPT; + } + + if (fNoncriticalErrors && result == DB_LOAD_OK) + result = DB_NONCRITICAL_ERROR; + + // Any wallet corruption at all: skip any rewriting or + // upgrading, we don't want to make it worse. + if (result != DB_LOAD_OK) + return result; + + LogPrintf("nFileVersion = %d\n", wss.nFileVersion); + + LogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total\n", + wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys); + + // nTimeFirstKey is only reliable if all keys have metadata + if ((wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta) + pwallet->UpdateTimeFirstKey(1); + + for (uint256 hash : wss.vWalletUpgrade) + WriteTx(pwallet->mapWallet[hash]); + + // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc: + if (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000)) + return DB_NEED_REWRITE; + + if (wss.nFileVersion < CLIENT_VERSION) // Update + WriteVersion(CLIENT_VERSION); + + if (wss.fAnyUnordered) + result = pwallet->ReorderTransactions(); + + pwallet->laccentries.clear(); + ListAccountCreditDebit("*", pwallet->laccentries); + for (CAccountingEntry& entry : pwallet->laccentries) { + pwallet->wtxOrdered.insert(make_pair(entry.nOrderPos, CWallet::TxPair(nullptr, &entry))); + } + + return result; +} + +DBErrors CWalletDB::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx) +{ + DBErrors result = DB_LOAD_OK; + + try { + int nMinVersion = 0; + if (batch.Read((std::string)"minversion", nMinVersion)) + { + if (nMinVersion > CLIENT_VERSION) + return DB_TOO_NEW; + } + + // Get cursor + Dbc* pcursor = batch.GetCursor(); + if (!pcursor) + { + LogPrintf("Error getting wallet database cursor\n"); + return DB_CORRUPT; + } + + while (true) + { + // Read next record + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + int ret = batch.ReadAtCursor(pcursor, ssKey, ssValue); + if (ret == DB_NOTFOUND) + break; + else if (ret != 0) + { + LogPrintf("Error reading next record from wallet database\n"); + return DB_CORRUPT; + } + + std::string strType; + ssKey >> strType; + if (strType == "tx") { + uint256 hash; + ssKey >> hash; + + CWalletTx wtx; + ssValue >> wtx; + + vTxHash.push_back(hash); + vWtx.push_back(wtx); + } + } + pcursor->close(); + } + catch (const boost::thread_interrupted&) { + throw; + } + catch (...) { + result = DB_CORRUPT; + } + + return result; +} + +DBErrors CWalletDB::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<uint256>& vTxHashOut) +{ + // build list of wallet TXs and hashes + std::vector<uint256> vTxHash; + std::vector<CWalletTx> vWtx; + DBErrors err = FindWalletTx(vTxHash, vWtx); + if (err != DB_LOAD_OK) { + return err; + } + + std::sort(vTxHash.begin(), vTxHash.end()); + std::sort(vTxHashIn.begin(), vTxHashIn.end()); + + // erase each matching wallet TX + bool delerror = false; + std::vector<uint256>::iterator it = vTxHashIn.begin(); + for (uint256 hash : vTxHash) { + while (it < vTxHashIn.end() && (*it) < hash) { + it++; + } + if (it == vTxHashIn.end()) { + break; + } + else if ((*it) == hash) { + if(!EraseTx(hash)) { + LogPrint(BCLog::DB, "Transaction was found for deletion but returned database error: %s\n", hash.GetHex()); + delerror = true; + } + vTxHashOut.push_back(hash); + } + } + + if (delerror) { + return DB_CORRUPT; + } + return DB_LOAD_OK; +} + +DBErrors CWalletDB::ZapWalletTx(std::vector<CWalletTx>& vWtx) +{ + // build list of wallet TXs + std::vector<uint256> vTxHash; + DBErrors err = FindWalletTx(vTxHash, vWtx); + if (err != DB_LOAD_OK) + return err; + + // erase each wallet TX + for (uint256& hash : vTxHash) { + if (!EraseTx(hash)) + return DB_CORRUPT; + } + + return DB_LOAD_OK; +} + +void MaybeCompactWalletDB() +{ + static std::atomic<bool> fOneThread(false); + if (fOneThread.exchange(true)) { + return; + } + if (!gArgs.GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) { + return; + } + + for (CWalletRef pwallet : vpwallets) { + CWalletDBWrapper& dbh = pwallet->GetDBHandle(); + + unsigned int nUpdateCounter = dbh.nUpdateCounter; + + if (dbh.nLastSeen != nUpdateCounter) { + dbh.nLastSeen = nUpdateCounter; + dbh.nLastWalletUpdate = GetTime(); + } + + if (dbh.nLastFlushed != nUpdateCounter && GetTime() - dbh.nLastWalletUpdate >= 2) { + if (CDB::PeriodicFlush(dbh)) { + dbh.nLastFlushed = nUpdateCounter; + } + } + } + + fOneThread = false; +} + +// +// Try to (very carefully!) recover wallet file if there is a problem. +// +bool CWalletDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename) +{ + return CDB::Recover(filename, callbackDataIn, recoverKVcallback, out_backup_filename); +} + +bool CWalletDB::Recover(const std::string& filename, std::string& out_backup_filename) +{ + // recover without a key filter callback + // results in recovering all record types + return CWalletDB::Recover(filename, nullptr, nullptr, out_backup_filename); +} + +bool CWalletDB::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDataStream ssValue) +{ + CWallet *dummyWallet = reinterpret_cast<CWallet*>(callbackData); + CWalletScanState dummyWss; + std::string strType, strErr; + bool fReadOK; + { + // Required in LoadKeyMetadata(): + LOCK(dummyWallet->cs_wallet); + fReadOK = ReadKeyValue(dummyWallet, ssKey, ssValue, + dummyWss, strType, strErr); + } + if (!IsKeyType(strType) && strType != "hdchain") + return false; + if (!fReadOK) + { + LogPrintf("WARNING: CWalletDB::Recover skipping %s: %s\n", strType, strErr); + return false; + } + + return true; +} + +bool CWalletDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr) +{ + return CDB::VerifyEnvironment(walletFile, walletDir, errorStr); +} + +bool CWalletDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr) +{ + return CDB::VerifyDatabaseFile(walletFile, walletDir, warningStr, errorStr, CWalletDB::Recover); +} + +bool CWalletDB::WriteDestData(const std::string &address, const std::string &key, const std::string &value) +{ + return WriteIC(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value); +} + +bool CWalletDB::EraseDestData(const std::string &address, const std::string &key) +{ + return EraseIC(std::make_pair(std::string("destdata"), std::make_pair(address, key))); +} + + +bool CWalletDB::WriteHDChain(const CHDChain& chain) +{ + return WriteIC(std::string("hdchain"), chain); +} + +bool CWalletDB::TxnBegin() +{ + return batch.TxnBegin(); +} + +bool CWalletDB::TxnCommit() +{ + return batch.TxnCommit(); +} + +bool CWalletDB::TxnAbort() +{ + return batch.TxnAbort(); +} + +bool CWalletDB::ReadVersion(int& nVersion) +{ + return batch.ReadVersion(nVersion); +} + +bool CWalletDB::WriteVersion(int nVersion) +{ + return batch.WriteVersion(nVersion); +} diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h new file mode 100644 index 0000000000..3691cfcb57 --- /dev/null +++ b/src/wallet/walletdb.h @@ -0,0 +1,254 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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_WALLETDB_H +#define BITCOIN_WALLET_WALLETDB_H + +#include <amount.h> +#include <primitives/transaction.h> +#include <wallet/db.h> +#include <key.h> + +#include <list> +#include <stdint.h> +#include <string> +#include <utility> +#include <vector> + +/** + * Overview of wallet database classes: + * + * - CDBEnv is an environment in which the database exists (has no analog in dbwrapper.h) + * - CWalletDBWrapper represents a wallet database (similar to CDBWrapper in dbwrapper.h) + * - CDB is a low-level database transaction (similar to CDBBatch in dbwrapper.h) + * - CWalletDB is a modifier object for the wallet, and encapsulates a database + * transaction as well as methods to act on the database (no analog in + * dbwrapper.h) + * + * The latter two are named confusingly, in contrast to what the names CDB + * and CWalletDB suggest they are transient transaction objects and don't + * represent the database itself. + */ + +static const bool DEFAULT_FLUSHWALLET = true; + +class CAccount; +class CAccountingEntry; +struct CBlockLocator; +class CKeyPool; +class CMasterKey; +class CScript; +class CWallet; +class CWalletTx; +class uint160; +class uint256; + +/** Error statuses for the wallet database */ +enum DBErrors +{ + DB_LOAD_OK, + DB_CORRUPT, + DB_NONCRITICAL_ERROR, + DB_TOO_NEW, + DB_LOAD_FAIL, + DB_NEED_REWRITE +}; + +/* simple HD chain data model */ +class CHDChain +{ +public: + uint32_t nExternalChainCounter; + uint32_t nInternalChainCounter; + CKeyID masterKeyID; //!< master key hash160 + + static const int VERSION_HD_BASE = 1; + static const int VERSION_HD_CHAIN_SPLIT = 2; + static const int CURRENT_VERSION = VERSION_HD_CHAIN_SPLIT; + int nVersion; + + CHDChain() { SetNull(); } + ADD_SERIALIZE_METHODS; + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(this->nVersion); + READWRITE(nExternalChainCounter); + READWRITE(masterKeyID); + if (this->nVersion >= VERSION_HD_CHAIN_SPLIT) + READWRITE(nInternalChainCounter); + } + + void SetNull() + { + nVersion = CHDChain::CURRENT_VERSION; + nExternalChainCounter = 0; + nInternalChainCounter = 0; + masterKeyID.SetNull(); + } +}; + +class CKeyMetadata +{ +public: + static const int VERSION_BASIC=1; + static const int VERSION_WITH_HDDATA=10; + static const int CURRENT_VERSION=VERSION_WITH_HDDATA; + int nVersion; + int64_t nCreateTime; // 0 means unknown + std::string hdKeypath; //optional HD/bip32 keypath + CKeyID hdMasterKeyID; //id of the HD masterkey used to derive this key + + CKeyMetadata() + { + SetNull(); + } + explicit CKeyMetadata(int64_t nCreateTime_) + { + SetNull(); + nCreateTime = nCreateTime_; + } + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(this->nVersion); + READWRITE(nCreateTime); + if (this->nVersion >= VERSION_WITH_HDDATA) + { + READWRITE(hdKeypath); + READWRITE(hdMasterKeyID); + } + } + + void SetNull() + { + nVersion = CKeyMetadata::CURRENT_VERSION; + nCreateTime = 0; + hdKeypath.clear(); + hdMasterKeyID.SetNull(); + } +}; + +/** Access to the wallet database. + * This should really be named CWalletDBBatch, as it represents a single transaction at the + * database. It will be committed when the object goes out of scope. + * Optionally (on by default) it will flush to disk as well. + */ +class CWalletDB +{ +private: + template <typename K, typename T> + bool WriteIC(const K& key, const T& value, bool fOverwrite = true) + { + if (!batch.Write(key, value, fOverwrite)) { + return false; + } + m_dbw.IncrementUpdateCounter(); + return true; + } + + template <typename K> + bool EraseIC(const K& key) + { + if (!batch.Erase(key)) { + return false; + } + m_dbw.IncrementUpdateCounter(); + return true; + } + +public: + explicit CWalletDB(CWalletDBWrapper& dbw, const char* pszMode = "r+", bool _fFlushOnClose = true) : + batch(dbw, pszMode, _fFlushOnClose), + m_dbw(dbw) + { + } + CWalletDB(const CWalletDB&) = delete; + CWalletDB& operator=(const CWalletDB&) = delete; + + bool WriteName(const std::string& strAddress, const std::string& strName); + bool EraseName(const std::string& strAddress); + + bool WritePurpose(const std::string& strAddress, const std::string& purpose); + bool ErasePurpose(const std::string& strAddress); + + bool WriteTx(const CWalletTx& wtx); + bool EraseTx(uint256 hash); + + bool WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata &keyMeta); + bool WriteCryptedKey(const CPubKey& vchPubKey, const std::vector<unsigned char>& vchCryptedSecret, const CKeyMetadata &keyMeta); + bool WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey); + + bool WriteCScript(const uint160& hash, const CScript& redeemScript); + + bool WriteWatchOnly(const CScript &script, const CKeyMetadata &keymeta); + bool EraseWatchOnly(const CScript &script); + + bool WriteBestBlock(const CBlockLocator& locator); + bool ReadBestBlock(CBlockLocator& locator); + + bool WriteOrderPosNext(int64_t nOrderPosNext); + + bool ReadPool(int64_t nPool, CKeyPool& keypool); + bool WritePool(int64_t nPool, const CKeyPool& keypool); + bool ErasePool(int64_t nPool); + + bool WriteMinVersion(int nVersion); + + /// 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 ReadAccount(const std::string& strAccount, CAccount& account); + bool WriteAccount(const std::string& strAccount, const CAccount& account); + + /// Write destination data key,value tuple to database + bool WriteDestData(const std::string &address, const std::string &key, const std::string &value); + /// Erase destination data tuple from wallet database + bool EraseDestData(const std::string &address, const std::string &key); + + CAmount GetAccountCreditDebit(const std::string& strAccount); + void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries); + + DBErrors LoadWallet(CWallet* pwallet); + DBErrors FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx); + DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx); + DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut); + /* Try to (very carefully!) recover wallet database (with a possible key type filter) */ + static bool Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename); + /* Recover convenience-function to bypass the key filter callback, called when verify fails, recovers everything */ + static bool Recover(const std::string& filename, std::string& out_backup_filename); + /* Recover filter (used as callback), will only let keys (cryptographical keys) as KV/key-type pass through */ + static bool RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDataStream ssValue); + /* Function to determine if a certain KV/key-type is a key (cryptographical key) type */ + static bool IsKeyType(const std::string& strType); + /* verifies the database environment */ + static bool VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr); + /* verifies the database file */ + static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr); + + //! write the hdchain model (external chain child index counter) + bool WriteHDChain(const CHDChain& chain); + + //! Begin a new transaction + bool TxnBegin(); + //! Commit current transaction + bool TxnCommit(); + //! Abort current transaction + bool TxnAbort(); + //! Read wallet version + bool ReadVersion(int& nVersion); + //! Write wallet version + bool WriteVersion(int nVersion); +private: + CDB batch; + CWalletDBWrapper& m_dbw; +}; + +//! Compacts BDB state so that wallet.dat is self-contained (if there are changes) +void MaybeCompactWalletDB(); + +#endif // BITCOIN_WALLET_WALLETDB_H diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp new file mode 100644 index 0000000000..7c97b668ae --- /dev/null +++ b/src/wallet/walletutil.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2017 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 <wallet/walletutil.h> + +fs::path GetWalletDir() +{ + fs::path path; + + if (gArgs.IsArgSet("-walletdir")) { + path = gArgs.GetArg("-walletdir", ""); + if (!fs::is_directory(path)) { + // If the path specified doesn't exist, we return the deliberately + // invalid empty string. + path = ""; + } + } else { + path = GetDataDir(); + // If a wallets directory exists, use that, otherwise default to GetDataDir + if (fs::is_directory(path / "wallets")) { + path /= "wallets"; + } + } + + return path; +} diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h new file mode 100644 index 0000000000..50ff736402 --- /dev/null +++ b/src/wallet/walletutil.h @@ -0,0 +1,14 @@ +// Copyright (c) 2017 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_UTIL_H +#define BITCOIN_WALLET_UTIL_H + +#include <chainparamsbase.h> +#include <util.h> + +//! Get the path of the wallet directory. +fs::path GetWalletDir(); + +#endif // BITCOIN_WALLET_UTIL_H |