elements/src/wallet/wallet.cpp

5898 lines
236 KiB
C++
Raw Normal View History

// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2020 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 <chain.h>
#include <consensus/consensus.h>
#include <consensus/validation.h>
#include <fs.h>
#include <interfaces/chain.h>
#include <interfaces/wallet.h>
#include <key.h>
#include <key_io.h>
2020-02-01 23:07:19 +02:00
#include <optional.h>
#include <pegins.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <script/descriptor.h>
#include <script/pegins.h>
#include <script/script.h>
#include <script/signingprovider.h>
#include <txmempool.h>
#include <util/bip32.h>
#include <util/check.h>
#include <util/error.h>
#include <util/fees.h>
scripted-diff: Move util files to separate directory. -BEGIN VERIFY SCRIPT- mkdir -p src/util git mv src/util.h src/util/system.h git mv src/util.cpp src/util/system.cpp git mv src/utilmemory.h src/util/memory.h git mv src/utilmoneystr.h src/util/moneystr.h git mv src/utilmoneystr.cpp src/util/moneystr.cpp git mv src/utilstrencodings.h src/util/strencodings.h git mv src/utilstrencodings.cpp src/util/strencodings.cpp git mv src/utiltime.h src/util/time.h git mv src/utiltime.cpp src/util/time.cpp sed -i 's/<util\.h>/<util\/system\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp') sed -i 's/<utilmemory\.h>/<util\/memory\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp') sed -i 's/<utilmoneystr\.h>/<util\/moneystr\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp') sed -i 's/<utilstrencodings\.h>/<util\/strencodings\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp') sed -i 's/<utiltime\.h>/<util\/time\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp') sed -i 's/BITCOIN_UTIL_H/BITCOIN_UTIL_SYSTEM_H/g' src/util/system.h sed -i 's/BITCOIN_UTILMEMORY_H/BITCOIN_UTIL_MEMORY_H/g' src/util/memory.h sed -i 's/BITCOIN_UTILMONEYSTR_H/BITCOIN_UTIL_MONEYSTR_H/g' src/util/moneystr.h sed -i 's/BITCOIN_UTILSTRENCODINGS_H/BITCOIN_UTIL_STRENCODINGS_H/g' src/util/strencodings.h sed -i 's/BITCOIN_UTILTIME_H/BITCOIN_UTIL_TIME_H/g' src/util/time.h sed -i 's/ util\.\(h\|cpp\)/ util\/system\.\1/g' src/Makefile.am sed -i 's/utilmemory\.\(h\|cpp\)/util\/memory\.\1/g' src/Makefile.am sed -i 's/utilmoneystr\.\(h\|cpp\)/util\/moneystr\.\1/g' src/Makefile.am sed -i 's/utilstrencodings\.\(h\|cpp\)/util\/strencodings\.\1/g' src/Makefile.am sed -i 's/utiltime\.\(h\|cpp\)/util\/time\.\1/g' src/Makefile.am sed -i 's/-> util ->/-> util\/system ->/' test/lint/lint-circular-dependencies.sh sed -i 's/src\/util\.cpp/src\/util\/system\.cpp/g' test/lint/lint-format-strings.py test/lint/lint-locale-dependence.sh sed -i 's/src\/utilmoneystr\.cpp/src\/util\/moneystr\.cpp/g' test/lint/lint-locale-dependence.sh sed -i 's/src\/utilstrencodings\.\(h\|cpp\)/src\/util\/strencodings\.\1/g' test/lint/lint-locale-dependence.sh sed -i 's/src\\utilstrencodings\.cpp/src\\util\\strencodings\.cpp/' build_msvc/libbitcoinconsensus/libbitcoinconsensus.vcxproj -END VERIFY SCRIPT-
2018-10-22 15:51:11 -07:00
#include <util/moneystr.h>
#include <util/rbf.h>
#include <util/string.h>
#include <util/translation.h>
#include <validation.h>
#include <wallet/coincontrol.h>
#include <wallet/fees.h>
#include <univalue.h>
#include <algorithm>
#include <assert.h>
#include <boost/algorithm/string/replace.hpp>
2019-03-20 11:01:36 +00:00
#include <blind.h>
#include <issuance.h>
#include <crypto/hmac_sha256.h>
#include <random.h>
2019-03-20 11:01:36 +00:00
using interfaces::FoundBlock;
const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS{
{WALLET_FLAG_AVOID_REUSE,
"You need to rescan the blockchain in order to correctly mark used "
"destinations in the past. Until this is done, some destinations may "
"be considered unused, even if the opposite is the case."
},
};
static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10;
RecursiveMutex cs_wallets;
static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets);
static std::list<LoadWalletFn> g_load_wallet_fns GUARDED_BY(cs_wallets);
bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name)
{
util::SettingsValue setting_value = chain.getRwSetting("wallet");
if (!setting_value.isArray()) setting_value.setArray();
for (const util::SettingsValue& value : setting_value.getValues()) {
if (value.isStr() && value.get_str() == wallet_name) return true;
}
setting_value.push_back(wallet_name);
return chain.updateRwSetting("wallet", setting_value);
}
bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name)
{
util::SettingsValue setting_value = chain.getRwSetting("wallet");
if (!setting_value.isArray()) return true;
util::SettingsValue new_value(util::SettingsValue::VARR);
for (const util::SettingsValue& value : setting_value.getValues()) {
if (!value.isStr() || value.get_str() != wallet_name) new_value.push_back(value);
}
if (new_value.size() == setting_value.size()) return true;
return chain.updateRwSetting("wallet", new_value);
}
static void UpdateWalletSetting(interfaces::Chain& chain,
const std::string& wallet_name,
Optional<bool> load_on_startup,
std::vector<bilingual_str>& warnings)
{
if (load_on_startup == nullopt) return;
if (load_on_startup.value() && !AddWalletSetting(chain, wallet_name)) {
warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may not be loaded next node startup."));
} else if (!load_on_startup.value() && !RemoveWalletSetting(chain, wallet_name)) {
warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may still be loaded next node startup."));
}
}
bool AddWallet(const std::shared_ptr<CWallet>& wallet)
{
LOCK(cs_wallets);
assert(wallet);
std::vector<std::shared_ptr<CWallet>>::const_iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet);
if (i != vpwallets.end()) return false;
vpwallets.push_back(wallet);
wallet->ConnectScriptPubKeyManNotifiers();
wallet->NotifyCanGetAddressesChanged();
return true;
}
bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on_start, std::vector<bilingual_str>& warnings)
{
assert(wallet);
interfaces::Chain& chain = wallet->chain();
std::string name = wallet->GetName();
// Unregister with the validation interface which also drops shared ponters.
wallet->m_chain_notifications_handler.reset();
LOCK(cs_wallets);
std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet);
if (i == vpwallets.end()) return false;
vpwallets.erase(i);
// Write the wallet setting
UpdateWalletSetting(chain, name, load_on_start, warnings);
return true;
}
bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on_start)
{
std::vector<bilingual_str> warnings;
return RemoveWallet(wallet, load_on_start, warnings);
}
std::vector<std::shared_ptr<CWallet>> GetWallets()
{
LOCK(cs_wallets);
return vpwallets;
}
std::shared_ptr<CWallet> GetWallet(const std::string& name)
{
LOCK(cs_wallets);
for (const std::shared_ptr<CWallet>& wallet : vpwallets) {
if (wallet->GetName() == name) return wallet;
}
return nullptr;
}
std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet)
{
LOCK(cs_wallets);
auto it = g_load_wallet_fns.emplace(g_load_wallet_fns.end(), std::move(load_wallet));
return interfaces::MakeHandler([it] { LOCK(cs_wallets); g_load_wallet_fns.erase(it); });
}
static Mutex g_loading_wallet_mutex;
static Mutex g_wallet_release_mutex;
static std::condition_variable g_wallet_release_cv;
static std::set<std::string> g_loading_wallet_set GUARDED_BY(g_loading_wallet_mutex);
static std::set<std::string> g_unloading_wallet_set GUARDED_BY(g_wallet_release_mutex);
2018-04-28 22:36:43 +01:00
// Custom deleter for shared_ptr<CWallet>.
static void ReleaseWallet(CWallet* wallet)
{
const std::string name = wallet->GetName();
wallet->WalletLogPrintf("Releasing wallet\n");
2018-04-28 22:36:43 +01:00
wallet->Flush();
delete wallet;
// Wallet is now released, notify UnloadWallet, if any.
{
LOCK(g_wallet_release_mutex);
if (g_unloading_wallet_set.erase(name) == 0) {
// UnloadWallet was not called for this wallet, all done.
return;
}
}
g_wallet_release_cv.notify_all();
}
void UnloadWallet(std::shared_ptr<CWallet>&& wallet)
{
// Mark wallet for unloading.
const std::string name = wallet->GetName();
{
LOCK(g_wallet_release_mutex);
auto it = g_unloading_wallet_set.insert(name);
assert(it.second);
}
// The wallet can be in use so it's not possible to explicitly unload here.
// Notify the unload intent so that all remaining shared pointers are
// released.
wallet->NotifyUnload();
// Time to ditch our shared_ptr and wait for ReleaseWallet call.
wallet.reset();
{
WAIT_LOCK(g_wallet_release_mutex, lock);
while (g_unloading_wallet_set.count(name) == 1) {
g_wallet_release_cv.wait(lock);
}
}
2018-04-28 22:36:43 +01:00
}
namespace {
std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
2019-01-12 11:47:04 +00:00
{
try {
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
if (!database) {
error = Untranslated("Wallet file verification failed.") + Untranslated(" ") + error;
return nullptr;
}
2019-01-12 11:47:04 +00:00
std::shared_ptr<CWallet> wallet = CWallet::Create(chain, name, std::move(database), options.create_flags, error, warnings);
if (!wallet) {
error = Untranslated("Wallet loading failed.") + Untranslated(" ") + error;
status = DatabaseStatus::FAILED_LOAD;
return nullptr;
}
AddWallet(wallet);
wallet->postInitProcess();
// Write the wallet setting
UpdateWalletSetting(chain, name, load_on_start, warnings);
return wallet;
} catch (const std::runtime_error& e) {
error = Untranslated(e.what());
status = DatabaseStatus::FAILED_LOAD;
2019-01-12 11:47:04 +00:00
return nullptr;
}
}
} // namespace
std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
auto result = WITH_LOCK(g_loading_wallet_mutex, return g_loading_wallet_set.insert(name));
if (!result.second) {
error = Untranslated("Wallet already being loading.");
status = DatabaseStatus::FAILED_LOAD;
return nullptr;
}
auto wallet = LoadWalletInternal(chain, name, load_on_start, options, status, error, warnings);
WITH_LOCK(g_loading_wallet_mutex, g_loading_wallet_set.erase(result.first));
return wallet;
}
2019-01-12 11:47:04 +00:00
std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
uint64_t wallet_creation_flags = options.create_flags;
const SecureString& passphrase = options.create_passphrase;
if (wallet_creation_flags & WALLET_FLAG_DESCRIPTORS) options.require_format = DatabaseFormat::SQLITE;
// Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted
bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET);
// Born encrypted wallets need to be created blank first.
if (!passphrase.empty()) {
wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET;
}
// Wallet::Verify will check if we're trying to create a wallet with a duplicate name.
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
if (!database) {
error = Untranslated("Wallet file verification failed.") + Untranslated(" ") + error;
status = DatabaseStatus::FAILED_VERIFY;
return nullptr;
}
// Do not allow a passphrase when private keys are disabled
if (!passphrase.empty() && (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
error = Untranslated("Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.");
status = DatabaseStatus::FAILED_CREATE;
return nullptr;
}
// Make the wallet
std::shared_ptr<CWallet> wallet = CWallet::Create(chain, name, std::move(database), wallet_creation_flags, error, warnings);
if (!wallet) {
error = Untranslated("Wallet creation failed.") + Untranslated(" ") + error;
status = DatabaseStatus::FAILED_CREATE;
return nullptr;
}
// Encrypt the wallet
if (!passphrase.empty() && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
if (!wallet->EncryptWallet(passphrase)) {
error = Untranslated("Error: Wallet created but failed to encrypt.");
status = DatabaseStatus::FAILED_ENCRYPT;
return nullptr;
}
if (!create_blank) {
// Unlock the wallet
if (!wallet->Unlock(passphrase)) {
error = Untranslated("Error: Wallet was encrypted but could not be unlocked");
status = DatabaseStatus::FAILED_ENCRYPT;
return nullptr;
}
// Set a seed for the wallet
{
LOCK(wallet->cs_wallet);
if (wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
wallet->SetupDescriptorScriptPubKeyMans();
} else {
for (auto spk_man : wallet->GetActiveScriptPubKeyMans()) {
if (!spk_man->SetupGeneration()) {
error = Untranslated("Unable to generate initial keys");
status = DatabaseStatus::FAILED_CREATE;
return nullptr;
}
}
}
}
// Relock the wallet
wallet->Lock();
}
}
AddWallet(wallet);
wallet->postInitProcess();
// Write the wallet settings
UpdateWalletSetting(chain, name, load_on_start, warnings);
status = DatabaseStatus::SUCCESS;
return wallet;
}
/** @defgroup mapWallet
*
* @{
*/
std::string COutput::ToString() const
{
2019-03-20 11:01:36 +00:00
return strprintf("COutput(%s, %d, %d) [%s] [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->GetOutputValueOut(i)), tx->GetOutputAsset(i).GetHex());
}
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
{
AssertLockHeld(cs_wallet);
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(hash);
if (it == mapWallet.end())
return nullptr;
return &(it->second);
}
void CWallet::UpgradeKeyMetadata()
{
if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) {
return;
}
auto spk_man = GetLegacyScriptPubKeyMan();
if (!spk_man) {
return;
}
spk_man->UpgradeKeyMetadata();
SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
}
bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys)
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
{
CCrypter crypter;
2016-11-10 08:00:05 +01:00
CKeyingMaterial _vMasterKey;
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
{
LOCK(cs_wallet);
for (const MasterKeyMap::value_type& pMasterKey : mapMasterKeys)
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
{
if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
2016-11-10 08:00:05 +01:00
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey))
continue; // try another master key
if (Unlock(_vMasterKey, accept_no_keys)) {
// Now that we've unlocked, upgrade the key metadata
UpgradeKeyMetadata();
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
return true;
}
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
}
}
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
return false;
}
bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase)
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
{
bool fWasLocked = IsLocked();
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
{
LOCK(cs_wallet);
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
Lock();
CCrypter crypter;
2016-11-10 08:00:05 +01:00
CKeyingMaterial _vMasterKey;
for (MasterKeyMap::value_type& pMasterKey : mapMasterKeys)
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
{
if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
2016-11-10 08:00:05 +01:00
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey))
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
return false;
if (Unlock(_vMasterKey))
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
{
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;
WalletLogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations);
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
2016-11-10 08:00:05 +01:00
if (!crypter.Encrypt(_vMasterKey, pMasterKey.second.vchCryptedKey))
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
return false;
WalletBatch(GetDatabase()).WriteMasterKey(pMasterKey.first, pMasterKey.second);
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
if (fWasLocked)
Lock();
return true;
}
}
}
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
return false;
}
void CWallet::chainStateFlushed(const CBlockLocator& loc)
2012-04-15 22:10:54 +02:00
{
WalletBatch batch(GetDatabase());
batch.WriteBestBlock(loc);
2012-04-15 22:10:54 +02:00
}
void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in)
2012-02-18 14:55:02 +01:00
{
LOCK(cs_wallet);
2012-02-18 14:55:02 +01:00
if (nWalletVersion >= nVersion)
return;
2012-02-18 14:55:02 +01:00
nWalletVersion = nVersion;
{
WalletBatch* batch = batch_in ? batch_in : new WalletBatch(GetDatabase());
2012-02-18 14:55:02 +01:00
if (nWalletVersion > 40000)
batch->WriteMinVersion(nWalletVersion);
if (!batch_in)
delete batch;
2012-02-18 14:55:02 +01:00
}
}
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);
2016-09-02 18:19:01 +02:00
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()
{
GetDatabase().Flush();
}
void CWallet::Close()
{
GetDatabase().Close();
}
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.at(it->second);
if (wtx->nOrderPos < nMinOrderPos) {
nMinOrderPos = wtx->nOrderPos;
copyFrom = wtx;
}
}
if (!copyFrom) {
return;
}
// 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.at(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;
// 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));
2018-05-25 14:27:58 +01:00
setLockedCoins.erase(outpoint);
std::pair<TxSpends::iterator, TxSpends::iterator> range;
range = mapTxSpends.equal_range(outpoint);
SyncMetaData(range);
}
void CWallet::AddToSpends(const uint256& wtxid)
{
2017-08-13 15:04:57 +01:00
auto it = mapWallet.find(wtxid);
assert(it != mapWallet.end());
const 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)
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
{
if (IsCrypted())
return false;
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
2016-11-10 08:00:05 +01:00
CKeyingMaterial _vMasterKey;
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
2016-11-10 08:00:05 +01:00
_vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
GetStrongRandBytes(&_vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
CMasterKey kMasterKey;
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
GetStrongRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
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;
WalletLogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod))
return false;
2016-11-10 08:00:05 +01:00
if (!crypter.Encrypt(_vMasterKey, kMasterKey.vchCryptedKey))
return false;
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
{
LOCK(cs_wallet);
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
WalletBatch* encrypted_batch = new WalletBatch(GetDatabase());
if (!encrypted_batch->TxnBegin()) {
delete encrypted_batch;
encrypted_batch = nullptr;
return false;
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
}
encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
for (const auto& spk_man_pair : m_spk_managers) {
auto spk_man = spk_man_pair.second.get();
if (!spk_man->Encrypt(_vMasterKey, encrypted_batch)) {
encrypted_batch->TxnAbort();
delete encrypted_batch;
encrypted_batch = nullptr;
// 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);
}
}
2012-02-18 14:55:02 +01:00
// Encryption was introduced in version 0.4.0
SetMinVersion(FEATURE_WALLETCRYPT, encrypted_batch);
2012-02-18 14:55:02 +01:00
if (!encrypted_batch->TxnCommit()) {
delete encrypted_batch;
encrypted_batch = nullptr;
// 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);
}
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
delete encrypted_batch;
encrypted_batch = nullptr;
Lock();
Unlock(strWalletPassphrase);
// If we are using descriptors, make new descriptors with a new seed
if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)) {
SetupDescriptorScriptPubKeyMans();
} else if (auto spk_man = GetLegacyScriptPubKeyMan()) {
// if we are using HD, replace the HD seed with a new one
if (spk_man->IsHDEnabled()) {
if (!spk_man->SetupGeneration(true)) {
return false;
}
}
}
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
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.
GetDatabase().Rewrite();
// 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:
GetDatabase().ReloadDbEnv();
}
NotifyStatusChanged(this);
2011-11-10 21:29:23 +01:00
Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet <passphrase> or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase <passphrase> <time to keep key in memory> first. A keypoolrefill command has been added which tops up the users keypool (requiring the passphrase via walletpassphrase first). keypoolsize has been added to the output of getinfo to show the user the number of keys left before they need to specify their passphrase (and call keypoolrefill). Note that walletpassphrase will automatically fill keypool in a separate thread which it spawns when the passphrase is set. This could cause some delays in other threads waiting for locks on the wallet passphrase, including one which could cause the passphrase to be stored longer than expected, however it will not allow the passphrase to be used longer than expected as ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon as the specified lock time has arrived. When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool returns vchDefaultKey, meaning miners may start to generate many blocks to vchDefaultKey instead of a new key each time. A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to allow the user to change their password via RPC. Whenever keying material (unencrypted private keys, the user's passphrase, the wallet's AES key) is stored unencrypted in memory, any reasonable attempt is made to mlock/VirtualLock that memory before storing the keying material. This is not true in several (commented) cases where mlock/VirtualLocking the memory is not possible. Although encryption of private keys in memory can be very useful on desktop systems (as some small amount of protection against stupid viruses), on an RPC server, the password is entered fairly insecurely. Thus, the only main advantage encryption has for RPC servers is for RPC servers that do not spend coins, except in rare cases, eg. a webserver of a merchant which only receives payment except for cases of manual intervention. Thanks to jgarzik for the original patch and sipa, gmaxwell and many others for all their input. Conflicts: src/wallet.cpp
2011-07-08 15:47:35 +02:00
return true;
}
DBErrors CWallet::ReorderTransactions()
{
LOCK(cs_wallet);
WalletBatch batch(GetDatabase());
// Old wallets didn't have any defined order for transactions
// Probably a bad idea to change the output of this
// First: get all CWalletTx into a sorted-by-time multimap.
typedef std::multimap<int64_t, CWalletTx*> TxItems;
TxItems txByTime;
for (auto& entry : mapWallet)
{
CWalletTx* wtx = &entry.second;
txByTime.insert(std::make_pair(wtx->nTimeReceived, wtx));
}
nOrderPosNext = 0;
std::vector<int64_t> nOrderPosOffsets;
for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it)
{
CWalletTx *const pwtx = (*it).second;
int64_t& nOrderPos = pwtx->nOrderPos;
if (nOrderPos == -1)
{
nOrderPos = nOrderPosNext++;
nOrderPosOffsets.push_back(nOrderPos);
if (!batch.WriteTx(*pwtx))
return DBErrors::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 (!batch.WriteTx(*pwtx))
return DBErrors::LOAD_FAIL;
}
}
batch.WriteOrderPosNext(nOrderPosNext);
scripted-diff: Convert 11 enums into scoped enums (C++11) -BEGIN VERIFY SCRIPT- sed -i 's/enum DBErrors/enum class DBErrors/g' src/wallet/walletdb.h git grep -l DB_ | xargs sed -i 's/DB_\(LOAD_OK\|CORRUPT\|NONCRITICAL_ERROR\|TOO_NEW\|LOAD_FAIL\|NEED_REWRITE\)/DBErrors::\1/g' sed -i 's/^ DBErrors::/ /g' src/wallet/walletdb.h sed -i 's/enum VerifyResult/enum class VerifyResult/g' src/wallet/db.h sed -i 's/\(VERIFY_OK\|RECOVER_OK\|RECOVER_FAIL\)/VerifyResult::\1/g' src/wallet/db.cpp sed -i 's/enum ThresholdState/enum class ThresholdState/g' src/versionbits.h git grep -l THRESHOLD_ | xargs sed -i 's/THRESHOLD_\(DEFINED\|STARTED\|LOCKED_IN\|ACTIVE\|FAILED\)/ThresholdState::\1/g' sed -i 's/^ ThresholdState::/ /g' src/versionbits.h sed -i 's/enum SigVersion/enum class SigVersion/g' src/script/interpreter.h git grep -l SIGVERSION_ | xargs sed -i 's/SIGVERSION_\(BASE\|WITNESS_V0\)/SigVersion::\1/g' sed -i 's/^ SigVersion::/ /g' src/script/interpreter.h sed -i 's/enum RetFormat {/enum class RetFormat {/g' src/rest.cpp sed -i 's/RF_\(UNDEF\|BINARY\|HEX\|JSON\)/RetFormat::\1/g' src/rest.cpp sed -i 's/^ RetFormat::/ /g' src/rest.cpp sed -i 's/enum HelpMessageMode {/enum class HelpMessageMode {/g' src/init.h git grep -l HMM_ | xargs sed -i 's/HMM_BITCOIN/HelpMessageMode::BITCOIN/g' sed -i 's/^ HelpMessageMode::/ /g' src/init.h sed -i 's/enum FeeEstimateHorizon/enum class FeeEstimateHorizon/g' src/policy/fees.h sed -i 's/enum RBFTransactionState/enum class RBFTransactionState/g' src/policy/rbf.h git grep -l RBF_ | xargs sed -i 's/RBF_TRANSACTIONSTATE_\(UNKNOWN\|REPLACEABLE_BIP125\|FINAL\)/RBFTransactionState::\1/g' sed -i 's/^ RBFTransactionState::/ /g' src/policy/rbf.h sed -i 's/enum BlockSource {/enum class BlockSource {/g' src/qt/clientmodel.h git grep -l BLOCK_SOURCE_ | xargs sed -i 's/BLOCK_SOURCE_\(NONE\|REINDEX\|DISK\|NETWORK\)/BlockSource::\1/g' sed -i 's/^ BlockSource::/ /g' src/qt/clientmodel.h sed -i 's/enum FlushStateMode {/enum class FlushStateMode {/g' src/validation.cpp sed -i 's/FLUSH_STATE_\(NONE\|IF_NEEDED\|PERIODIC\|ALWAYS\)/FlushStateMode::\1/g' src/validation.cpp sed -i 's/^ FlushStateMode::/ /g' src/validation.cpp sed -i 's/enum WitnessMode {/enum class WitnessMode {/g' src/test/script_tests.cpp sed -i 's/WITNESS_\(NONE\|PKH\|SH\)/WitnessMode::\1/g' src/test/script_tests.cpp sed -i 's/^ WitnessMode::/ /g' src/test/script_tests.cpp -END VERIFY SCRIPT-
2018-03-09 15:03:40 +01:00
return DBErrors::LOAD_OK;
}
int64_t CWallet::IncOrderPosNext(WalletBatch* batch)
{
AssertLockHeld(cs_wallet);
int64_t nRet = nOrderPosNext++;
if (batch) {
batch->WriteOrderPosNext(nOrderPosNext);
} else {
WalletBatch(GetDatabase()).WriteOrderPosNext(nOrderPosNext);
}
return nRet;
}
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();
WalletBatch batch(GetDatabase());
bool success = true;
if (!batch.WriteTx(wtx)) {
WalletLogPrintf("%s: Updating batch tx %s failed\n", __func__, wtx.GetHash().ToString());
success = false;
}
NotifyTransactionChanged(this, originalHash, CT_UPDATED);
return success;
}
void CWallet::SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations)
2018-09-13 13:53:19 +09:00
{
AssertLockHeld(cs_wallet);
2018-09-13 13:53:19 +09:00
const CWalletTx* srctx = GetWalletTx(hash);
if (!srctx) return;
CTxDestination dst;
if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) {
if (IsMine(dst)) {
2018-09-13 13:53:19 +09:00
if (used && !GetDestData(dst, "used", nullptr)) {
if (AddDestData(batch, dst, "used", "p")) { // p for "present", opposite of absent (null)
tx_destinations.insert(dst);
}
2018-09-13 13:53:19 +09:00
} else if (!used && GetDestData(dst, "used", nullptr)) {
EraseDestData(batch, dst, "used");
2018-09-13 13:53:19 +09:00
}
}
}
}
bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const
2018-09-13 13:53:19 +09:00
{
AssertLockHeld(cs_wallet);
2018-09-13 13:53:19 +09:00
const CWalletTx* srctx = GetWalletTx(hash);
if (srctx) {
assert(srctx->tx->vout.size() > n);
CTxDestination dest;
if (!ExtractDestination(srctx->tx->vout[n].scriptPubKey, dest)) {
return false;
}
if (GetDestData(dest, "used", nullptr)) {
return true;
}
if (IsLegacy()) {
LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
assert(spk_man != nullptr);
for (const auto& keyid : GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) {
WitnessV0KeyHash wpkh_dest(keyid);
if (GetDestData(wpkh_dest, "used", nullptr)) {
return true;
}
ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest));
if (GetDestData(sh_wpkh_dest, "used", nullptr)) {
return true;
}
PKHash pkh_dest(keyid);
if (GetDestData(pkh_dest, "used", nullptr)) {
return true;
}
}
}
}
return false;
2018-09-13 13:53:19 +09:00
}
CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmation& confirm, const UpdateWalletTxFn& update_wtx, bool fFlushOnClose)
{
LOCK(cs_wallet);
WalletBatch batch(GetDatabase(), fFlushOnClose);
uint256 hash = tx->GetHash();
2018-09-13 13:53:19 +09:00
if (IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
// Mark used destinations
std::set<CTxDestination> tx_destinations;
for (const CTxIn& txin : tx->vin) {
2018-09-13 13:53:19 +09:00
const COutPoint& op = txin.prevout;
SetSpentKeyState(batch, op.hash, op.n, true, tx_destinations);
2018-09-13 13:53:19 +09:00
}
MarkDestinationsDirty(tx_destinations);
2018-09-13 13:53:19 +09:00
}
// Inserts only if not already there, returns tx inserted or tx found
auto ret = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(this, tx));
CWalletTx& wtx = (*ret.first).second;
bool fInsertedNew = ret.second;
bool fUpdated = update_wtx && update_wtx(wtx, fInsertedNew);
if (fInsertedNew) {
wtx.m_confirm = confirm;
wtx.nTimeReceived = chain().getAdjustedTime();
wtx.nOrderPos = IncOrderPosNext(&batch);
wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
wtx.nTimeSmart = ComputeTimeSmart(wtx);
AddToSpends(hash);
}
if (!fInsertedNew)
{
if (confirm.status != wtx.m_confirm.status) {
wtx.m_confirm.status = confirm.status;
wtx.m_confirm.nIndex = confirm.nIndex;
wtx.m_confirm.hashBlock = confirm.hashBlock;
wtx.m_confirm.block_height = confirm.block_height;
fUpdated = true;
} else {
assert(wtx.m_confirm.nIndex == confirm.nIndex);
assert(wtx.m_confirm.hashBlock == confirm.hashBlock);
assert(wtx.m_confirm.block_height == confirm.block_height);
}
// 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 (tx->HasWitness() && !wtx.tx->HasWitness()) {
wtx.SetTx(tx);
fUpdated = true;
}
}
//// debug print
WalletLogPrintf("AddToWallet %s %s%s\n", hash.ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
// Write to disk
if (fInsertedNew || fUpdated)
if (!batch.WriteTx(wtx))
return nullptr;
// Break debit/credit balance caches:
wtx.MarkDirty();
// Notify UI of new or updated transaction
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
#if HAVE_SYSTEM
// 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", hash.GetHex());
#ifndef WIN32
// Substituting the wallet name isn't currently supported on windows
// because windows shell escaping has not been implemented yet:
// https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-537384875
// A few ways it could be implemented in the future are described in:
// https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-461288094
boost::replace_all(strCmd, "%w", ShellEscape(GetName()));
#endif
std::thread t(runCommand, strCmd);
t.detach(); // thread runs free
}
#endif
return &wtx;
}
bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx)
{
const auto& ins = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(this, nullptr));
CWalletTx& wtx = ins.first->second;
if (!fill_wtx(wtx, ins.second)) {
return false;
}
// If wallet doesn't have a chain (e.g wallet-tool), don't bother to update txn.
if (HaveChain()) {
bool active;
int height;
if (chain().findBlock(wtx.m_confirm.hashBlock, FoundBlock().inActiveChain(active).height(height)) && active) {
// Update cached block height variable since it not stored in the
// serialized transaction.
wtx.m_confirm.block_height = height;
} else if (wtx.isConflicted() || wtx.isConfirmed()) {
// If tx block (or conflicting block) was reorged out of chain
// while the wallet was shutdown, change tx status to UNCONFIRMED
// and reset block height, hash, and index. ABANDONED tx don't have
// associated blocks and don't need to be updated. The case where a
// transaction was reorged out while online and then reconfirmed
// while offline is covered by the rescan logic.
wtx.setUnconfirmed();
wtx.m_confirm.hashBlock = uint256();
wtx.m_confirm.block_height = 0;
wtx.m_confirm.nIndex = 0;
}
}
if (/* insertion took place */ ins.second) {
wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
}
AddToSpends(hash);
for (const CTxIn& txin : wtx.tx->vin) {
2017-08-13 15:04:57 +01:00
auto it = mapWallet.find(txin.prevout.hash);
if (it != mapWallet.end()) {
CWalletTx& prevtx = it->second;
if (prevtx.isConflicted()) {
MarkConflicted(prevtx.m_confirm.hashBlock, prevtx.m_confirm.block_height, wtx.GetHash());
}
}
}
return true;
}
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool fUpdate)
{
const CTransaction& tx = *ptx;
{
AssertLockHeld(cs_wallet);
if (!confirm.hashBlock.IsNull()) {
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()) {
WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), confirm.hashBlock.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n);
MarkConflicted(confirm.hashBlock, confirm.block_height, 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) {
for (const auto& spk_man_pair : m_spk_managers) {
spk_man_pair.second->MarkUnusedAddresses(txout.scriptPubKey);
}
}
// Block disconnection override an abandoned tx as unconfirmed
// which means user may have to call abandontransaction again
return AddToWallet(MakeTransactionRef(tx), confirm, /* update_wtx= */ nullptr, /* fFlushOnClose= */ false);
}
}
return false;
}
bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
{
LOCK(cs_wallet);
const CWalletTx* wtx = GetWalletTx(hashTx);
return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool();
}
void CWallet::MarkInputsDirty(const CTransactionRef& tx)
{
for (const CTxIn& txin : tx->vin) {
auto it = mapWallet.find(txin.prevout.hash);
if (it != mapWallet.end()) {
it->second.MarkDirty();
}
}
}
bool CWallet::AbandonTransaction(const uint256& hashTx)
{
LOCK(cs_wallet);
WalletBatch batch(GetDatabase());
std::set<uint256> todo;
std::set<uint256> done;
// Can't mark abandoned if confirmed or in mempool
2017-08-13 15:04:57 +01:00
auto it = mapWallet.find(hashTx);
assert(it != mapWallet.end());
const 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);
2017-08-13 15:04:57 +01:00
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.setAbandoned();
wtx.MarkDirty();
batch.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(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
MarkInputsDirty(wtx.tx);
}
}
return true;
}
void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx)
{
LOCK(cs_wallet);
int conflictconfirms = (m_last_block_processed_height - conflicting_height + 1) * -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
WalletBatch batch(GetDatabase(), 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);
2017-08-13 15:04:57 +01:00
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.m_confirm.nIndex = 0;
wtx.m_confirm.hashBlock = hashBlock;
wtx.m_confirm.block_height = conflicting_height;
wtx.setConflicted();
wtx.MarkDirty();
batch.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
MarkInputsDirty(wtx.tx);
}
}
}
void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool update_tx)
{
if (!AddToWalletIfInvolvingMe(ptx, confirm, update_tx))
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:
MarkInputsDirty(ptx);
}
void CWallet::transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) {
LOCK(cs_wallet);
SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
auto it = mapWallet.find(tx->GetHash());
if (it != mapWallet.end()) {
it->second.fInMempool = true;
}
}
void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) {
LOCK(cs_wallet);
auto it = mapWallet.find(tx->GetHash());
if (it != mapWallet.end()) {
it->second.fInMempool = false;
}
// Handle transactions that were removed from the mempool because they
// conflict with transactions in a newly connected block.
if (reason == MemPoolRemovalReason::CONFLICT) {
// Trigger external -walletnotify notifications for these transactions.
// Set Status::UNCONFIRMED instead of Status::CONFLICTED for a few reasons:
//
// 1. The transactionRemovedFromMempool callback does not currently
// provide the conflicting block's hash and height, and for backwards
// compatibility reasons it may not be not safe to store conflicted
// wallet transactions with a null block hash. See
// https://github.com/bitcoin/bitcoin/pull/18600#discussion_r420195993.
// 2. For most of these transactions, the wallet's internal conflict
// detection in the blockConnected handler will subsequently call
// MarkConflicted and update them with CONFLICTED status anyway. This
// applies to any wallet transaction that has inputs spent in the
// block, or that has ancestors in the wallet with inputs spent by
// the block.
// 3. Longstanding behavior since the sync implementation in
// https://github.com/bitcoin/bitcoin/pull/9371 and the prior sync
// implementation before that was to mark these transactions
// unconfirmed rather than conflicted.
//
// Nothing described above should be seen as an unchangeable requirement
// when improving this code in the future. The wallet's heuristics for
// distinguishing between conflicted and unconfirmed transactions are
// imperfect, and could be improved in general, see
// https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
}
}
void CWallet::blockConnected(const CBlock& block, int height)
{
const uint256& block_hash = block.GetHash();
LOCK(cs_wallet);
m_last_block_processed_height = height;
m_last_block_processed = block_hash;
for (size_t index = 0; index < block.vtx.size(); index++) {
SyncTransaction(block.vtx[index], {CWalletTx::Status::CONFIRMED, height, block_hash, (int)index});
transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::BLOCK, 0 /* mempool_sequence */);
}
}
void CWallet::blockDisconnected(const CBlock& block, int height)
{
LOCK(cs_wallet);
// At block disconnection, this will change an abandoned transaction to
// be unconfirmed, whether or not the transaction is added back to the mempool.
// User may have to call abandontransaction again. It may be addressed in the
// future with a stickier abandoned state or even removing abandontransaction call.
m_last_block_processed_height = height - 1;
m_last_block_processed = block.hashPrevBlock;
for (const CTransactionRef& ptx : block.vtx) {
SyncTransaction(ptx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
}
}
void CWallet::updatedBlockTip()
{
m_best_block_time = GetTime();
}
void CWallet::BlockUntilSyncedToCurrentChain() const {
AssertLockNotHeld(cs_wallet);
// Skip the queue-draining stuff if we know we're caught up with
// ::ChainActive().Tip(), 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).
uint256 last_block_hash = WITH_LOCK(cs_wallet, return m_last_block_processed);
chain().waitForNotificationsIfTipChanged(last_block_hash);
}
isminetype CWallet::IsMine(const CTxIn &txin) const
{
AssertLockHeld(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]);
}
2014-07-01 11:00:22 +02:00
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.
2019-03-20 11:01:36 +00:00
CAmountMap 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())
2019-03-20 11:01:36 +00:00
if (IsMine(prev.tx->vout[txin.prevout.n]) & filter) {
CAmountMap amounts;
amounts[prev.GetOutputAsset(txin.prevout.n)] = std::max<CAmount>(0, prev.GetOutputValueOut(txin.prevout.n));
return amounts;
}
}
}
2019-03-20 11:01:36 +00:00
return CAmountMap();
}
isminetype CWallet::IsMine(const CTxOut& txout) const
{
AssertLockHeld(cs_wallet);
return IsMine(txout.scriptPubKey);
}
isminetype CWallet::IsMine(const CTxDestination& dest) const
{
AssertLockHeld(cs_wallet);
return IsMine(GetScriptForDestination(dest));
}
isminetype CWallet::IsMine(const CScript& script) const
{
AssertLockHeld(cs_wallet);
isminetype result = ISMINE_NO;
for (const auto& spk_man_pair : m_spk_managers) {
result = std::max(result, spk_man_pair.second->IsMine(script));
}
return result;
}
2019-05-22 08:43:21 -04:00
CAmountMap CWallet::GetCredit(const CTransaction& tx, const size_t out_index, const isminefilter& filter) const
{
2019-05-22 08:43:21 -04:00
{
LOCK(cs_wallet);
std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(tx.GetHash());
if (mi != mapWallet.end())
{
const CWalletTx& wtx = (*mi).second;
if (out_index < wtx.tx->vout.size() && IsMine(wtx.tx->vout[out_index]) & filter) {
CAmountMap amounts;
amounts[wtx.GetOutputAsset(out_index)] = std::max<CAmount>(0, wtx.GetOutputValueOut(out_index));
return amounts;
}
}
2019-03-20 11:01:36 +00:00
}
2019-05-22 08:43:21 -04:00
return CAmountMap();
}
bool CWallet::IsChange(const CTxOut& txout) const
{
return IsChange(txout.scriptPubKey);
}
bool CWallet::IsChange(const CScript& script) 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).
AssertLockHeld(cs_wallet);
if (IsMine(script))
{
CTxDestination address;
if (!ExtractDestination(script, address))
return true;
if (!FindAddressBookEntry(address)) {
return true;
}
}
return false;
}
2019-03-20 11:01:36 +00:00
CAmountMap CWallet::GetChange(const CTxOut& txout) const
{
AssertLockHeld(cs_wallet);
2019-03-20 11:01:36 +00:00
CAmountMap change;
change[txout.nAsset.GetAsset()] = txout.nValue.GetAmount();
if (!MoneyRange(change))
throw std::runtime_error(std::string(__func__) + ": value out of range");
2019-03-20 11:01:36 +00:00
return (IsChange(txout) ? change : CAmountMap());
}
bool CWallet::IsMine(const CTransaction& tx) const
{
AssertLockHeld(cs_wallet);
for (const CTxOut& txout : tx.vout)
if (IsMine(txout))
return true;
return false;
}
bool CWallet::IsFromMe(const CTransaction& tx) const
{
2019-03-20 11:01:36 +00:00
return (GetDebit(tx, ISMINE_ALL) > CAmountMap());
}
2019-03-20 11:01:36 +00:00
CAmountMap CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) const
{
2019-03-20 11:01:36 +00:00
CAmountMap nDebit;
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;
}
2019-03-20 11:01:36 +00:00
CAmountMap CWallet::GetCredit(const CWalletTx& wtx, const isminefilter& filter) const {
CAmountMap nCredit;
for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
if (IsMine(wtx.tx->vout[i]) & filter) {
CAmount credit = std::max<CAmount>(0, wtx.GetOutputValueOut(i));
if (!MoneyRange(credit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
nCredit[wtx.GetOutputAsset(i)] += credit;
if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
}
return nCredit;
}
CAmountMap CWallet::GetChange(const CWalletTx& wtx) const {
CAmountMap nChange;
for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
if (IsChange(wtx.tx->vout[i])) {
CAmount change = wtx.GetOutputValueOut(i);
if (change < 0) {
continue;
}
if (!MoneyRange(change))
throw std::runtime_error(std::string(__func__) + ": value out of range");
nChange[wtx.GetOutputAsset(i)] += change;
if (!MoneyRange(nChange))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
}
return nChange;
}
CAmountMap CWallet::GetChange(const CTransaction& tx) const
{
LOCK(cs_wallet);
2019-03-20 11:01:36 +00:00
CAmountMap nChange;
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;
}
bool CWallet::IsHDEnabled() const
{
// All Active ScriptPubKeyMans must be HD for this to be true
bool result = true;
for (const auto& spk_man : GetActiveScriptPubKeyMans()) {
result &= spk_man->IsHDEnabled();
}
return result;
}
bool CWallet::CanGetAddresses(bool internal) const
{
LOCK(cs_wallet);
if (m_spk_managers.empty()) return false;
for (OutputType t : OUTPUT_TYPES) {
auto spk_man = GetScriptPubKeyMan(t, internal);
if (spk_man && spk_man->CanGetAddresses(internal)) {
return true;
}
}
return false;
}
void CWallet::SetWalletFlag(uint64_t flags)
{
LOCK(cs_wallet);
m_wallet_flags |= flags;
if (!WalletBatch(GetDatabase()).WriteWalletFlags(m_wallet_flags))
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
}
void CWallet::UnsetWalletFlag(uint64_t flag)
{
WalletBatch batch(GetDatabase());
UnsetWalletFlagWithDB(batch, flag);
}
void CWallet::UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag)
{
LOCK(cs_wallet);
m_wallet_flags &= ~flag;
if (!batch.WriteWalletFlags(m_wallet_flags))
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
}
void CWallet::UnsetBlankWalletFlag(WalletBatch& batch)
{
UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
}
2018-08-02 16:43:55 +09:00
bool CWallet::IsWalletFlagSet(uint64_t flag) const
{
return (m_wallet_flags & flag);
}
bool CWallet::LoadWalletFlags(uint64_t flags)
{
LOCK(cs_wallet);
if (((flags & KNOWN_WALLET_FLAGS) >> 32) ^ (flags >> 32)) {
// contains unknown non-tolerable wallet flags
return false;
}
m_wallet_flags = flags;
return true;
}
bool CWallet::AddWalletFlags(uint64_t flags)
{
LOCK(cs_wallet);
// We should never be writing unknown non-tolerable wallet flags
assert(((flags & KNOWN_WALLET_FLAGS) >> 32) == (flags >> 32));
if (!WalletBatch(GetDatabase()).WriteWalletFlags(flags)) {
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
}
return LoadWalletFlags(flags);
}
int64_t CWalletTx::GetTxTime() const
{
int64_t n = nTimeSmart;
return n ? n : nTimeReceived;
}
// Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
// or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true
static bool DummySignInput(const SigningProvider* provider, CMutableTransaction& tx, const size_t nIn, const CTxOut& txout, bool use_max_sig)
{
// Fill in dummy signatures for fee calculation.
const CScript& scriptPubKey = txout.scriptPubKey;
SignatureData sigdata;
if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
return false;
}
UpdateTransaction(tx, nIn, sigdata);
return true;
}
// Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes)
bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control) const
{
// Fill in dummy signatures for fee calculation.
int nIn = 0;
for (const auto& txout : txouts)
{
std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey);
// Use max sig if watch only inputs were used or if this particular input is an external input
bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || (coin_control && coin_control->IsExternalSelected(txNew.vin[nIn].prevout)));
if (!provider || !DummySignInput(provider.get(), txNew, nIn, txout, use_max_sig)) {
if (!coin_control || !DummySignInput(&coin_control->m_external_provider, txNew, nIn, txout, use_max_sig)) {
return false;
}
}
nIn++;
}
return true;
}
bool CWallet::ImportScripts(const std::set<CScript> scripts, int64_t timestamp)
{
auto spk_man = GetLegacyScriptPubKeyMan();
if (!spk_man) {
return false;
}
LOCK(spk_man->cs_KeyStore);
return spk_man->ImportScripts(scripts, timestamp);
}
bool CWallet::ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp)
{
auto spk_man = GetLegacyScriptPubKeyMan();
if (!spk_man) {
return false;
}
LOCK(spk_man->cs_KeyStore);
return spk_man->ImportPrivKeys(privkey_map, timestamp);
}
bool CWallet::ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp)
{
auto spk_man = GetLegacyScriptPubKeyMan();
if (!spk_man) {
return false;
}
LOCK(spk_man->cs_KeyStore);
return spk_man->ImportPubKeys(ordered_pubkeys, pubkey_map, key_origins, add_keypool, internal, timestamp);
}
bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp)
{
auto spk_man = GetLegacyScriptPubKeyMan();
if (!spk_man) {
return false;
}
LOCK(spk_man->cs_KeyStore);
if (!spk_man->ImportScriptPubKeys(script_pub_keys, have_solving_data, timestamp)) {
return false;
}
if (apply_label) {
WalletBatch batch(GetDatabase());
for (const CScript& script : script_pub_keys) {
CTxDestination dest;
ExtractDestination(script, dest);
if (IsValidDestination(dest)) {
SetAddressBookWithDB(batch, dest, label, "receive");
}
}
}
return true;
}
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const CCoinControl* coin_control)
{
std::vector<CTxOut> txouts;
// Look up the inputs. The inputs are either in the wallet, or in coin_control.
for (const CTxIn& input : tx.vin) {
const auto mi = wallet->mapWallet.find(input.prevout.hash);
if (mi != wallet->mapWallet.end()) {
assert(input.prevout.n < mi->second.tx->vout.size());
txouts.emplace_back(mi->second.tx->vout[input.prevout.n]);
} else if (coin_control) {
CTxOut txout;
if (!coin_control->GetExternalOutput(input.prevout, txout)) {
return -1;
}
txouts.emplace_back(txout);
} else {
return -1;
}
}
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
}
// txouts needs to be in the order of tx.vin
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control)
{
CMutableTransaction txNew(tx);
if (!wallet->DummySignTx(txNew, txouts, coin_control)) {
return -1;
}
return GetVirtualTransactionSize(CTransaction(txNew));
}
int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig) {
CMutableTransaction txn;
txn.vin.push_back(CTxIn(COutPoint()));
if (!provider || !DummySignInput(provider, txn, 0, txout, use_max_sig)) {
return -1;
}
return GetVirtualTransactionInputSize(CTransaction(txn));
}
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig)
{
std::unique_ptr<SigningProvider> provider = wallet->GetSolvingProvider(txout.scriptPubKey);
return CalculateMaximumSignedInputSize(txout, provider.get(), use_max_sig);
}
void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) const
{
nFee = 0;
listReceived.clear();
listSent.clear();
// Compute fee:
2019-03-20 11:01:36 +00:00
CAmountMap mapDebit = GetDebit(filter);
if (mapDebit > CAmountMap()) // debit>0 means we signed/sent this transaction
{
2019-03-20 11:01:36 +00:00
nFee = -GetFeeMap(*tx)[::policyAsset];
}
LOCK(pwallet->cs_wallet);
// Sent/received.
for (unsigned int i = 0; i < tx->vout.size(); ++i)
{
const CTxOut& txout = tx->vout[i];
2019-03-20 11:01:36 +00:00
CAmount output_value = GetOutputValueOut(i);
// Don't list unknown assets
isminetype fIsMine = output_value != -1 ? pwallet->IsMine(txout) : ISMINE_NO;
// 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)
2019-03-20 11:01:36 +00:00
if (mapDebit > CAmountMap())
{
// 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;
2015-12-12 10:45:53 -08:00
if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable())
{
pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
this->GetHash().ToString());
address = CNoDestination();
}
2019-03-20 11:01:36 +00:00
COutputEntry output = {address, output_value, (int)i, GetOutputAsset(i), GetOutputAmountBlindingFactor(i), GetOutputAssetBlindingFactor(i)};
2014-05-30 00:54:00 +02:00
// If we are debited by the transaction, add the output as a "sent" entry
2019-03-20 11:01:36 +00:00
if (mapDebit > CAmountMap() && !txout.IsFee())
2014-05-30 00:54:00 +02:00
listSent.push_back(output);
// If we are receiving the output, add it as a "received" entry
if (fIsMine & filter)
2014-05-30 00:54:00 +02:00
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.
int start_height = 0;
uint256 start_block;
bool start = chain().findFirstBlockWithTimeAndHeight(startTime - TIMESTAMP_WINDOW, 0, FoundBlock().hash(start_block).height(start_height));
WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, start ? WITH_LOCK(cs_wallet, return GetLastBlockHeight()) - start_height + 1 : 0);
if (start) {
// TODO: this should take into account failure by ScanResult::USER_ABORT
ScanResult result = ScanForWalletTransactions(start_block, start_height, {} /* max_height */, reserver, update);
if (result.status == ScanResult::FAILURE) {
int64_t time_max;
CHECK_NONFATAL(chain().findBlock(result.last_failed_block, FoundBlock().maxTime(time_max)));
return time_max + TIMESTAMP_WINDOW + 1;
}
}
return startTime;
}
/**
* Scan the block chain (starting in start_block) for transactions
* from or to us. If fUpdate is true, found transactions that already
* exist in the wallet will be updated.
*
* @param[in] start_block Scan starting block. If block is not on the active
* chain, the scan will return SUCCESS immediately.
* @param[in] start_height Height of start_block
* @param[in] max_height Optional max scanning height. If unset there is
* no maximum and scanning can continue to the tip
*
* @return ScanResult returning scan information and indicating success or
* failure. Return status will be set to SUCCESS if scan was
* successful. FAILURE if a complete rescan was not possible (due to
* pruning or corruption). USER_ABORT if the rescan was aborted before
* it could complete.
*
* @pre Caller needs to make sure start_block (and the optional stop_block) are on
* the main chain after to the addition of any new keys you want to detect
* transactions for.
*/
CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, int start_height, Optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate)
{
2014-02-17 16:35:37 -08:00
int64_t nNow = GetTime();
2019-04-27 10:15:56 -04:00
int64_t start_time = GetTimeMillis();
assert(reserver.isReserved());
uint256 block_hash = start_block;
ScanResult result;
2018-04-09 18:48:19 +02:00
WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString());
2018-04-09 18:48:19 +02:00
2019-04-27 10:16:33 -04:00
fAbortRescan = false;
ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
uint256 tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash());
uint256 end_hash = tip_hash;
if (max_height) chain().findAncestorByHeight(tip_hash, *max_height, FoundBlock().hash(end_hash));
double progress_begin = chain().guessVerificationProgress(block_hash);
double progress_end = chain().guessVerificationProgress(end_hash);
2019-04-27 10:16:33 -04:00
double progress_current = progress_begin;
int block_height = start_height;
while (!fAbortRescan && !chain().shutdownRequested()) {
if (progress_end - progress_begin > 0.0) {
m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin);
} else { // avoid divide-by-zero for single block scan range (i.e. start and stop hashes are equal)
m_scanning_progress = 0;
}
if (block_height % 100 == 0 && progress_end - progress_begin > 0.0) {
ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100))));
2019-04-27 10:16:33 -04:00
}
if (GetTime() >= nNow + 60) {
nNow = GetTime();
WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current);
}
// Read block data
2019-04-27 10:16:33 -04:00
CBlock block;
chain().findBlock(block_hash, FoundBlock().data(block));
// Find next block separately from reading data above, because reading
// is slow and there might be a reorg while it is read.
bool block_still_active = false;
bool next_block = false;
uint256 next_block_hash;
chain().findBlock(block_hash, FoundBlock().inActiveChain(block_still_active).nextBlock(FoundBlock().inActiveChain(next_block).hash(next_block_hash)));
if (!block.IsNull()) {
2019-04-27 10:16:33 -04:00
LOCK(cs_wallet);
if (!block_still_active) {
2019-04-27 10:16:33 -04:00
// Abort scan if current block is no longer active, to prevent
// marking transactions as coming from the wrong block.
result.last_failed_block = block_hash;
result.status = ScanResult::FAILURE;
2019-04-27 10:16:33 -04:00
break;
}
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
SyncTransaction(block.vtx[posInBlock], {CWalletTx::Status::CONFIRMED, block_height, block_hash, (int)posInBlock}, fUpdate);
}
2019-04-27 10:16:33 -04:00
// scan succeeded, record block as most recent successfully scanned
result.last_scanned_block = block_hash;
result.last_scanned_height = block_height;
2019-04-27 10:16:33 -04:00
} else {
// could not scan block, keep scanning but record this block as the most recent failure
result.last_failed_block = block_hash;
result.status = ScanResult::FAILURE;
}
if (max_height && block_height >= *max_height) {
2019-04-27 10:16:33 -04:00
break;
}
{
if (!next_block) {
2019-04-27 10:16:33 -04:00
// break successfully when rescan has reached the tip, or
// previous block is no longer on the chain due to a reorg
break;
}
2019-04-27 10:16:33 -04:00
// increment block and verification progress
block_hash = next_block_hash;
++block_height;
2019-04-27 10:16:33 -04:00
progress_current = chain().guessVerificationProgress(block_hash);
2019-04-27 10:16:33 -04:00
// handle updated tip hash
const uint256 prev_tip_hash = tip_hash;
tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash());
if (!max_height && prev_tip_hash != tip_hash) {
2019-04-27 10:16:33 -04:00
// in case the tip has changed, update progress max
progress_end = chain().guessVerificationProgress(tip_hash);
}
}
2019-04-27 10:16:33 -04:00
}
ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 100); // hide progress dialog in GUI
2019-04-27 10:16:33 -04:00
if (block_height && fAbortRescan) {
WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", block_height, progress_current);
2019-04-27 10:16:33 -04:00
result.status = ScanResult::USER_ABORT;
} else if (block_height && chain().shutdownRequested()) {
WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height, progress_current);
2019-04-27 10:16:33 -04:00
result.status = ScanResult::USER_ABORT;
} else {
WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - start_time);
}
return result;
}
void CWallet::ReacceptWalletTransactions()
{
2015-04-28 14:48:28 +00:00
// If transactions aren't being broadcasted, don't let them into local mempool either
if (!fBroadcastTransactions)
return;
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 (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) {
CWalletTx& wtx = *(item.second);
std::string unused_err_string;
wtx.SubmitMemoryPoolAndRelay(unused_err_string, false);
}
}
bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay)
{
// Can't relay if wallet is not broadcasting
if (!pwallet->GetBroadcastTransactions()) return false;
// Don't relay abandoned transactions
if (isAbandoned()) return false;
// Don't try to submit coinbase transactions. These would fail anyway but would
// cause log spam.
if (IsCoinBase()) return false;
// Don't try to submit conflicted or confirmed transactions.
if (GetDepthInMainChain() != 0) return false;
// Submit transaction to mempool for relay
pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString());
// 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 this newly generated transaction's change is
// unavailable as we're not yet aware that it is in the mempool.
//
// Irrespective of the failure reason, un-marking fInMempool
// out-of-order is incorrect - it should be unmarked when
// TransactionRemovedFromMempool fires.
bool ret = pwallet->chain().broadcastTransaction(tx, pwallet->m_default_max_tx_fee, relay, err_string);
fInMempool |= ret;
return ret;
}
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;
}
CAmountMap CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate) const
{
auto& amount = m_amounts[type];
if (recalculate || !amount.m_cached[filter]) {
amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*this, filter));
m_is_cache_empty = false;
}
return amount.m_value[filter];
}
2019-03-20 11:01:36 +00:00
CAmountMap CWalletTx::GetDebit(const isminefilter& filter) const
{
if (tx->vin.empty())
2019-03-20 11:01:36 +00:00
return CAmountMap();
2019-03-20 11:01:36 +00:00
CAmountMap debit;
if (filter & ISMINE_SPENDABLE) {
debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE);
}
if (filter & ISMINE_WATCH_ONLY) {
debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY);
}
return debit;
}
CAmountMap CWalletTx::GetCredit(const isminefilter& filter) const
{
// Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsImmatureCoinBase())
2019-03-20 11:01:36 +00:00
return CAmountMap();
2019-03-20 11:01:36 +00:00
CAmountMap credit;
if (filter & ISMINE_SPENDABLE) {
// GetBalance can assume transactions in mapWallet won't change
credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE);
}
if (filter & ISMINE_WATCH_ONLY) {
credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY);
}
return credit;
}
CAmountMap CWalletTx::GetImmatureCredit(bool fUseCache) const
{
if (IsImmatureCoinBase() && IsInMainChain()) {
return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache);
}
2019-03-20 11:01:36 +00:00
return CAmountMap();
}
CAmountMap CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const
{
if (pwallet == nullptr)
2019-03-20 11:01:36 +00:00
return CAmountMap();
// Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future).
2018-09-13 13:53:19 +09:00
bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL;
// Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsImmatureCoinBase())
2019-03-20 11:01:36 +00:00
return CAmountMap();
if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) {
return m_amounts[AVAILABLE_CREDIT].m_value[filter];
}
2018-09-13 13:53:19 +09:00
bool allow_used_addresses = (filter & ISMINE_USED) || !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
2019-03-20 11:01:36 +00:00
CAmountMap nCredit;
uint256 hashTx = GetHash();
for (unsigned int i = 0; i < tx->vout.size(); i++)
{
if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsSpentKey(hashTx, i))) {
2019-03-20 11:01:36 +00:00
if (pwallet->IsMine(tx->vout[i]) & filter) {
CAmount credit = std::max<CAmount>(0, GetOutputValueOut(i));
if (!MoneyRange(credit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
nCredit[GetOutputAsset(i)] += std::max<CAmount>(0, GetOutputValueOut(i));
if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
}
}
if (allow_cache) {
m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit);
m_is_cache_empty = false;
}
return nCredit;
}
CAmountMap CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const
{
if (IsImmatureCoinBase() && IsInMainChain()) {
return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache);
}
2019-03-20 11:01:36 +00:00
return CAmountMap();
}
2019-03-20 11:01:36 +00:00
CAmountMap CWalletTx::GetChange() const
{
if (fChangeCached)
return nChangeCached;
2019-03-20 11:01:36 +00:00
nChangeCached = pwallet->GetChange(*this);
fChangeCached = true;
return nChangeCached;
}
bool CWalletTx::InMempool() const
{
return fInMempool;
}
bool CWalletTx::IsTrusted() const
2019-08-30 13:25:41 -07:00
{
std::set<uint256> trusted_parents;
LOCK(pwallet->cs_wallet);
return pwallet->IsTrusted(*this, trusted_parents);
2019-08-30 13:25:41 -07:00
}
bool CWallet::IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const
{
AssertLockHeld(cs_wallet);
// Quick answer in most cases
if (!chain().checkFinalTx(*wtx.tx)) return false;
int nDepth = wtx.GetDepthInMainChain();
if (nDepth >= 1) return true;
if (nDepth < 0) return false;
// using wtx's cached debit
if (!m_spend_zero_conf_change || !wtx.IsFromMe(ISMINE_ALL)) return false;
// Don't trust unconfirmed transactions from us unless they are in the mempool.
if (!wtx.InMempool()) return false;
// Trusted if all inputs are from us and are in the mempool:
for (const CTxIn& txin : wtx.tx->vin)
{
// Transactions not sent by us: not trusted
const CWalletTx* parent = GetWalletTx(txin.prevout.hash);
if (parent == nullptr) return false;
const CTxOut& parentOut = parent->tx->vout[txin.prevout.n];
// Check that this specific input being spent is trusted
if (IsMine(parentOut) != ISMINE_SPENDABLE) return false;
2019-08-30 13:25:41 -07:00
// If we've already trusted this parent, continue
if (trusted_parents.count(parent->GetHash())) continue;
// Recurse to check that the parent is also trusted
if (!IsTrusted(*parent, trusted_parents)) return false;
trusted_parents.insert(parent->GetHash());
}
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);
}
CAmountMap CWalletTx::GetIssuanceAssets(unsigned int input_index) const {
CAmountMap ret;
CAsset asset, token;
GetIssuanceAssets(input_index, &asset, &token);
if (!asset.IsNull()) {
ret[asset] = GetIssuanceAmount(input_index, false);
}
if (!token.IsNull()) {
ret[token] = GetIssuanceAmount(input_index, true);
}
return ret;
}
// Rebroadcast transactions from the wallet. We do this on a random timer
// to slightly obfuscate which transactions come from our wallet.
//
// Ideally, we'd only resend transactions that we think should have been
// mined in the most recent block. Any transaction that wasn't in the top
// blockweight of transactions in the mempool shouldn't have been mined,
// and so is probably just sitting in the mempool waiting to be confirmed.
// Rebroadcasting does nothing to speed up confirmation and only damages
// privacy.
void CWallet::ResendWalletTransactions()
{
// During reindex, importing and IBD, old wallet transactions become
// unconfirmed. Don't resend them as that would spam other nodes.
if (!chain().isReadyToBroadcast()) return;
// Do this infrequently and randomly to avoid giving away
// that these are our transactions.
if (GetTime() < nNextResend || !fBroadcastTransactions) return;
bool fFirst = (nNextResend == 0);
// resend 12-36 hours from now, ~1 day on average.
nNextResend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60);
if (fFirst) return;
int submitted_tx_count = 0;
{ // cs_wallet scope
LOCK(cs_wallet);
// Relay transactions
for (std::pair<const uint256, CWalletTx>& item : mapWallet) {
CWalletTx& wtx = item.second;
// Attempt to rebroadcast all txes more than 5 minutes older than
// the last block. SubmitMemoryPoolAndRelay() will not rebroadcast
// any confirmed or conflicting txs.
if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
std::string unused_err_string;
if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) ++submitted_tx_count;
}
} // cs_wallet
if (submitted_tx_count > 0) {
WalletLogPrintf("%s: resubmit %u unconfirmed transactions\n", __func__, submitted_tx_count);
}
}
/** @} */ // end of mapWallet
void MaybeResendWalletTxs()
{
for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
pwallet->ResendWalletTransactions();
}
}
/** @defgroup Actions
*
* @{
*/
2018-09-13 13:53:19 +09:00
CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) const
{
2019-03-11 16:12:58 -04:00
Balance ret;
isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED;
{
LOCK(cs_wallet);
std::set<uint256> trusted_parents;
for (const auto& entry : mapWallet)
{
const CWalletTx& wtx = entry.second;
const bool is_trusted{IsTrusted(wtx, trusted_parents)};
const int tx_depth{wtx.GetDepthInMainChain()};
const CAmountMap tx_credit_mine{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
const CAmountMap tx_credit_watchonly{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
2019-03-11 16:12:58 -04:00
if (is_trusted && tx_depth >= min_depth) {
ret.m_mine_trusted += tx_credit_mine;
ret.m_watchonly_trusted += tx_credit_watchonly;
}
2019-03-11 16:12:58 -04:00
if (!is_trusted && tx_depth == 0 && wtx.InMempool()) {
ret.m_mine_untrusted_pending += tx_credit_mine;
ret.m_watchonly_untrusted_pending += tx_credit_watchonly;
}
ret.m_mine_immature += wtx.GetImmatureCredit();
ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit();
}
}
2019-03-11 16:12:58 -04:00
return ret;
}
2019-03-20 11:01:36 +00:00
CAmountMap CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
{
LOCK(cs_wallet);
2019-03-20 11:01:36 +00:00
CAmountMap balance;
std::vector<COutput> vCoins;
AvailableCoins(vCoins, true, coinControl);
for (const COutput& out : vCoins) {
if (out.fSpendable) {
2019-03-20 11:01:36 +00:00
CAmount amt = out.tx->GetOutputValueOut(out.i);
if (amt < 0) {
continue;
}
balance[out.tx->GetOutputAsset(out.i)] += amt;
}
}
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 CAsset* asset_filter) const
{
AssertLockHeld(cs_wallet);
vCoins.clear();
CAmount nTotal = 0;
2018-09-13 13:53:19 +09:00
// Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where
// a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses
bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse);
const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH};
const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH};
std::set<uint256> trusted_parents;
for (const auto& entry : mapWallet)
{
const uint256& wtxid = entry.first;
const CWalletTx& wtx = entry.second;
if (!chain().checkFinalTx(*wtx.tx)) {
continue;
}
if (wtx.IsImmatureCoinBase())
continue;
int nDepth = wtx.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 && !wtx.InMempool())
continue;
bool safeTx = IsTrusted(wtx, trusted_parents);
// 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 && wtx.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 && wtx.mapValue.count("replaced_by_txid")) {
safeTx = false;
}
if (fOnlySafe && !safeTx) {
continue;
}
if (nDepth < min_depth || nDepth > max_depth) {
continue;
}
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
// Only consider selected coins if add_inputs is false
if (coinControl && !coinControl->m_add_inputs && !coinControl->IsSelected(COutPoint(entry.first, i))) {
continue;
}
CAmount outValue = wtx.GetOutputValueOut(i);
CAsset asset = wtx.GetOutputAsset(i);
2019-03-20 11:01:36 +00:00
if (asset_filter && asset != *asset_filter) {
continue;
}
if (outValue < nMinimumAmount || outValue > 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(wtx.tx->vout[i]);
if (mine == ISMINE_NO) {
continue;
}
if (!allow_used_addresses && IsSpentKey(wtxid, i)) {
2018-09-13 13:53:19 +09:00
continue;
}
std::unique_ptr<SigningProvider> provider = GetSolvingProvider(wtx.tx->vout[i].scriptPubKey);
bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false;
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
vCoins.push_back(COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly)));
// Checks the sum amount of all UTXO's.
if (nMinimumSumAmount != MAX_MONEY) {
2019-03-20 11:01:36 +00:00
nTotal += outValue;
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
{
AssertLockHeld(cs_wallet);
std::map<CTxDestination, std::vector<COutput>> result;
std::vector<COutput> availableCoins;
AvailableCoins(availableCoins);
for (const COutput& coin : availableCoins) {
CTxDestination address;
if ((coin.fSpendable || (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) &&
ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) {
result[address].emplace_back(std::move(coin));
}
}
std::vector<COutPoint> lockedCoins;
ListLockedCoins(lockedCoins);
// Include watch-only for LegacyScriptPubKeyMan wallets without private keys
const bool include_watch_only = GetLegacyScriptPubKeyMan() && IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
const isminetype is_mine_filter = include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
for (const COutPoint& 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]) == is_mine_filter
) {
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
{
AssertLockHeld(cs_wallet);
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];
}
2019-03-20 11:01:36 +00:00
bool CWallet::SelectCoinsMinConf(const CAmountMap& mapTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups,
std::set<CInputCoin>& setCoinsRet, CAmountMap& mapValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const
{
setCoinsRet.clear();
2019-03-20 11:01:36 +00:00
mapValueRet.clear();
std::vector<OutputGroup> utxo_pool;
2019-03-20 11:01:36 +00:00
if (coin_selection_params.use_bnb && mapTargetValue.size() == 1) {
// ELEMENTS:
CAsset asset = mapTargetValue.begin()->first;
CAmount nTargetValue = mapTargetValue.begin()->second;
// Get output groups that only contain this asset.
std::vector<OutputGroup> asset_groups;
for (OutputGroup g : groups) {
bool add = true;
for (CInputCoin c : g.m_outputs) {
if (c.asset != asset) {
add = false;
break;
}
}
if (add) {
asset_groups.push_back(g);
}
}
// END ELEMENTS
// Get long term estimate
FeeCalculation feeCalc;
CCoinControl temp;
temp.m_confirm_target = 1008;
CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp, &feeCalc);
// Calculate cost of change
CAmount cost_of_change = GetDiscardRate(*this).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size);
// Filter by the min conf specs and add to utxo_pool and calculate effective value
2019-03-20 11:01:36 +00:00
for (OutputGroup& group : asset_groups) {
if (!group.EligibleForSpending(eligibility_filter)) continue;
if (coin_selection_params.m_subtract_fee_outputs) {
// Set the effective feerate to 0 as we don't want to use the effective value since the fees will be deducted from the output
group.SetFees(CFeeRate(0) /* effective_feerate */, long_term_feerate);
} else {
group.SetFees(coin_selection_params.effective_fee, long_term_feerate);
}
OutputGroup pos_group = group.GetPositiveOnlyGroup();
if (pos_group.effective_value > 0) utxo_pool.push_back(pos_group);
}
// Calculate the fees for things that aren't inputs
CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size);
bnb_used = true;
2019-03-20 11:01:36 +00:00
CAmount nValueRet;
bool ret = SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees);
mapValueRet[asset] = nValueRet;
return ret;
} else {
// Filter by the min conf specs and add to utxo_pool
for (const OutputGroup& group : groups) {
if (!group.EligibleForSpending(eligibility_filter)) continue;
utxo_pool.push_back(group);
}
bnb_used = false;
2019-03-20 11:01:36 +00:00
return KnapsackSolver(mapTargetValue, utxo_pool, setCoinsRet, mapValueRet);
}
}
2019-03-20 11:01:36 +00:00
bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmountMap& mapTargetValue, std::set<CInputCoin>& setCoinsRet, CAmountMap& mapValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const
{
AssertLockHeld(cs_wallet); // mapWallet
std::vector<COutput> vCoins(vAvailableCoins);
CAmountMap value_to_select = mapTargetValue;
2013-08-12 17:03:03 +02:00
// Default to bnb was not used. If we use it, we set it later
bnb_used = false;
2013-08-12 17:03:03 +02:00
// coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs)
2013-08-12 17:03:03 +02:00
{
for (const COutput& out : vCoins)
2013-08-12 17:03:03 +02:00
{
if (!out.fSpendable)
continue;
2019-03-20 11:01:36 +00:00
CAmount amt = out.tx->GetOutputValueOut(out.i);
if (amt < 0) {
continue;
}
mapValueRet[out.tx->GetOutputAsset(out.i)] += amt;
setCoinsRet.insert(out.GetInputCoin());
2013-08-12 17:03:03 +02:00
}
2019-03-20 11:01:36 +00:00
return (mapValueRet >= mapTargetValue);
2013-08-12 17:03:03 +02:00
}
// calculate value from preset inputs and store them
std::set<CInputCoin> setPresetCoins;
2019-03-20 11:01:36 +00:00
CAmountMap mapValueFromPresetInputs;
std::vector<COutPoint> vPresetInputs;
coin_control.ListSelected(vPresetInputs);
for (const COutPoint& outpoint : vPresetInputs)
{
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash);
// ELEMENTS: this code pulled from unmerged Core PR #17211
int input_bytes = -1;
CTxOut txout;
CInputCoin coin(outpoint, txout, 0); // dummy initialization
if (it != mapWallet.end()) {
const CWalletTx& wtx = it->second;
// Clearly invalid input, fail
if (wtx.tx->vout.size() <= outpoint.n) {
return false;
}
// Just to calculate the marginal byte size
if (wtx.GetOutputValueOut(outpoint.n) < 0) {
2019-03-20 11:01:36 +00:00
continue;
}
input_bytes = wtx.GetSpendSize(outpoint.n, false);
txout = wtx.tx->vout[outpoint.n];
// ELEMENTS: must assign coin from wtx if we can, so the wallet
// can look up any confidential amounts/assets
coin = CInputCoin(&wtx, outpoint.n, input_bytes);
}
if (input_bytes == -1) {
// The input is external. We either did not find the tx in mapWallet, or we did but couldn't compute the input size with wallet data
if (!coin_control.GetExternalOutput(outpoint, txout)) {
// Not ours, and we don't have solving data.
return false;
}
input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /* use_max_sig */ true);
// ELEMENTS: one more try to get a signed input size: for pegins,
// the outpoint is provided as external data but the information
// needed to spend is in the wallet (not the external provider,
// as the user is expecting the wallet to remember this information
// after they called getpeginaddress). So try estimating size with
// the wallet rather than the external provider.
if (input_bytes == -1) {
input_bytes = CalculateMaximumSignedInputSize(txout, this, /* use_max_sig */ true);
}
if (!txout.nValue.IsExplicit() || !txout.nAsset.IsExplicit()) {
return false; // We can't get its value, so abort
}
coin = CInputCoin(outpoint, txout, input_bytes);
}
mapValueFromPresetInputs[coin.asset] += coin.value;
if (coin.m_input_bytes <= 0) {
// ELEMENTS: if we're here we can't compute the coin's effective value. At
// this point in the rebase this is only used for BnB, and our functional
// tests expect the user to get a "missing data" error rather than an
// "insufficient funds" error, which means we need some way to make
// SelectCoins pass. So rather than "return false;" as in upstream we
// just turn off bnb and keep going.
coin_selection_params.use_bnb = false;
coin.m_input_bytes = 0;
}
coin.effective_value = coin.value - coin_selection_params.effective_fee.GetFee(coin.m_input_bytes);
if (coin_selection_params.use_bnb) {
value_to_select[coin.asset] -= coin.effective_value;
} else {
value_to_select[coin.asset] -= coin.value;
}
setPresetCoins.insert(coin);
}
// remove preset inputs from vCoins
for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
{
if (setPresetCoins.count(it->GetInputCoin()))
it = vCoins.erase(it);
else
++it;
}
unsigned int limit_ancestor_count = 0;
unsigned int limit_descendant_count = 0;
chain().getPackageLimits(limit_ancestor_count, limit_descendant_count);
size_t max_ancestors = (size_t)std::max<int64_t>(1, limit_ancestor_count);
size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count);
bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
2019-03-20 11:01:36 +00:00
// ELEMENTS: filter coins for assets we are interested in; always keep policyAsset for fees
for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();) {
CAsset asset = it->GetInputCoin().asset;
if (asset != ::policyAsset && mapTargetValue.find(asset) == mapTargetValue.end()) {
it = vCoins.erase(it);
} else {
++it;
}
}
// form groups from remaining coins; note that preset coins will not
// automatically have their associated (same address) coins included
if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
// Cases where we have 11+ outputs all pointing to the same destination may result in
// privacy leaks as they will potentially be deterministically sorted. We solve that by
// explicitly shuffling the outputs before processing
Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
}
std::vector<OutputGroup> groups = GroupOutputs(vCoins, !coin_control.m_avoid_partial_spends, max_ancestors);
2019-03-20 11:01:36 +00:00
// We will have to do coin selection on the difference between the target and the provided values.
// If value_to_select <= 0 for all asset types, we are done; but unlike in Bitcoin, this may be
// true for some assets whlie being false for others. So clear all the "completed" assets out
// of value_to_select before calling SelectCoinsMinConf.
for (CAmountMap::const_iterator it = value_to_select.begin(); it != value_to_select.end();) {
2019-03-20 11:01:36 +00:00
if (it->second <= 0) {
it = value_to_select.erase(it);
2019-03-20 11:01:36 +00:00
} else {
++it;
}
}
bool res = value_to_select.empty() ||
SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, mapValueRet, coin_selection_params, bnb_used) ||
SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, mapValueRet, coin_selection_params, bnb_used) ||
(m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, mapValueRet, coin_selection_params, bnb_used)) ||
(m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), groups, setCoinsRet, mapValueRet, coin_selection_params, bnb_used)) ||
(m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), groups, setCoinsRet, mapValueRet, coin_selection_params, bnb_used)) ||
(m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), groups, setCoinsRet, mapValueRet, coin_selection_params, bnb_used)) ||
(m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, mapValueRet, coin_selection_params, bnb_used));
// because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset
util::insert(setCoinsRet, setPresetCoins);
// add preset inputs to the total value selected
2019-03-20 11:01:36 +00:00
mapValueRet += mapValueFromPresetInputs;
return res;
}
bool CWallet::SignTransaction(CMutableTransaction& tx) const
2017-03-03 16:15:47 +01:00
{
AssertLockHeld(cs_wallet);
// Build coins map
std::map<COutPoint, Coin> coins;
for (auto& input : tx.vin) {
2017-03-03 16:15:47 +01:00
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 CWalletTx& wtx = mi->second;
coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], wtx.m_confirm.block_height, wtx.IsCoinBase());
}
std::map<int, std::string> input_errors;
return SignTransaction(tx, coins, SIGHASH_ALL, input_errors);
}
bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const
{
// Try to sign with all ScriptPubKeyMans
tx.witness.vtxinwit.resize(tx.vin.size());
for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
// spk_man->SignTransaction will return true if the transaction is complete,
// so we can exit early and return true if that happens
if (spk_man->SignTransaction(tx, coins, sighash, input_errors)) {
return true;
}
}
// At this point, one input was not fully signed otherwise we would have exited already
return false;
2017-03-03 16:15:47 +01:00
}
// ELEMENTS: split FillPSBT into FillPSBData and SignPSBT
TransactionError CWallet::FillPSBTData(PartiallySignedTransaction& psbtx, bool bip32derivs) const
{
LOCK(cs_wallet);
// Get all of the previous transactions
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i);
if (PSBTInputSigned(input)) {
continue;
}
// If we have no utxo, grab it from the wallet.
if (!input.non_witness_utxo) {
const uint256& txhash = txin.prevout.hash;
const auto it = mapWallet.find(txhash);
if (it != mapWallet.end()) {
const CWalletTx& wtx = it->second;
// We only need the non_witness_utxo, which is a superset of the witness_utxo.
// The signing code will switch to the smaller witness_utxo if this is ok.
input.non_witness_utxo = wtx.tx;
// ELEMENTS: Grab the CA data
CAmount val_tmp;
wtx.GetNonIssuanceBlindingData(txin.prevout.n, nullptr, &val_tmp, &input.value_blinding_factor, &input.asset, &input.asset_blinding_factor);
if (val_tmp != -1) {
input.value = val_tmp;
}
}
}
}
// Fill in information from ScriptPubKeyMans
// Because each ScriptPubKeyMan may be able to fill more than one input, we need to keep track of each ScriptPubKeyMan that has filled this psbt.
// Each iteration, we may fill more inputs than the input that is specified in that iteration.
// We assume that each input is filled by only one ScriptPubKeyMan
std::set<uint256> visited_spk_mans;
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i);
if (PSBTInputSigned(input)) {
continue;
}
// Get the scriptPubKey to know which ScriptPubKeyMan to use
CScript script;
if (!input.witness_utxo.IsNull()) {
script = input.witness_utxo.scriptPubKey;
} else if (input.non_witness_utxo) {
if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
return TransactionError::MISSING_INPUTS;
}
script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey;
} else {
// There's no UTXO so we can just skip this now
continue;
}
SignatureData sigdata;
input.FillSignatureData(sigdata);
std::set<ScriptPubKeyMan*> spk_mans = GetScriptPubKeyMans(script, sigdata);
if (spk_mans.size() == 0) {
continue;
}
for (auto& spk_man : spk_mans) {
// If we've already been signed by this spk_man, skip it
if (visited_spk_mans.count(spk_man->GetID()) > 0) {
continue;
}
// Fill in the information from the spk_man
// ELEMENTS: Get key origin info for input, if bip32derivs is true. Does not actually sign anything.
TransactionError res = spk_man->FillPSBT(psbtx, 1, false /* don't sign */, bip32derivs);
if (res != TransactionError::OK) {
return res;
}
// Add this spk_man to visited_spk_mans so we can skip it later
visited_spk_mans.insert(spk_man->GetID());
}
}
return TransactionError::OK;
}
TransactionError CWallet::SignPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool imbalance_ok, bool bip32derivs, size_t* n_signed) const
{
// If we're signing, check that the transaction is not still in need of blinding
if (sign) {
for (const PSBTOutput& o : psbtx.outputs) {
if (o.blinding_pubkey.IsValid()) {
return TransactionError::BLINDING_REQUIRED;
}
}
}
if (n_signed) {
*n_signed = 0;
}
// Save the original transaction since we need to munge it temporarily, which would violate the PSBT rules
CTransaction oldtx = CTransaction(*psbtx.tx);
LOCK(cs_wallet);
CMutableTransaction& tx = *psbtx.tx;
tx.witness.vtxoutwit.resize(tx.vout.size());
// Stuff in auxiliary CA blinding data, if we have it
for (unsigned int i = 0; i < tx.vout.size(); ++i) {
PSBTOutput& output = psbtx.outputs.at(i);
CTxOut& out = tx.vout[i];
if (!output.value_commitment.IsNull()) {
out.nValue = output.value_commitment;
}
if (!output.asset_commitment.IsNull()) {
out.nAsset = output.asset_commitment;
}
if (!output.nonce_commitment.IsNull()) {
out.nNonce = output.nonce_commitment;
}
// The signature can't depend on witness contents, so these are technically not necessary to sign.
// HOWEVER, as long as we're checking that values balance before signing, they are required.
CTxOutWitness& outwit = tx.witness.vtxoutwit[i];
if (!output.range_proof.empty()) {
outwit.vchRangeproof = output.range_proof;
}
if (!output.surjection_proof.empty()) {
outwit.vchSurjectionproof = output.surjection_proof;
}
}
// Stuff in the peg-in data
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
PSBTInput& input = psbtx.inputs[i];
if (input.value && input.peg_in_tx.which() != 0 && input.txout_proof.which() != 0 && !input.claim_script.empty() && !input.genesis_hash.IsNull()) {
CScriptWitness pegin_witness;
if (Params().GetConsensus().ParentChainHasPow()) {
const Sidechain::Bitcoin::CTransactionRef& btc_peg_in_tx = boost::get<Sidechain::Bitcoin::CTransactionRef>(input.peg_in_tx);
const Sidechain::Bitcoin::CMerkleBlock& btc_txout_proof = boost::get<Sidechain::Bitcoin::CMerkleBlock>(input.txout_proof);
pegin_witness = CreatePeginWitness(*input.value, input.asset, input.genesis_hash, input.claim_script, btc_peg_in_tx, btc_txout_proof);
} else {
const CTransactionRef& elem_peg_in_tx = boost::get<CTransactionRef>(input.peg_in_tx);
const CMerkleBlock& elem_txout_proof = boost::get<CMerkleBlock>(input.txout_proof);
pegin_witness = CreatePeginWitness(*input.value, input.asset, input.genesis_hash, input.claim_script, elem_peg_in_tx, elem_txout_proof);
}
tx.vin[i].m_is_pegin = true;
tx.witness.vtxinwit[i].m_pegin_witness = pegin_witness;
// Set the witness utxo
input.witness_utxo = GetPeginOutputFromWitness(tx.witness.vtxinwit[i].m_pegin_witness);
}
}
// This is a convenience/usability check -- it's not invalid to sign an unbalanced transaction, but it's easy to shoot yourself in the foot.
if (!imbalance_ok) {
// Get UTXOs for all inputs, to check that amounts balance before signing.
std::vector<CTxOut> inputs_utxos;
for (size_t i = 0; i < psbtx.inputs.size(); ++i) {
PSBTInput& inp = psbtx.inputs[i];
if (inp.non_witness_utxo) {
if (inp.non_witness_utxo->GetHash() != tx.vin[i].prevout.hash) {
return TransactionError::INVALID_PSBT;
}
if (!inp.witness_utxo.IsNull() && inp.non_witness_utxo->vout[tx.vin[i].prevout.n] != inp.witness_utxo) {
return TransactionError::INVALID_PSBT;
}
inputs_utxos.push_back(inp.non_witness_utxo->vout[tx.vin[i].prevout.n]);
} else if (!inp.witness_utxo.IsNull()) {
inputs_utxos.push_back(inp.witness_utxo);
} else {
return TransactionError::UTXOS_MISSING_BALANCE_CHECK;
}
}
CTransaction tx_tmp(tx);
if (!VerifyAmounts(inputs_utxos, tx_tmp, nullptr, false)) {
return TransactionError::VALUE_IMBALANCE;
}
}
for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
int n_signed_this_spkm = 0;
// ELEMENTS: Here we _only_ sign, and do not e.g. fill in key origin data.
TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs, &n_signed_this_spkm);
if (res != TransactionError::OK) {
return res;
}
if (n_signed) {
(*n_signed) += n_signed_this_spkm;
}
}
// Complete if every input is now signed
complete = true;
for (const auto& input : psbtx.inputs) {
complete &= PSBTInputSigned(input);
}
// Restore the saved transaction, to remove our temporary munging.
psbtx.tx = (CMutableTransaction)oldtx;
return TransactionError::OK;
}
// This function remains for backwards compatibility. It will not succeed in Elements unless everything involved is non-blinded.
TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs, size_t* n_signed) const
{
complete = false;
TransactionError te;
te = FillPSBTData(psbtx, bip32derivs);
if (te != TransactionError::OK) {
return te;
}
// For backwards compatibility, do not check if amounts balance before signing in this case.
te = SignPSBT(psbtx, complete, sighash_type, sign, true, bip32derivs, n_signed);
if (te != TransactionError::OK) {
return te;
}
return TransactionError::OK;
}
SigningResult CWallet::SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const
{
SignatureData sigdata;
CScript script_pub_key = GetScriptForDestination(pkhash);
for (const auto& spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script_pub_key, sigdata)) {
return spk_man_pair.second->SignMessage(message, pkhash, str_sig);
}
}
return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
}
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, 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];
2019-03-20 11:01:36 +00:00
// ELEMENTS:
if (!txOut.nValue.IsExplicit() || !txOut.nAsset.IsExplicit()) {
error = _("Pre-funded amounts must be non-blinded");
2019-03-20 11:01:36 +00:00
return false;
}
// Fee outputs should not be added to avoid overpayment of fees
if (txOut.IsFee()) {
continue;
}
CRecipient recipient = {txOut.scriptPubKey, txOut.nValue.GetAmount(), txOut.nAsset.GetAsset(), CPubKey(txOut.nNonce.vchCommitment), 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).
LOCK(cs_wallet);
CTransactionRef tx_new;
FeeCalculation fee_calc_out;
auto blind_details = g_con_elementsmode ? MakeUnique<BlindDetails>() : nullptr;
if (!CreateTransaction(vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false, blind_details.get())) {
return false;
}
2019-03-20 11:01:36 +00:00
// Wipe outputs and output witness and re-add one by one
tx.vout.clear();
tx.witness.vtxoutwit.clear();
for (unsigned int i = 0; i < tx_new->vout.size(); i++) {
const CTxOut& out = tx_new->vout[i];
tx.vout.push_back(out);
if (tx_new->witness.vtxoutwit.size() > i) {
// We want to re-add previously existing outwitnesses
// even though we don't create any new ones
const CTxOutWitness& outwit = tx_new->witness.vtxoutwit[i];
tx.witness.vtxoutwit.push_back(outwit);
}
}
// Add new txins while keeping original txin scriptSig/order.
for (const CTxIn& txin : tx_new->vin) {
if (!coinControl.IsSelected(txin.prevout)) {
tx.vin.push_back(txin);
}
if (lockUnspents) {
LockCoin(txin.prevout);
}
}
return true;
}
static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256& block_hash)
{
if (chain.isInitialBlockDownload()) {
return false;
}
constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60; // in seconds
int64_t block_time;
CHECK_NONFATAL(chain.findBlock(block_hash, FoundBlock().time(block_time)));
if (block_time < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) {
return false;
}
return true;
}
/**
* Return a height-based locktime for new transactions (uses the height of the
* current chain tip unless we are not synced with the current chain
*/
static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uint256& block_hash, int block_height)
{
uint32_t locktime;
// 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.
if (IsCurrentForAntiFeeSniping(chain, block_hash)) {
locktime = block_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)
locktime = std::max(0, (int)locktime - GetRandInt(100));
} else {
// If our chain is lagging behind, we can't discourage fee sniping nor help
// the privacy of high-latency transactions. To avoid leaking a potentially
// unique "nLockTime fingerprint", set nLockTime to a constant.
locktime = 0;
}
assert(locktime < LOCKTIME_THRESHOLD);
return locktime;
}
OutputType CWallet::TransactionChangeType(const Optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const
{
// If -changetype is specified, always use that change type.
if (change_type) {
return *change_type;
}
// if m_default_address_type is legacy, use legacy address as change (even
// if some of the outputs are P2WPKH or P2WSH).
if (m_default_address_type == OutputType::LEGACY) {
return OutputType::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 OutputType::BECH32;
}
}
// else use m_default_address_type for change
return m_default_address_type;
}
2019-03-20 11:01:36 +00:00
// Reset all non-global blinding details.
void resetBlindDetails(BlindDetails* det) {
det->i_amount_blinds.clear();
det->i_asset_blinds.clear();
det->i_assets.clear();
det->i_amounts.clear();
det->o_amounts.clear();
det->o_pubkeys.clear();
det->o_amount_blinds.clear();
det->o_assets.clear();
det->o_asset_blinds.clear();
det->tx_unblinded_unsigned = CMutableTransaction();
det->num_to_blind = 0;
det->change_to_blind = 0;
det->only_recipient_blind_index = -1;
det->only_change_pos = -1;
}
bool fillBlindDetails(BlindDetails* det, CWallet* wallet, CMutableTransaction& txNew, std::vector<CInputCoin>& selected_coins, bilingual_str& error) {
2019-03-20 11:01:36 +00:00
int num_inputs_blinded = 0;
// Fill in input blinding details
for (const CInputCoin& coin : selected_coins) {
det->i_amount_blinds.push_back(coin.bf_value);
det->i_asset_blinds.push_back(coin.bf_asset);
det->i_assets.push_back(coin.asset);
det->i_amounts.push_back(coin.value);
if (coin.txout.nValue.IsCommitment() || coin.txout.nAsset.IsCommitment()) {
num_inputs_blinded++;
}
}
// Fill in output blinding details
for (size_t nOut = 0; nOut < txNew.vout.size(); nOut++) {
//TODO(CA) consider removing all blind setting before BlindTransaction as they get cleared anyway
det->o_amount_blinds.push_back(uint256());
det->o_asset_blinds.push_back(uint256());
det->o_assets.push_back(txNew.vout[nOut].nAsset.GetAsset());
det->o_amounts.push_back(txNew.vout[nOut].nValue.GetAmount());
}
// There are a few edge-cases of blinding we need to take care of
//
// First, if there are blinded inputs but not outputs to blind
// We need this to go through, even though no privacy is gained.
if (num_inputs_blinded > 0 && det->num_to_blind == 0) {
// We need to make sure to dupe an asset that is in input set
//TODO Have blinding do some extremely minimal rangeproof
CTxOut newTxOut(det->o_assets.back(), 0, CScript() << OP_RETURN);
txNew.vout.push_back(newTxOut);
det->o_pubkeys.push_back(wallet->GetBlindingPubKey(newTxOut.scriptPubKey));
det->o_amount_blinds.push_back(uint256());
det->o_asset_blinds.push_back(uint256());
det->o_amounts.push_back(0);
det->o_assets.push_back(det->o_assets.back());
det->num_to_blind++;
wallet->WalletLogPrintf("Adding OP_RETURN output to complete blinding since there are %d blinded inputs and no blinded outputs\n", num_inputs_blinded);
// No blinded inputs, but 1 blinded output
} else if (num_inputs_blinded == 0 && det->num_to_blind == 1) {
if (det->change_to_blind == 1) {
// Only 1 blinded change, unblind the change
//TODO Split up change instead if possible
if (det->ignore_blind_failure) {
det->num_to_blind--;
det->change_to_blind--;
txNew.vout[det->only_change_pos].nNonce.SetNull();
det->o_pubkeys[det->only_change_pos] = CPubKey();
det->o_amount_blinds[det->only_change_pos] = uint256();
det->o_asset_blinds[det->only_change_pos] = uint256();
wallet->WalletLogPrintf("Unblinding change at index %d due to lack of inputs and other outputs being blinded.\n", det->only_change_pos);
} else {
error = _("Change output could not be blinded as there are no blinded inputs and no other blinded outputs.");
2019-03-20 11:01:36 +00:00
return false;
}
} else {
// 1 blinded destination
// TODO Attempt to get a blinded input, OR add unblinded coin to make blinded change
assert(det->only_recipient_blind_index != -1);
if (det->ignore_blind_failure) {
det->num_to_blind--;
txNew.vout[det->only_recipient_blind_index].nNonce.SetNull();
det->o_pubkeys[det->only_recipient_blind_index] = CPubKey();
det->o_amount_blinds[det->only_recipient_blind_index] = uint256();
det->o_asset_blinds[det->only_recipient_blind_index] = uint256();
2019-03-20 10:32:08 -04:00
wallet->WalletLogPrintf("Unblinding single blinded output at index %d due to lack of inputs and other outputs being blinded.\n", det->only_recipient_blind_index);
2019-03-20 11:01:36 +00:00
} else {
error = _("Transaction output could not be blinded as there are no blinded inputs and no other blinded outputs.");
2019-03-20 11:01:36 +00:00
return false;
}
}
}
// All other combinations should work.
return true;
}
bool CWallet::CreateTransactionInternal(
const std::vector<CRecipient>& vecSend,
CTransactionRef& tx,
CAmount& nFeeRet,
int& nChangePosInOut,
bilingual_str& error,
const CCoinControl& coin_control,
FeeCalculation& fee_calc_out,
bool sign,
BlindDetails* blind_details,
const IssuanceDetails* issuance_details)
{
2019-03-20 11:01:36 +00:00
if (blind_details || issuance_details) {
assert(g_con_elementsmode);
}
CAmountMap mapValue;
// Always assume that we are at least sending policyAsset.
mapValue[::policyAsset] = 0;
std::vector<std::unique_ptr<ReserveDestination>> reservedest;
const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
reservedest.emplace_back(new ReserveDestination(this, change_type)); // policy asset
int nChangePosRequest = nChangePosInOut;
2019-03-20 11:01:36 +00:00
std::map<CAsset, int> vChangePosInOut;
unsigned int nSubtractFeeFromAmount = 0;
2019-03-20 11:01:36 +00:00
std::set<CAsset> assets_seen;
for (const auto& recipient : vecSend)
{
// Pad change keys to cover total possible number of assets
// One already exists(for policyAsset), so one for each destination
if (assets_seen.insert(recipient.asset).second) {
reservedest.emplace_back(new ReserveDestination(this, change_type));
}
2019-03-20 11:01:36 +00:00
// Skip over issuance outputs, no need to select those coins
if (recipient.asset == CAsset(uint256S("1")) || recipient.asset == CAsset(uint256S("2"))) {
continue;
}
if (g_con_elementsmode && recipient.asset.IsNull()) {
error = _("No asset provided for recipient");
2019-03-20 11:01:36 +00:00
return false;
}
if (mapValue[recipient.asset] < 0 || recipient.nAmount < 0) {
error = _("Transaction amounts must not be negative");
return false;
}
2019-03-20 11:01:36 +00:00
mapValue[recipient.asset] += recipient.nAmount;
if (recipient.fSubtractFeeFromAmount)
nSubtractFeeFromAmount++;
}
2016-11-22 12:48:01 -05:00
if (vecSend.empty())
{
error = _("Transaction must have at least one recipient");
return false;
}
CMutableTransaction txNew;
FeeCalculation feeCalc;
CAmount nFeeNeeded;
int nBytes;
{
std::set<CInputCoin> setCoins;
2019-03-20 11:01:36 +00:00
// Preserve order of selected inputs for surjection proofs
std::vector<CInputCoin> selected_coins;
2019-03-20 11:15:45 -04:00
// A map that keeps track of the change script for each asset and also
// the index of the reservedest used for that script (-1 if none).
2019-03-20 11:01:36 +00:00
std::map<CAsset, std::pair<int, CScript>> mapScriptChange;
LOCK(cs_wallet);
txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight());
{
CScript dummy_script = CScript() << 0x00;
std::vector<COutput> vAvailableCoins;
AvailableCoins(vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
2019-03-20 11:01:36 +00:00
mapScriptChange.clear();
if (coin_control.destChange.size() > 0) {
2019-05-01 18:28:10 -07:00
for (const auto& dest : coin_control.destChange) {
2019-03-20 11:01:36 +00:00
// No need to test we cover all assets. We produce error for that later.
mapScriptChange[dest.first] = std::pair<int, CScript>(-1, GetScriptForDestination(dest.second));
}
} 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.
2019-03-20 11:01:36 +00:00
// One change script per output asset.
size_t index = 0;
2019-04-10 09:20:27 -04:00
for (const auto& value : mapValue) {
Merge bbb1ba1814 into merged_master (Bitcoin PR #17219) This modifies the CreateTransaction loop in a way not remotely worth the complexity, and includes an absurdly fragile test where I had to add a bunch of trace statements and tweak pretty-much every single hardcoded number. Not to name names, but it was Sjors. (In fairness, the PR is a pure simplification of the CreateTransaction logic, and it wasn't hard to merge even. It was just the test that caused my grief.) Adapting the "use a dummy CTxDestination in the case that we cannot retrieve one from the wallet" logic to our `mapScriptChange` map was not trivial. On my first attempt I incorrectly assigned a positive vout index to the dummy script, which caused us to call `ReturnDestination` later on the (unused) dummy destination. This is harmless now, but when descriptor wallets are introduced in #16528, they introduce an edge case where returning a null destination can incorrectly mark the 0th key of a BIP32 range as unused. This triggered a test failure much later, in #19504, which uses descriptor wallets in fundrawtransaction. The bug was that we'd import a descriptor, mark the first key as being used, lock the wallet, call `fundrawtransaction` on a transaction that did not require change (incorrectly marking the first key as unused but leaving it in the descriptor ScriptPubKeyMan's cache), then call `fundrawtransaction` again on a transaction that *did* require change. The wallet would then incorrectly retrieve the "unused" key from cache and use it for change, rather than correctly failing and advising the user that it could not produce change with a locked wallet and empty keypool. This was not a fun bug to track down. Another interesting observation is that branch-and-bound uses the CT size-overestimate for change when trying to create changeless outputs, while our normal dust detection uses Core's unchanged "an output is 133 bytes" logic. So when BnB is used we're willing to delete a far bigger change output than we are when we don't use BnB. Lest you think this works in Core, they're also inconsistent because BnB uses a normal fee estimate for gauging change cost, while non-BnB uses the discardfee rate. My advice is to hold your nose, pull stuff in from Core as it comes in, and thanks to Andy's efforts things are getting better. Don't bother reviewing this too closely.
2020-12-03 00:58:01 +00:00
// Reserve a new key pair from key pool. If it fails, provide a dummy
// destination in case we don't need change.
CTxDestination dest;
if (index >= reservedest.size() || !reservedest[index]->GetReservedDestination(dest, true)) {
error = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.");
Merge bbb1ba1814 into merged_master (Bitcoin PR #17219) This modifies the CreateTransaction loop in a way not remotely worth the complexity, and includes an absurdly fragile test where I had to add a bunch of trace statements and tweak pretty-much every single hardcoded number. Not to name names, but it was Sjors. (In fairness, the PR is a pure simplification of the CreateTransaction logic, and it wasn't hard to merge even. It was just the test that caused my grief.) Adapting the "use a dummy CTxDestination in the case that we cannot retrieve one from the wallet" logic to our `mapScriptChange` map was not trivial. On my first attempt I incorrectly assigned a positive vout index to the dummy script, which caused us to call `ReturnDestination` later on the (unused) dummy destination. This is harmless now, but when descriptor wallets are introduced in #16528, they introduce an edge case where returning a null destination can incorrectly mark the 0th key of a BIP32 range as unused. This triggered a test failure much later, in #19504, which uses descriptor wallets in fundrawtransaction. The bug was that we'd import a descriptor, mark the first key as being used, lock the wallet, call `fundrawtransaction` on a transaction that did not require change (incorrectly marking the first key as unused but leaving it in the descriptor ScriptPubKeyMan's cache), then call `fundrawtransaction` again on a transaction that *did* require change. The wallet would then incorrectly retrieve the "unused" key from cache and use it for change, rather than correctly failing and advising the user that it could not produce change with a locked wallet and empty keypool. This was not a fun bug to track down. Another interesting observation is that branch-and-bound uses the CT size-overestimate for change when trying to create changeless outputs, while our normal dust detection uses Core's unchanged "an output is 133 bytes" logic. So when BnB is used we're willing to delete a far bigger change output than we are when we don't use BnB. Lest you think this works in Core, they're also inconsistent because BnB uses a normal fee estimate for gauging change cost, while non-BnB uses the discardfee rate. My advice is to hold your nose, pull stuff in from Core as it comes in, and thanks to Andy's efforts things are getting better. Don't bother reviewing this too closely.
2020-12-03 00:58:01 +00:00
// ELEMENTS: We need to put a dummy destination here. Core uses an empty script
// but we can't because empty scripts indicate fees (which trigger assertation
// failures in `BlindTransaction`). We also set the index to -1, indicating
// that this destination is not actually used, and therefore should not be
// returned by the `ReturnDestination` loop below.
mapScriptChange[value.first] = std::pair<int, CScript>(-1, dummy_script);
Merge bbb1ba1814 into merged_master (Bitcoin PR #17219) This modifies the CreateTransaction loop in a way not remotely worth the complexity, and includes an absurdly fragile test where I had to add a bunch of trace statements and tweak pretty-much every single hardcoded number. Not to name names, but it was Sjors. (In fairness, the PR is a pure simplification of the CreateTransaction logic, and it wasn't hard to merge even. It was just the test that caused my grief.) Adapting the "use a dummy CTxDestination in the case that we cannot retrieve one from the wallet" logic to our `mapScriptChange` map was not trivial. On my first attempt I incorrectly assigned a positive vout index to the dummy script, which caused us to call `ReturnDestination` later on the (unused) dummy destination. This is harmless now, but when descriptor wallets are introduced in #16528, they introduce an edge case where returning a null destination can incorrectly mark the 0th key of a BIP32 range as unused. This triggered a test failure much later, in #19504, which uses descriptor wallets in fundrawtransaction. The bug was that we'd import a descriptor, mark the first key as being used, lock the wallet, call `fundrawtransaction` on a transaction that did not require change (incorrectly marking the first key as unused but leaving it in the descriptor ScriptPubKeyMan's cache), then call `fundrawtransaction` again on a transaction that *did* require change. The wallet would then incorrectly retrieve the "unused" key from cache and use it for change, rather than correctly failing and advising the user that it could not produce change with a locked wallet and empty keypool. This was not a fun bug to track down. Another interesting observation is that branch-and-bound uses the CT size-overestimate for change when trying to create changeless outputs, while our normal dust detection uses Core's unchanged "an output is 133 bytes" logic. So when BnB is used we're willing to delete a far bigger change output than we are when we don't use BnB. Lest you think this works in Core, they're also inconsistent because BnB uses a normal fee estimate for gauging change cost, while non-BnB uses the discardfee rate. My advice is to hold your nose, pull stuff in from Core as it comes in, and thanks to Andy's efforts things are getting better. Don't bother reviewing this too closely.
2020-12-03 00:58:01 +00:00
} else {
mapScriptChange[value.first] = std::pair<int, CScript>(index, GetScriptForDestination(dest));
++index;
2019-03-20 11:01:36 +00:00
}
}
// Also make sure we have change scripts for the pre-selected inputs.
std::vector<COutPoint> vPresetInputs;
coin_control.ListSelected(vPresetInputs);
for (const COutPoint& presetInput : vPresetInputs) {
CAsset asset;
2019-03-20 11:01:36 +00:00
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(presetInput.hash);
CTxOut txout;
if (it != mapWallet.end()) {
asset = it->second.GetOutputAsset(presetInput.n);
} else if (coin_control.GetExternalOutput(presetInput, txout)) {
asset = txout.nAsset.GetAsset();
} else {
2019-03-20 11:01:36 +00:00
// Ignore this here, will fail more gracefully later.
continue;
}
if (mapScriptChange.find(asset) != mapScriptChange.end()) {
// This asset already has a change script.
continue;
}
CTxDestination dest;
if (index >= reservedest.size() || !reservedest[index]->GetReservedDestination(dest, true)) {
error = _("Keypool ran out, please call keypoolrefill first");
2019-03-20 11:01:36 +00:00
return false;
}
Merge bbb1ba1814 into merged_master (Bitcoin PR #17219) This modifies the CreateTransaction loop in a way not remotely worth the complexity, and includes an absurdly fragile test where I had to add a bunch of trace statements and tweak pretty-much every single hardcoded number. Not to name names, but it was Sjors. (In fairness, the PR is a pure simplification of the CreateTransaction logic, and it wasn't hard to merge even. It was just the test that caused my grief.) Adapting the "use a dummy CTxDestination in the case that we cannot retrieve one from the wallet" logic to our `mapScriptChange` map was not trivial. On my first attempt I incorrectly assigned a positive vout index to the dummy script, which caused us to call `ReturnDestination` later on the (unused) dummy destination. This is harmless now, but when descriptor wallets are introduced in #16528, they introduce an edge case where returning a null destination can incorrectly mark the 0th key of a BIP32 range as unused. This triggered a test failure much later, in #19504, which uses descriptor wallets in fundrawtransaction. The bug was that we'd import a descriptor, mark the first key as being used, lock the wallet, call `fundrawtransaction` on a transaction that did not require change (incorrectly marking the first key as unused but leaving it in the descriptor ScriptPubKeyMan's cache), then call `fundrawtransaction` again on a transaction that *did* require change. The wallet would then incorrectly retrieve the "unused" key from cache and use it for change, rather than correctly failing and advising the user that it could not produce change with a locked wallet and empty keypool. This was not a fun bug to track down. Another interesting observation is that branch-and-bound uses the CT size-overestimate for change when trying to create changeless outputs, while our normal dust detection uses Core's unchanged "an output is 133 bytes" logic. So when BnB is used we're willing to delete a far bigger change output than we are when we don't use BnB. Lest you think this works in Core, they're also inconsistent because BnB uses a normal fee estimate for gauging change cost, while non-BnB uses the discardfee rate. My advice is to hold your nose, pull stuff in from Core as it comes in, and thanks to Andy's efforts things are getting better. Don't bother reviewing this too closely.
2020-12-03 00:58:01 +00:00
CScript scriptChange = GetScriptForDestination(dest);
// A valid destination implies a change script (and
// vice-versa). An empty change script will abort later, if the
// change keypool ran out, but change is required.
CHECK_NONFATAL(IsValidDestination(dest) != (scriptChange == dummy_script));
Merge bbb1ba1814 into merged_master (Bitcoin PR #17219) This modifies the CreateTransaction loop in a way not remotely worth the complexity, and includes an absurdly fragile test where I had to add a bunch of trace statements and tweak pretty-much every single hardcoded number. Not to name names, but it was Sjors. (In fairness, the PR is a pure simplification of the CreateTransaction logic, and it wasn't hard to merge even. It was just the test that caused my grief.) Adapting the "use a dummy CTxDestination in the case that we cannot retrieve one from the wallet" logic to our `mapScriptChange` map was not trivial. On my first attempt I incorrectly assigned a positive vout index to the dummy script, which caused us to call `ReturnDestination` later on the (unused) dummy destination. This is harmless now, but when descriptor wallets are introduced in #16528, they introduce an edge case where returning a null destination can incorrectly mark the 0th key of a BIP32 range as unused. This triggered a test failure much later, in #19504, which uses descriptor wallets in fundrawtransaction. The bug was that we'd import a descriptor, mark the first key as being used, lock the wallet, call `fundrawtransaction` on a transaction that did not require change (incorrectly marking the first key as unused but leaving it in the descriptor ScriptPubKeyMan's cache), then call `fundrawtransaction` again on a transaction that *did* require change. The wallet would then incorrectly retrieve the "unused" key from cache and use it for change, rather than correctly failing and advising the user that it could not produce change with a locked wallet and empty keypool. This was not a fun bug to track down. Another interesting observation is that branch-and-bound uses the CT size-overestimate for change when trying to create changeless outputs, while our normal dust detection uses Core's unchanged "an output is 133 bytes" logic. So when BnB is used we're willing to delete a far bigger change output than we are when we don't use BnB. Lest you think this works in Core, they're also inconsistent because BnB uses a normal fee estimate for gauging change cost, while non-BnB uses the discardfee rate. My advice is to hold your nose, pull stuff in from Core as it comes in, and thanks to Andy's efforts things are getting better. Don't bother reviewing this too closely.
2020-12-03 00:58:01 +00:00
mapScriptChange[asset] = std::pair<int, CScript>(index, scriptChange);
2019-03-20 11:01:36 +00:00
++index;
}
}
2019-03-20 11:01:36 +00:00
assert(mapScriptChange.size() > 0);
CTxOut change_prototype_txout(mapScriptChange.begin()->first, 0, mapScriptChange.begin()->second.second);
// TODO CA: Set this for each change output
coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout);
2019-03-20 11:01:36 +00:00
if (g_con_elementsmode) {
// Assume blinded output for coin selection purposes. Over-paying is ok!
change_prototype_txout.nAsset.vchCommitment.resize(33);
change_prototype_txout.nValue.vchCommitment.resize(33);
change_prototype_txout.nNonce.vchCommitment.resize(33);
2019-03-20 11:01:36 +00:00
coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout);
coin_selection_params.change_output_size += (MAX_RANGEPROOF_SIZE + DEFAULT_SURJECTIONPROOF_SIZE + WITNESS_SCALE_FACTOR - 1)/WITNESS_SCALE_FACTOR;
2019-03-20 11:01:36 +00:00
}
CFeeRate discard_rate = GetDiscardRate(*this);
// Get the fee rate to use effective values in coin selection
CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coin_control, &feeCalc);
// Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
// provided one
if (coin_control.m_feerate && nFeeRateNeeded > *coin_control.m_feerate) {
error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), nFeeRateNeeded.ToString(FeeEstimateMode::SAT_VB));
return false;
}
2019-03-20 11:01:36 +00:00
// ELEMENTS:
// Start with tiny non-zero fee for issuance entropy and loop until there is enough fee
nFeeRet = 1;
bool pick_new_inputs = true;
2019-03-20 11:01:36 +00:00
CAmountMap mapValueIn;
// BnB selector is the only selector used when this is true.
// That should only happen on the first pass through the loop.
coin_selection_params.use_bnb = true;
coin_selection_params.m_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // If we are doing subtract fee from recipient, don't use effective values
//ELEMENTS: stopgap solution to https://github.com/bitcoin/bitcoin/issues/20347
bool one_more_try_20347 = false;
2015-09-14 14:49:59 +02:00
// Start with no fee and loop until there is enough fee
while (true)
{
2019-03-20 11:01:36 +00:00
if (blind_details) {
// Clear out previous blinding/data info as needed
resetBlindDetails(blind_details);
}
// We need to output the position of the policyAsset change output.
// So we keep track of the change position of all assets
// individually and set the export variable in the end.
vChangePosInOut.clear();
if (nChangePosRequest >= 0) {
vChangePosInOut[::policyAsset] = nChangePosRequest;
}
txNew.vin.clear();
txNew.vout.clear();
2019-03-20 11:01:36 +00:00
txNew.witness.SetNull();
bool fFirst = true;
2019-03-20 11:01:36 +00:00
CAmountMap mapValueToSelect = mapValue;
if (nSubtractFeeFromAmount == 0)
2019-03-20 11:01:36 +00:00
mapValueToSelect[::policyAsset] += nFeeRet;
// vouts to the payees
if (!coin_selection_params.m_subtract_fee_outputs) {
coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size)
}
// Account for the fee output in the tx.
if (g_con_elementsmode) {
CTxOut fee(::policyAsset, nFeeRet, CScript());
assert(fee.IsFee());
coin_selection_params.tx_noinputs_size += ::GetSerializeSize(fee, PROTOCOL_VERSION);
}
for (const auto& recipient : vecSend)
{
2019-03-20 11:01:36 +00:00
CTxOut txout(recipient.asset, recipient.nAmount, recipient.scriptPubKey);
txout.nNonce.vchCommitment = std::vector<unsigned char>(recipient.confidentiality_key.begin(), recipient.confidentiality_key.end());
if (recipient.fSubtractFeeFromAmount)
{
2019-03-20 11:01:36 +00:00
if (recipient.asset != policyAsset) {
error = Untranslated(strprintf("Wallet does not support more than one type of fee at a time, therefore can not subtract fee from address amount, which is of a different asset id. fee asset: %s recipient asset: %s", policyAsset.GetHex(), recipient.asset.GetHex()));
2019-03-20 11:01:36 +00:00
return false;
}
assert(nSubtractFeeFromAmount != 0);
2019-03-20 11:01:36 +00:00
txout.nValue = txout.nValue.GetAmount() - nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
if (fFirst) // first receiver pays the remainder not divisible by output count
{
fFirst = false;
2019-03-20 11:01:36 +00:00
txout.nValue = txout.nValue.GetAmount() - nFeeRet % nSubtractFeeFromAmount;
}
}
2019-03-20 11:01:36 +00:00
// ELEMENTS: Core's logic isn't great here. We should be computing
// cost of making output + future spend. We're not as concerned
// about dust anyways, so let's focus upstream.
if (recipient.asset == policyAsset && IsDust(txout, chain().relayDustFee()))
{
if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
{
2019-03-20 11:01:36 +00:00
if (txout.nValue.GetAmount() < 0)
error = _("The transaction amount is too small to pay the fee");
else
error = _("The transaction amount is too small to send after the fee has been deducted");
}
else
error = _("Transaction amount too small");
return false;
}
// Include the fee cost for outputs. Note this is only used for BnB right now
if (!coin_selection_params.m_subtract_fee_outputs) {
coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
}
txNew.vout.push_back(txout);
2019-03-20 11:01:36 +00:00
if (blind_details) {
blind_details->o_pubkeys.push_back(recipient.confidentiality_key);
if (blind_details->o_pubkeys.back().IsFullyValid()) {
blind_details->num_to_blind++;
blind_details->only_recipient_blind_index = txNew.vout.size()-1;
coin_selection_params.tx_noinputs_size += (MAX_RANGEPROOF_SIZE + DEFAULT_SURJECTIONPROOF_SIZE + WITNESS_SCALE_FACTOR - 1)/WITNESS_SCALE_FACTOR;
2019-03-20 11:01:36 +00:00
}
}
}
// Choose coins to use
bool bnb_used = false;
if (pick_new_inputs) {
2019-03-20 11:01:36 +00:00
mapValueIn.clear();
setCoins.clear();
int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
// If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh
// as lower-bound to allow BnB to do it's thing
if (change_spend_size == -1) {
coin_selection_params.change_spend_size = DUMMY_NESTED_P2WPKH_INPUT_SIZE;
} else {
coin_selection_params.change_spend_size = (size_t)change_spend_size;
}
coin_selection_params.effective_fee = nFeeRateNeeded;
2019-03-20 11:01:36 +00:00
if (!SelectCoins(vAvailableCoins, mapValueToSelect, setCoins, mapValueIn, coin_control, coin_selection_params, bnb_used))
{
// If BnB was used, it was the first pass. No longer the first pass and continue loop with knapsack.
if (bnb_used) {
coin_selection_params.use_bnb = false;
continue;
}
else {
error = _("Insufficient funds");
return false;
}
}
} else {
bnb_used = false;
}
2019-03-20 11:01:36 +00:00
const CAmountMap mapChange = mapValueIn - mapValueToSelect;
2019-04-10 09:20:27 -04:00
for(const auto& assetChange : mapChange) {
2019-03-20 11:01:36 +00:00
if (assetChange.second == 0) {
vChangePosInOut.erase(assetChange.first);
continue;
}
// Fill a vout to ourself
2019-03-20 11:01:36 +00:00
const std::map<CAsset, std::pair<int, CScript>>::const_iterator itScript = mapScriptChange.find(assetChange.first);
if (itScript == mapScriptChange.end()) {
error = Untranslated(strprintf("No change destination provided for asset %s", assetChange.first.GetHex()));
2019-03-20 11:01:36 +00:00
return false;
}
CTxOut newTxOut(assetChange.first, assetChange.second, itScript->second.second);
// Never create dust outputs; if we would, just
// add the dust to the fee.
// The nChange when BnB is used is always going to go to fees.
2019-03-20 11:01:36 +00:00
if (assetChange.first == policyAsset && (IsDust(newTxOut, discard_rate) || bnb_used))
{
2019-03-20 11:01:36 +00:00
vChangePosInOut.erase(assetChange.first);
nFeeRet += assetChange.second;
}
else
{
2019-03-20 11:01:36 +00:00
std::map<CAsset, int>::const_iterator itPos = vChangePosInOut.find(assetChange.first);
if (itPos == vChangePosInOut.end())
{
// Insert change txn at random position:
2019-03-20 11:01:36 +00:00
int newPos = GetRandInt(txNew.vout.size()+1);
// Update existing entries in vChangePos that have been moved.
for (std::map<CAsset, int>::iterator it = vChangePosInOut.begin(); it != vChangePosInOut.end(); ++it) {
if (it->second >= newPos) {
it->second++;
}
}
vChangePosInOut[assetChange.first] = newPos;
}
2019-03-20 11:01:36 +00:00
else if ((unsigned int)itPos->second > txNew.vout.size())
{
error = _("Change index out of range");
return false;
}
2019-03-20 11:01:36 +00:00
std::vector<CTxOut>::iterator position = txNew.vout.begin()+vChangePosInOut[assetChange.first];
if (blind_details) {
CPubKey blind_pub = GetBlindingPubKey(itScript->second.second);
2019-03-20 11:01:36 +00:00
blind_details->o_pubkeys.insert(blind_details->o_pubkeys.begin() + vChangePosInOut[assetChange.first], blind_pub);
assert(blind_pub.IsFullyValid());
blind_details->num_to_blind++;
blind_details->change_to_blind++;
blind_details->only_change_pos = vChangePosInOut[assetChange.first];
// Place the blinding pubkey here in case of fundraw calls
newTxOut.nNonce.vchCommitment = std::vector<unsigned char>(blind_pub.begin(), blind_pub.end());
2019-03-20 11:01:36 +00:00
}
txNew.vout.insert(position, newTxOut);
}
2019-03-20 11:01:36 +00:00
}
// Set the correct nChangePosInOut for output. Should be policyAsset's position.
std::map<CAsset, int>::const_iterator itPos = vChangePosInOut.find(::policyAsset);
if (itPos != vChangePosInOut.end()) {
nChangePosInOut = itPos->second;
} else {
2019-03-20 11:01:36 +00:00
// no policy change inserted; others assets may have been
nChangePosInOut = -1;
}
2019-03-20 11:01:36 +00:00
// Add fee output.
if (g_con_elementsmode) {
CTxOut fee(::policyAsset, nFeeRet, CScript());
assert(fee.IsFee());
txNew.vout.push_back(fee);
if (blind_details) {
blind_details->o_pubkeys.push_back(CPubKey());
}
}
// Set token input if reissuing
int reissuance_index = -1;
uint256 token_blinding;
// Elements: Shuffle here to preserve random ordering for surjection proofs
selected_coins = std::vector<CInputCoin>(setCoins.begin(), setCoins.end());
Shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext());
2019-03-20 11:01:36 +00:00
// Dummy fill vin for maximum size estimation
2013-08-25 14:13:25 -04:00
//
2019-03-20 11:01:36 +00:00
for (const CInputCoin& coin : selected_coins) {
txNew.vin.push_back(CTxIn(coin.outpoint, CScript()));
if (issuance_details && coin.asset == issuance_details->reissuance_token) {
reissuance_index = txNew.vin.size() - 1;
token_blinding = coin.bf_asset;
}
}
std::vector<CKey> issuance_asset_keys;
std::vector<CKey> issuance_token_keys;
if (issuance_details) {
// Fill in issuances now that inputs are set
assert(txNew.vin.size() > 0);
int asset_index = -1;
int token_index = -1;
for (unsigned int i = 0; i < txNew.vout.size(); i++) {
if (txNew.vout[i].nAsset.IsExplicit() && txNew.vout[i].nAsset.GetAsset() == CAsset(uint256S("1"))) {
asset_index = i;
} else if (txNew.vout[i].nAsset.IsExplicit() && txNew.vout[i].nAsset.GetAsset() == CAsset(uint256S("2"))) {
token_index = i;
}
}
// Initial issuance request
if (issuance_details->reissuance_asset.IsNull() && issuance_details->reissuance_token.IsNull() && (asset_index != -1 || token_index != -1)) {
uint256 entropy;
CAsset asset;
CAsset token;
//TODO take optional contract hash
// Initial issuance always uses vin[0]
GenerateAssetEntropy(entropy, txNew.vin[0].prevout, uint256());
CalculateAsset(asset, entropy);
CalculateReissuanceToken(token, entropy, issuance_details->blind_issuance);
CScript blindingScript(CScript() << OP_RETURN << std::vector<unsigned char>(txNew.vin[0].prevout.hash.begin(), txNew.vin[0].prevout.hash.end()) << txNew.vin[0].prevout.n);
// We're making asset outputs, fill out asset type and issuance input
if (asset_index != -1) {
txNew.vin[0].assetIssuance.nAmount = txNew.vout[asset_index].nValue;
txNew.vout[asset_index].nAsset = asset;
if (issuance_details->blind_issuance && blind_details) {
issuance_asset_keys.push_back(GetBlindingKey(&blindingScript));
blind_details->num_to_blind++;
}
}
// We're making reissuance token outputs
if (token_index != -1) {
txNew.vin[0].assetIssuance.nInflationKeys = txNew.vout[token_index].nValue;
txNew.vout[token_index].nAsset = token;
if (issuance_details->blind_issuance && blind_details) {
issuance_token_keys.push_back(GetBlindingKey(&blindingScript));
blind_details->num_to_blind++;
// If we're blinding a token issuance and no assets, we must make
// the asset issuance a blinded commitment to 0
if (asset_index == -1) {
txNew.vin[0].assetIssuance.nAmount = 0;
issuance_asset_keys.push_back(GetBlindingKey(&blindingScript));
blind_details->num_to_blind++;
}
}
}
// Asset being reissued with explicitly named asset/token
} else if (asset_index != -1) {
assert(reissuance_index != -1);
// Fill in output with issuance
txNew.vout[asset_index].nAsset = issuance_details->reissuance_asset;
// Fill in issuance
// Blinding revealing underlying asset
txNew.vin[reissuance_index].assetIssuance.assetBlindingNonce = token_blinding;
txNew.vin[reissuance_index].assetIssuance.assetEntropy = issuance_details->entropy;
txNew.vin[reissuance_index].assetIssuance.nAmount = txNew.vout[asset_index].nValue;
// If blinded token derivation, blind the issuance
CAsset temp_token;
CalculateReissuanceToken(temp_token, issuance_details->entropy, true);
if (temp_token == issuance_details->reissuance_token && blind_details) {
CScript blindingScript(CScript() << OP_RETURN << std::vector<unsigned char>(txNew.vin[reissuance_index].prevout.hash.begin(), txNew.vin[reissuance_index].prevout.hash.end()) << txNew.vin[reissuance_index].prevout.n);
issuance_asset_keys.resize(reissuance_index);
issuance_asset_keys.push_back(GetBlindingKey(&blindingScript));
blind_details->num_to_blind++;
}
}
}
if (blind_details) {
if (!fillBlindDetails(blind_details, this, txNew, selected_coins, error)) {
2019-03-20 11:01:36 +00:00
return false;
}
// Keep a backup of transaction in case re-blinding necessary
blind_details->tx_unblinded_unsigned = txNew;
int ret = BlindTransaction(blind_details->i_amount_blinds, blind_details->i_asset_blinds, blind_details->i_assets, blind_details->i_amounts, blind_details->o_amount_blinds, blind_details->o_asset_blinds, blind_details->o_pubkeys, issuance_asset_keys, issuance_token_keys, txNew);
assert(ret != -1);
if (ret != blind_details->num_to_blind) {
error = _("Unable to blind the transaction properly. This should not happen.");
2019-03-20 11:01:36 +00:00
return false;
}
}
nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, &coin_control);
if (nBytes < 0) {
error = _("Missing solving data for estimating transaction size");
return false;
}
2019-03-20 11:01:36 +00:00
// Remove blinding if we're not actually signing
if (blind_details && !sign) {
txNew = blind_details->tx_unblinded_unsigned;
}
nFeeNeeded = GetMinimumFee(*this, nBytes, coin_control, &feeCalc);
if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) {
// eventually allow a fallback fee
error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
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 + coin_selection_params.change_output_size + 2; // Add 2 as a buffer in case increasing # of outputs changes compact size
CAmount fee_needed_with_change = GetMinimumFee(*this, tx_size_with_change, coin_control, 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;
2019-03-20 11:01:36 +00:00
// If blinding we need to edit the unblinded tx and re-blind. Otherwise just edit the tx.
if (blind_details) {
txNew = blind_details->tx_unblinded_unsigned;
std::vector<CTxOut>::iterator change_position = txNew.vout.begin() + nChangePosInOut;
change_position->nValue = change_position->nValue.GetAmount() + extraFeePaid;
blind_details->o_amounts[nChangePosInOut] = change_position->nValue.GetAmount();
nFeeRet -= extraFeePaid;
txNew.vout.back().nValue = nFeeRet; // update fee output
blind_details->o_amounts.back() = nFeeRet;
// Wipe output blinding factors and start over
blind_details->o_amount_blinds.clear();
blind_details->o_asset_blinds.clear();
// Re-blind tx after editing and change.
blind_details->tx_unblinded_unsigned = txNew;
int ret = BlindTransaction(blind_details->i_amount_blinds, blind_details->i_asset_blinds, blind_details->i_assets, blind_details->i_amounts, blind_details->o_amount_blinds, blind_details->o_asset_blinds, blind_details->o_pubkeys, issuance_asset_keys, issuance_token_keys, txNew);
assert(ret != -1);
if (ret != blind_details->num_to_blind) {
error = _("Unable to blind the transaction properly. This should not happen.");
2019-03-20 11:01:36 +00:00
return false;
}
} else {
std::vector<CTxOut>::iterator change_position = txNew.vout.begin() + nChangePosInOut;
change_position->nValue = change_position->nValue.GetAmount() + extraFeePaid;
nFeeRet -= extraFeePaid;
if (g_con_elementsmode) {
txNew.vout.back().nValue = nFeeRet; // update fee output
}
}
}
break; // Done, enough fee included.
}
else if (!pick_new_inputs && !one_more_try_20347) {
// 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
error = _("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;
2019-03-20 11:01:36 +00:00
// If blinding we need to edit the unblinded tx and re-blind. Otherwise just edit the tx.
if (blind_details) {
txNew = blind_details->tx_unblinded_unsigned;
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.GetAmount() >= MIN_FINAL_CHANGE + additionalFeeNeeded) {
change_position->nValue = change_position->nValue.GetAmount() - additionalFeeNeeded;
blind_details->o_amounts[nChangePosInOut] = change_position->nValue.GetAmount();
nFeeRet += additionalFeeNeeded;
txNew.vout.back().nValue = nFeeRet; // update fee output
blind_details->o_amounts.back() = nFeeRet; // update change details
// Wipe output blinding factors and start over
blind_details->o_amount_blinds.clear();
blind_details->o_asset_blinds.clear();
// Re-blind tx after editing and change.
blind_details->tx_unblinded_unsigned = txNew;
int ret = BlindTransaction(blind_details->i_amount_blinds, blind_details->i_asset_blinds, blind_details->i_assets, blind_details->i_amounts, blind_details->o_amount_blinds, blind_details->o_asset_blinds, blind_details->o_pubkeys, issuance_asset_keys, issuance_token_keys, txNew);
assert(ret != -1);
if (ret != blind_details->num_to_blind) {
error = _("Unable to blind the transaction properly. This should not happen.");
2019-03-20 11:01:36 +00:00
return false;
}
break; // Done, able to increase fee from change
}
} else {
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.GetAmount() >= MIN_FINAL_CHANGE + additionalFeeNeeded) {
change_position->nValue = change_position->nValue.GetAmount() - additionalFeeNeeded;
nFeeRet += additionalFeeNeeded;
if (g_con_elementsmode) {
txNew.vout.back().nValue = nFeeRet; // update fee output
}
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
// In case we used branch-and-bound, this could result in our transaction
// since increasing since we force-elided change on this iteration for bnb.
// In this case we turn on `one_more_try` so that `!pick_new_inputs` doesn't
// cause the loop to fail. This is a stopgap. See Core #20347.
one_more_try_20347 = false;
if (nSubtractFeeFromAmount > 0) {
pick_new_inputs = false;
one_more_try_20347 = bnb_used;
}
// Include more fee and try again.
nFeeRet = nFeeNeeded;
coin_selection_params.use_bnb = false;
continue;
}
// Give up if change keypool ran out and change is required
Merge bbb1ba1814 into merged_master (Bitcoin PR #17219) This modifies the CreateTransaction loop in a way not remotely worth the complexity, and includes an absurdly fragile test where I had to add a bunch of trace statements and tweak pretty-much every single hardcoded number. Not to name names, but it was Sjors. (In fairness, the PR is a pure simplification of the CreateTransaction logic, and it wasn't hard to merge even. It was just the test that caused my grief.) Adapting the "use a dummy CTxDestination in the case that we cannot retrieve one from the wallet" logic to our `mapScriptChange` map was not trivial. On my first attempt I incorrectly assigned a positive vout index to the dummy script, which caused us to call `ReturnDestination` later on the (unused) dummy destination. This is harmless now, but when descriptor wallets are introduced in #16528, they introduce an edge case where returning a null destination can incorrectly mark the 0th key of a BIP32 range as unused. This triggered a test failure much later, in #19504, which uses descriptor wallets in fundrawtransaction. The bug was that we'd import a descriptor, mark the first key as being used, lock the wallet, call `fundrawtransaction` on a transaction that did not require change (incorrectly marking the first key as unused but leaving it in the descriptor ScriptPubKeyMan's cache), then call `fundrawtransaction` again on a transaction that *did* require change. The wallet would then incorrectly retrieve the "unused" key from cache and use it for change, rather than correctly failing and advising the user that it could not produce change with a locked wallet and empty keypool. This was not a fun bug to track down. Another interesting observation is that branch-and-bound uses the CT size-overestimate for change when trying to create changeless outputs, while our normal dust detection uses Core's unchanged "an output is 133 bytes" logic. So when BnB is used we're willing to delete a far bigger change output than we are when we don't use BnB. Lest you think this works in Core, they're also inconsistent because BnB uses a normal fee estimate for gauging change cost, while non-BnB uses the discardfee rate. My advice is to hold your nose, pull stuff in from Core as it comes in, and thanks to Andy's efforts things are getting better. Don't bother reviewing this too closely.
2020-12-03 00:58:01 +00:00
for (const auto& it : vChangePosInOut) {
if (mapScriptChange[it.first].second == dummy_script) {
Merge bbb1ba1814 into merged_master (Bitcoin PR #17219) This modifies the CreateTransaction loop in a way not remotely worth the complexity, and includes an absurdly fragile test where I had to add a bunch of trace statements and tweak pretty-much every single hardcoded number. Not to name names, but it was Sjors. (In fairness, the PR is a pure simplification of the CreateTransaction logic, and it wasn't hard to merge even. It was just the test that caused my grief.) Adapting the "use a dummy CTxDestination in the case that we cannot retrieve one from the wallet" logic to our `mapScriptChange` map was not trivial. On my first attempt I incorrectly assigned a positive vout index to the dummy script, which caused us to call `ReturnDestination` later on the (unused) dummy destination. This is harmless now, but when descriptor wallets are introduced in #16528, they introduce an edge case where returning a null destination can incorrectly mark the 0th key of a BIP32 range as unused. This triggered a test failure much later, in #19504, which uses descriptor wallets in fundrawtransaction. The bug was that we'd import a descriptor, mark the first key as being used, lock the wallet, call `fundrawtransaction` on a transaction that did not require change (incorrectly marking the first key as unused but leaving it in the descriptor ScriptPubKeyMan's cache), then call `fundrawtransaction` again on a transaction that *did* require change. The wallet would then incorrectly retrieve the "unused" key from cache and use it for change, rather than correctly failing and advising the user that it could not produce change with a locked wallet and empty keypool. This was not a fun bug to track down. Another interesting observation is that branch-and-bound uses the CT size-overestimate for change when trying to create changeless outputs, while our normal dust detection uses Core's unchanged "an output is 133 bytes" logic. So when BnB is used we're willing to delete a far bigger change output than we are when we don't use BnB. Lest you think this works in Core, they're also inconsistent because BnB uses a normal fee estimate for gauging change cost, while non-BnB uses the discardfee rate. My advice is to hold your nose, pull stuff in from Core as it comes in, and thanks to Andy's efforts things are getting better. Don't bother reviewing this too closely.
2020-12-03 00:58:01 +00:00
return false;
}
}
}
2019-03-20 11:01:36 +00:00
// Release any change keys that we didn't use.
2019-05-01 18:28:10 -07:00
for (const auto& it : mapScriptChange) {
2019-03-20 11:01:36 +00:00
int index = it.second.first;
if (index < 0) {
continue;
}
2019-03-20 11:01:36 +00:00
if (vChangePosInOut.find(it.first) == vChangePosInOut.end()) {
reservedest[index]->ReturnDestination();
2019-03-20 11:01:36 +00:00
}
}
// 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.m_signal_bip125_rbf.value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1);
2019-03-20 11:01:36 +00:00
for (auto& input : txNew.vin) {
// Remove sigs and then set sequence
input.scriptSig = CScript();
input.nSequence = nSequence;
}
// Also remove witness data for scripts
for (auto& inwit : txNew.witness.vtxinwit) {
inwit.scriptWitness.SetNull();
}
// Do the same things for unblinded version of tx when applicable
if (blind_details) {
for (auto& input : blind_details->tx_unblinded_unsigned.vin) {
input.nSequence = nSequence;
}
}
// Print blinded transaction info before we possibly blow it away when !sign.
if (blind_details) {
std::string summary = "CreateTransaction created blinded transaction:\nIN: ";
for (unsigned int i = 0; i < selected_coins.size(); ++i) {
if (i > 0) {
summary += " ";
}
summary += strprintf("#%d: %s [%s] (%s [%s])\n", i,
selected_coins[i].value,
selected_coins[i].txout.nValue.IsExplicit() ? "explicit" : "blinded",
selected_coins[i].asset.GetHex(),
selected_coins[i].txout.nAsset.IsExplicit() ? "explicit" : "blinded"
);
}
summary += "OUT: ";
for (unsigned int i = 0; i < txNew.vout.size(); ++i) {
if (i > 0) {
summary += " ";
}
CTxOut unblinded = blind_details->tx_unblinded_unsigned.vout[i];
summary += strprintf("#%d: %s%s [%s] (%s [%s])\n", i,
txNew.vout[i].IsFee() ? "[fee] " : "",
unblinded.nValue.GetAmount(),
txNew.vout[i].nValue.IsExplicit() ? "explicit" : "blinded",
unblinded.nAsset.GetAsset().GetHex(),
txNew.vout[i].nAsset.IsExplicit() ? "explicit" : "blinded"
);
}
WalletLogPrintf(summary+"\n");
}
if (sign) {
if (!SignTransaction(txNew)) {
error = _("Signing transaction failed");
return false;
}
2019-03-20 11:01:36 +00:00
} else if (blind_details) {
// "sign" also means blind for the purposes of making a complete tx
// or just funding one properly
txNew = blind_details->tx_unblinded_unsigned;
}
// Normalize the witness in case it is not serialized before mempool
if (!txNew.HasWitness()) {
txNew.witness.SetNull();
}
// Return the constructed transaction data.
tx = MakeTransactionRef(std::move(txNew));
// Limit size
if (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT)
{
error = _("Transaction too large");
return false;
}
}
if (nFeeRet > m_default_max_tx_fee) {
error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
return false;
}
if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits
if (!chain().checkChainLimits(tx)) {
error = _("Transaction has too long of a mempool chain");
return false;
}
}
// Before we return success, we assume any change key will be used to prevent
// accidental re-use.
for (auto& reservedest_ : reservedest) {
reservedest_->KeepDestination();
}
fee_calc_out = feeCalc;
WalletLogPrintf("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,
2020-11-12 16:33:06 +01:00
(feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) > 0.0 ? 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) : 0.0,
feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool,
feeCalc.est.fail.start, feeCalc.est.fail.end,
2020-11-12 16:33:06 +01:00
(feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0,
feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool);
return true;
}
bool CWallet::CreateTransaction(
const std::vector<CRecipient>& vecSend,
CTransactionRef& tx,
CAmount& nFeeRet,
int& nChangePosInOut,
bilingual_str& error,
const CCoinControl& coin_control,
FeeCalculation& fee_calc_out,
bool sign,
BlindDetails* blind_details,
const IssuanceDetails* issuance_details)
{
int nChangePosIn = nChangePosInOut;
Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr)
bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign, blind_details, issuance_details);
// try with avoidpartialspends unless it's enabled already
if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
CCoinControl tmp_cc = coin_control;
tmp_cc.m_avoid_partial_spends = true;
CAmount nFeeRet2;
CTransactionRef tx2;
int nChangePosInOut2 = nChangePosIn;
bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
BlindDetails blind_details2;
BlindDetails *blind_details2_ptr = blind_details ? &blind_details2 : nullptr;
if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign, blind_details2_ptr, issuance_details)) {
// if fee of this alternative one is within the range of the max fee, we use this one
const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee;
WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped");
if (use_aps) {
tx = tx2;
nFeeRet = nFeeRet2;
nChangePosInOut = nChangePosInOut2;
if (blind_details) {
*blind_details = blind_details2;
}
}
}
}
return res;
}
void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, const BlindDetails* blind_details)
{
LOCK(cs_wallet);
WalletLogPrintf("CommitTransaction:\n%s", tx->ToString()); /* Continued */
// Add tx to wallet, because if it has change it's also ours,
// otherwise just for transaction history.
AddToWallet(tx, {}, [&](CWalletTx& wtx, bool new_tx) {
CHECK_NONFATAL(wtx.mapValue.empty());
CHECK_NONFATAL(wtx.vOrderForm.empty());
wtx.mapValue = std::move(mapValue);
wtx.vOrderForm = std::move(orderForm);
wtx.fTimeReceivedIsTxTime = true;
wtx.fFromMe = true;
// ELEMENTS: Write down blinding information
if (blind_details) {
assert(blind_details->o_amounts.size() == wtx.tx->vout.size());
assert(blind_details->o_asset_blinds.size() == wtx.tx->vout.size());
assert(blind_details->o_amount_blinds.size() == wtx.tx->vout.size());
for (unsigned int i = 0; i < blind_details->o_amounts.size(); i++) {
wtx.SetBlindingData(i, blind_details->o_pubkeys[i], blind_details->o_amounts[i], blind_details->o_amount_blinds[i], blind_details->o_assets[i], blind_details->o_asset_blinds[i]);
}
}
return true;
});
// Notify that old coins are spent
for (const CTxIn& txin : tx->vin) {
// ELEMENTS: Pegins are not in our UTXO set.
if (txin.m_is_pegin)
continue;
CWalletTx &coin = mapWallet.at(txin.prevout.hash);
coin.MarkDirty();
NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED);
}
// Get the inserted-CWalletTx from mapWallet so that the
// fInMempool flag is cached properly
CWalletTx& wtx = mapWallet.at(tx->GetHash());
if (!fBroadcastTransactions) {
// Don't submit tx to the mempool
return;
}
std::string err_string;
if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) {
WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string);
// TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
}
}
DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
LOCK(cs_wallet);
fFirstRunRet = false;
DBErrors nLoadWalletRet = WalletBatch(GetDatabase()).LoadWallet(this);
scripted-diff: Convert 11 enums into scoped enums (C++11) -BEGIN VERIFY SCRIPT- sed -i 's/enum DBErrors/enum class DBErrors/g' src/wallet/walletdb.h git grep -l DB_ | xargs sed -i 's/DB_\(LOAD_OK\|CORRUPT\|NONCRITICAL_ERROR\|TOO_NEW\|LOAD_FAIL\|NEED_REWRITE\)/DBErrors::\1/g' sed -i 's/^ DBErrors::/ /g' src/wallet/walletdb.h sed -i 's/enum VerifyResult/enum class VerifyResult/g' src/wallet/db.h sed -i 's/\(VERIFY_OK\|RECOVER_OK\|RECOVER_FAIL\)/VerifyResult::\1/g' src/wallet/db.cpp sed -i 's/enum ThresholdState/enum class ThresholdState/g' src/versionbits.h git grep -l THRESHOLD_ | xargs sed -i 's/THRESHOLD_\(DEFINED\|STARTED\|LOCKED_IN\|ACTIVE\|FAILED\)/ThresholdState::\1/g' sed -i 's/^ ThresholdState::/ /g' src/versionbits.h sed -i 's/enum SigVersion/enum class SigVersion/g' src/script/interpreter.h git grep -l SIGVERSION_ | xargs sed -i 's/SIGVERSION_\(BASE\|WITNESS_V0\)/SigVersion::\1/g' sed -i 's/^ SigVersion::/ /g' src/script/interpreter.h sed -i 's/enum RetFormat {/enum class RetFormat {/g' src/rest.cpp sed -i 's/RF_\(UNDEF\|BINARY\|HEX\|JSON\)/RetFormat::\1/g' src/rest.cpp sed -i 's/^ RetFormat::/ /g' src/rest.cpp sed -i 's/enum HelpMessageMode {/enum class HelpMessageMode {/g' src/init.h git grep -l HMM_ | xargs sed -i 's/HMM_BITCOIN/HelpMessageMode::BITCOIN/g' sed -i 's/^ HelpMessageMode::/ /g' src/init.h sed -i 's/enum FeeEstimateHorizon/enum class FeeEstimateHorizon/g' src/policy/fees.h sed -i 's/enum RBFTransactionState/enum class RBFTransactionState/g' src/policy/rbf.h git grep -l RBF_ | xargs sed -i 's/RBF_TRANSACTIONSTATE_\(UNKNOWN\|REPLACEABLE_BIP125\|FINAL\)/RBFTransactionState::\1/g' sed -i 's/^ RBFTransactionState::/ /g' src/policy/rbf.h sed -i 's/enum BlockSource {/enum class BlockSource {/g' src/qt/clientmodel.h git grep -l BLOCK_SOURCE_ | xargs sed -i 's/BLOCK_SOURCE_\(NONE\|REINDEX\|DISK\|NETWORK\)/BlockSource::\1/g' sed -i 's/^ BlockSource::/ /g' src/qt/clientmodel.h sed -i 's/enum FlushStateMode {/enum class FlushStateMode {/g' src/validation.cpp sed -i 's/FLUSH_STATE_\(NONE\|IF_NEEDED\|PERIODIC\|ALWAYS\)/FlushStateMode::\1/g' src/validation.cpp sed -i 's/^ FlushStateMode::/ /g' src/validation.cpp sed -i 's/enum WitnessMode {/enum class WitnessMode {/g' src/test/script_tests.cpp sed -i 's/WITNESS_\(NONE\|PKH\|SH\)/WitnessMode::\1/g' src/test/script_tests.cpp sed -i 's/^ WitnessMode::/ /g' src/test/script_tests.cpp -END VERIFY SCRIPT-
2018-03-09 15:03:40 +01:00
if (nLoadWalletRet == DBErrors::NEED_REWRITE)
2011-11-10 21:29:23 +01:00
{
if (GetDatabase().Rewrite("\x04pool"))
{
for (const auto& spk_man_pair : m_spk_managers) {
spk_man_pair.second->RewriteDB();
}
}
2011-11-10 21:29:23 +01:00
}
// This wallet is in its first run if there are no ScriptPubKeyMans and it isn't blank or no privkeys
fFirstRunRet = m_spk_managers.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
if (fFirstRunRet) {
assert(m_external_spk_managers.empty());
assert(m_internal_spk_managers.empty());
2018-04-29 20:15:05 +02:00
}
scripted-diff: Convert 11 enums into scoped enums (C++11) -BEGIN VERIFY SCRIPT- sed -i 's/enum DBErrors/enum class DBErrors/g' src/wallet/walletdb.h git grep -l DB_ | xargs sed -i 's/DB_\(LOAD_OK\|CORRUPT\|NONCRITICAL_ERROR\|TOO_NEW\|LOAD_FAIL\|NEED_REWRITE\)/DBErrors::\1/g' sed -i 's/^ DBErrors::/ /g' src/wallet/walletdb.h sed -i 's/enum VerifyResult/enum class VerifyResult/g' src/wallet/db.h sed -i 's/\(VERIFY_OK\|RECOVER_OK\|RECOVER_FAIL\)/VerifyResult::\1/g' src/wallet/db.cpp sed -i 's/enum ThresholdState/enum class ThresholdState/g' src/versionbits.h git grep -l THRESHOLD_ | xargs sed -i 's/THRESHOLD_\(DEFINED\|STARTED\|LOCKED_IN\|ACTIVE\|FAILED\)/ThresholdState::\1/g' sed -i 's/^ ThresholdState::/ /g' src/versionbits.h sed -i 's/enum SigVersion/enum class SigVersion/g' src/script/interpreter.h git grep -l SIGVERSION_ | xargs sed -i 's/SIGVERSION_\(BASE\|WITNESS_V0\)/SigVersion::\1/g' sed -i 's/^ SigVersion::/ /g' src/script/interpreter.h sed -i 's/enum RetFormat {/enum class RetFormat {/g' src/rest.cpp sed -i 's/RF_\(UNDEF\|BINARY\|HEX\|JSON\)/RetFormat::\1/g' src/rest.cpp sed -i 's/^ RetFormat::/ /g' src/rest.cpp sed -i 's/enum HelpMessageMode {/enum class HelpMessageMode {/g' src/init.h git grep -l HMM_ | xargs sed -i 's/HMM_BITCOIN/HelpMessageMode::BITCOIN/g' sed -i 's/^ HelpMessageMode::/ /g' src/init.h sed -i 's/enum FeeEstimateHorizon/enum class FeeEstimateHorizon/g' src/policy/fees.h sed -i 's/enum RBFTransactionState/enum class RBFTransactionState/g' src/policy/rbf.h git grep -l RBF_ | xargs sed -i 's/RBF_TRANSACTIONSTATE_\(UNKNOWN\|REPLACEABLE_BIP125\|FINAL\)/RBFTransactionState::\1/g' sed -i 's/^ RBFTransactionState::/ /g' src/policy/rbf.h sed -i 's/enum BlockSource {/enum class BlockSource {/g' src/qt/clientmodel.h git grep -l BLOCK_SOURCE_ | xargs sed -i 's/BLOCK_SOURCE_\(NONE\|REINDEX\|DISK\|NETWORK\)/BlockSource::\1/g' sed -i 's/^ BlockSource::/ /g' src/qt/clientmodel.h sed -i 's/enum FlushStateMode {/enum class FlushStateMode {/g' src/validation.cpp sed -i 's/FLUSH_STATE_\(NONE\|IF_NEEDED\|PERIODIC\|ALWAYS\)/FlushStateMode::\1/g' src/validation.cpp sed -i 's/^ FlushStateMode::/ /g' src/validation.cpp sed -i 's/enum WitnessMode {/enum class WitnessMode {/g' src/test/script_tests.cpp sed -i 's/WITNESS_\(NONE\|PKH\|SH\)/WitnessMode::\1/g' src/test/script_tests.cpp sed -i 's/^ WitnessMode::/ /g' src/test/script_tests.cpp -END VERIFY SCRIPT-
2018-03-09 15:03:40 +01:00
if (nLoadWalletRet != DBErrors::LOAD_OK)
2011-07-05 03:06:19 +02:00
return nLoadWalletRet;
scripted-diff: Convert 11 enums into scoped enums (C++11) -BEGIN VERIFY SCRIPT- sed -i 's/enum DBErrors/enum class DBErrors/g' src/wallet/walletdb.h git grep -l DB_ | xargs sed -i 's/DB_\(LOAD_OK\|CORRUPT\|NONCRITICAL_ERROR\|TOO_NEW\|LOAD_FAIL\|NEED_REWRITE\)/DBErrors::\1/g' sed -i 's/^ DBErrors::/ /g' src/wallet/walletdb.h sed -i 's/enum VerifyResult/enum class VerifyResult/g' src/wallet/db.h sed -i 's/\(VERIFY_OK\|RECOVER_OK\|RECOVER_FAIL\)/VerifyResult::\1/g' src/wallet/db.cpp sed -i 's/enum ThresholdState/enum class ThresholdState/g' src/versionbits.h git grep -l THRESHOLD_ | xargs sed -i 's/THRESHOLD_\(DEFINED\|STARTED\|LOCKED_IN\|ACTIVE\|FAILED\)/ThresholdState::\1/g' sed -i 's/^ ThresholdState::/ /g' src/versionbits.h sed -i 's/enum SigVersion/enum class SigVersion/g' src/script/interpreter.h git grep -l SIGVERSION_ | xargs sed -i 's/SIGVERSION_\(BASE\|WITNESS_V0\)/SigVersion::\1/g' sed -i 's/^ SigVersion::/ /g' src/script/interpreter.h sed -i 's/enum RetFormat {/enum class RetFormat {/g' src/rest.cpp sed -i 's/RF_\(UNDEF\|BINARY\|HEX\|JSON\)/RetFormat::\1/g' src/rest.cpp sed -i 's/^ RetFormat::/ /g' src/rest.cpp sed -i 's/enum HelpMessageMode {/enum class HelpMessageMode {/g' src/init.h git grep -l HMM_ | xargs sed -i 's/HMM_BITCOIN/HelpMessageMode::BITCOIN/g' sed -i 's/^ HelpMessageMode::/ /g' src/init.h sed -i 's/enum FeeEstimateHorizon/enum class FeeEstimateHorizon/g' src/policy/fees.h sed -i 's/enum RBFTransactionState/enum class RBFTransactionState/g' src/policy/rbf.h git grep -l RBF_ | xargs sed -i 's/RBF_TRANSACTIONSTATE_\(UNKNOWN\|REPLACEABLE_BIP125\|FINAL\)/RBFTransactionState::\1/g' sed -i 's/^ RBFTransactionState::/ /g' src/policy/rbf.h sed -i 's/enum BlockSource {/enum class BlockSource {/g' src/qt/clientmodel.h git grep -l BLOCK_SOURCE_ | xargs sed -i 's/BLOCK_SOURCE_\(NONE\|REINDEX\|DISK\|NETWORK\)/BlockSource::\1/g' sed -i 's/^ BlockSource::/ /g' src/qt/clientmodel.h sed -i 's/enum FlushStateMode {/enum class FlushStateMode {/g' src/validation.cpp sed -i 's/FLUSH_STATE_\(NONE\|IF_NEEDED\|PERIODIC\|ALWAYS\)/FlushStateMode::\1/g' src/validation.cpp sed -i 's/^ FlushStateMode::/ /g' src/validation.cpp sed -i 's/enum WitnessMode {/enum class WitnessMode {/g' src/test/script_tests.cpp sed -i 's/WITNESS_\(NONE\|PKH\|SH\)/WitnessMode::\1/g' src/test/script_tests.cpp sed -i 's/^ WitnessMode::/ /g' src/test/script_tests.cpp -END VERIFY SCRIPT-
2018-03-09 15:03:40 +01:00
return DBErrors::LOAD_OK;
}
DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut)
{
AssertLockHeld(cs_wallet);
DBErrors nZapSelectTxRet = WalletBatch(GetDatabase()).ZapSelectTx(vHashIn, vHashOut);
for (const uint256& hash : vHashOut) {
const auto& it = mapWallet.find(hash);
wtxOrdered.erase(it->second.m_it_wtxOrdered);
for (const auto& txin : it->second.tx->vin)
mapTxSpends.erase(txin.prevout);
mapWallet.erase(it);
NotifyTransactionChanged(this, hash, CT_DELETED);
}
scripted-diff: Convert 11 enums into scoped enums (C++11) -BEGIN VERIFY SCRIPT- sed -i 's/enum DBErrors/enum class DBErrors/g' src/wallet/walletdb.h git grep -l DB_ | xargs sed -i 's/DB_\(LOAD_OK\|CORRUPT\|NONCRITICAL_ERROR\|TOO_NEW\|LOAD_FAIL\|NEED_REWRITE\)/DBErrors::\1/g' sed -i 's/^ DBErrors::/ /g' src/wallet/walletdb.h sed -i 's/enum VerifyResult/enum class VerifyResult/g' src/wallet/db.h sed -i 's/\(VERIFY_OK\|RECOVER_OK\|RECOVER_FAIL\)/VerifyResult::\1/g' src/wallet/db.cpp sed -i 's/enum ThresholdState/enum class ThresholdState/g' src/versionbits.h git grep -l THRESHOLD_ | xargs sed -i 's/THRESHOLD_\(DEFINED\|STARTED\|LOCKED_IN\|ACTIVE\|FAILED\)/ThresholdState::\1/g' sed -i 's/^ ThresholdState::/ /g' src/versionbits.h sed -i 's/enum SigVersion/enum class SigVersion/g' src/script/interpreter.h git grep -l SIGVERSION_ | xargs sed -i 's/SIGVERSION_\(BASE\|WITNESS_V0\)/SigVersion::\1/g' sed -i 's/^ SigVersion::/ /g' src/script/interpreter.h sed -i 's/enum RetFormat {/enum class RetFormat {/g' src/rest.cpp sed -i 's/RF_\(UNDEF\|BINARY\|HEX\|JSON\)/RetFormat::\1/g' src/rest.cpp sed -i 's/^ RetFormat::/ /g' src/rest.cpp sed -i 's/enum HelpMessageMode {/enum class HelpMessageMode {/g' src/init.h git grep -l HMM_ | xargs sed -i 's/HMM_BITCOIN/HelpMessageMode::BITCOIN/g' sed -i 's/^ HelpMessageMode::/ /g' src/init.h sed -i 's/enum FeeEstimateHorizon/enum class FeeEstimateHorizon/g' src/policy/fees.h sed -i 's/enum RBFTransactionState/enum class RBFTransactionState/g' src/policy/rbf.h git grep -l RBF_ | xargs sed -i 's/RBF_TRANSACTIONSTATE_\(UNKNOWN\|REPLACEABLE_BIP125\|FINAL\)/RBFTransactionState::\1/g' sed -i 's/^ RBFTransactionState::/ /g' src/policy/rbf.h sed -i 's/enum BlockSource {/enum class BlockSource {/g' src/qt/clientmodel.h git grep -l BLOCK_SOURCE_ | xargs sed -i 's/BLOCK_SOURCE_\(NONE\|REINDEX\|DISK\|NETWORK\)/BlockSource::\1/g' sed -i 's/^ BlockSource::/ /g' src/qt/clientmodel.h sed -i 's/enum FlushStateMode {/enum class FlushStateMode {/g' src/validation.cpp sed -i 's/FLUSH_STATE_\(NONE\|IF_NEEDED\|PERIODIC\|ALWAYS\)/FlushStateMode::\1/g' src/validation.cpp sed -i 's/^ FlushStateMode::/ /g' src/validation.cpp sed -i 's/enum WitnessMode {/enum class WitnessMode {/g' src/test/script_tests.cpp sed -i 's/WITNESS_\(NONE\|PKH\|SH\)/WitnessMode::\1/g' src/test/script_tests.cpp sed -i 's/^ WitnessMode::/ /g' src/test/script_tests.cpp -END VERIFY SCRIPT-
2018-03-09 15:03:40 +01:00
if (nZapSelectTxRet == DBErrors::NEED_REWRITE)
{
if (GetDatabase().Rewrite("\x04pool"))
{
for (const auto& spk_man_pair : m_spk_managers) {
spk_man_pair.second->RewriteDB();
}
}
}
scripted-diff: Convert 11 enums into scoped enums (C++11) -BEGIN VERIFY SCRIPT- sed -i 's/enum DBErrors/enum class DBErrors/g' src/wallet/walletdb.h git grep -l DB_ | xargs sed -i 's/DB_\(LOAD_OK\|CORRUPT\|NONCRITICAL_ERROR\|TOO_NEW\|LOAD_FAIL\|NEED_REWRITE\)/DBErrors::\1/g' sed -i 's/^ DBErrors::/ /g' src/wallet/walletdb.h sed -i 's/enum VerifyResult/enum class VerifyResult/g' src/wallet/db.h sed -i 's/\(VERIFY_OK\|RECOVER_OK\|RECOVER_FAIL\)/VerifyResult::\1/g' src/wallet/db.cpp sed -i 's/enum ThresholdState/enum class ThresholdState/g' src/versionbits.h git grep -l THRESHOLD_ | xargs sed -i 's/THRESHOLD_\(DEFINED\|STARTED\|LOCKED_IN\|ACTIVE\|FAILED\)/ThresholdState::\1/g' sed -i 's/^ ThresholdState::/ /g' src/versionbits.h sed -i 's/enum SigVersion/enum class SigVersion/g' src/script/interpreter.h git grep -l SIGVERSION_ | xargs sed -i 's/SIGVERSION_\(BASE\|WITNESS_V0\)/SigVersion::\1/g' sed -i 's/^ SigVersion::/ /g' src/script/interpreter.h sed -i 's/enum RetFormat {/enum class RetFormat {/g' src/rest.cpp sed -i 's/RF_\(UNDEF\|BINARY\|HEX\|JSON\)/RetFormat::\1/g' src/rest.cpp sed -i 's/^ RetFormat::/ /g' src/rest.cpp sed -i 's/enum HelpMessageMode {/enum class HelpMessageMode {/g' src/init.h git grep -l HMM_ | xargs sed -i 's/HMM_BITCOIN/HelpMessageMode::BITCOIN/g' sed -i 's/^ HelpMessageMode::/ /g' src/init.h sed -i 's/enum FeeEstimateHorizon/enum class FeeEstimateHorizon/g' src/policy/fees.h sed -i 's/enum RBFTransactionState/enum class RBFTransactionState/g' src/policy/rbf.h git grep -l RBF_ | xargs sed -i 's/RBF_TRANSACTIONSTATE_\(UNKNOWN\|REPLACEABLE_BIP125\|FINAL\)/RBFTransactionState::\1/g' sed -i 's/^ RBFTransactionState::/ /g' src/policy/rbf.h sed -i 's/enum BlockSource {/enum class BlockSource {/g' src/qt/clientmodel.h git grep -l BLOCK_SOURCE_ | xargs sed -i 's/BLOCK_SOURCE_\(NONE\|REINDEX\|DISK\|NETWORK\)/BlockSource::\1/g' sed -i 's/^ BlockSource::/ /g' src/qt/clientmodel.h sed -i 's/enum FlushStateMode {/enum class FlushStateMode {/g' src/validation.cpp sed -i 's/FLUSH_STATE_\(NONE\|IF_NEEDED\|PERIODIC\|ALWAYS\)/FlushStateMode::\1/g' src/validation.cpp sed -i 's/^ FlushStateMode::/ /g' src/validation.cpp sed -i 's/enum WitnessMode {/enum class WitnessMode {/g' src/test/script_tests.cpp sed -i 's/WITNESS_\(NONE\|PKH\|SH\)/WitnessMode::\1/g' src/test/script_tests.cpp sed -i 's/^ WitnessMode::/ /g' src/test/script_tests.cpp -END VERIFY SCRIPT-
2018-03-09 15:03:40 +01:00
if (nZapSelectTxRet != DBErrors::LOAD_OK)
return nZapSelectTxRet;
MarkDirty();
scripted-diff: Convert 11 enums into scoped enums (C++11) -BEGIN VERIFY SCRIPT- sed -i 's/enum DBErrors/enum class DBErrors/g' src/wallet/walletdb.h git grep -l DB_ | xargs sed -i 's/DB_\(LOAD_OK\|CORRUPT\|NONCRITICAL_ERROR\|TOO_NEW\|LOAD_FAIL\|NEED_REWRITE\)/DBErrors::\1/g' sed -i 's/^ DBErrors::/ /g' src/wallet/walletdb.h sed -i 's/enum VerifyResult/enum class VerifyResult/g' src/wallet/db.h sed -i 's/\(VERIFY_OK\|RECOVER_OK\|RECOVER_FAIL\)/VerifyResult::\1/g' src/wallet/db.cpp sed -i 's/enum ThresholdState/enum class ThresholdState/g' src/versionbits.h git grep -l THRESHOLD_ | xargs sed -i 's/THRESHOLD_\(DEFINED\|STARTED\|LOCKED_IN\|ACTIVE\|FAILED\)/ThresholdState::\1/g' sed -i 's/^ ThresholdState::/ /g' src/versionbits.h sed -i 's/enum SigVersion/enum class SigVersion/g' src/script/interpreter.h git grep -l SIGVERSION_ | xargs sed -i 's/SIGVERSION_\(BASE\|WITNESS_V0\)/SigVersion::\1/g' sed -i 's/^ SigVersion::/ /g' src/script/interpreter.h sed -i 's/enum RetFormat {/enum class RetFormat {/g' src/rest.cpp sed -i 's/RF_\(UNDEF\|BINARY\|HEX\|JSON\)/RetFormat::\1/g' src/rest.cpp sed -i 's/^ RetFormat::/ /g' src/rest.cpp sed -i 's/enum HelpMessageMode {/enum class HelpMessageMode {/g' src/init.h git grep -l HMM_ | xargs sed -i 's/HMM_BITCOIN/HelpMessageMode::BITCOIN/g' sed -i 's/^ HelpMessageMode::/ /g' src/init.h sed -i 's/enum FeeEstimateHorizon/enum class FeeEstimateHorizon/g' src/policy/fees.h sed -i 's/enum RBFTransactionState/enum class RBFTransactionState/g' src/policy/rbf.h git grep -l RBF_ | xargs sed -i 's/RBF_TRANSACTIONSTATE_\(UNKNOWN\|REPLACEABLE_BIP125\|FINAL\)/RBFTransactionState::\1/g' sed -i 's/^ RBFTransactionState::/ /g' src/policy/rbf.h sed -i 's/enum BlockSource {/enum class BlockSource {/g' src/qt/clientmodel.h git grep -l BLOCK_SOURCE_ | xargs sed -i 's/BLOCK_SOURCE_\(NONE\|REINDEX\|DISK\|NETWORK\)/BlockSource::\1/g' sed -i 's/^ BlockSource::/ /g' src/qt/clientmodel.h sed -i 's/enum FlushStateMode {/enum class FlushStateMode {/g' src/validation.cpp sed -i 's/FLUSH_STATE_\(NONE\|IF_NEEDED\|PERIODIC\|ALWAYS\)/FlushStateMode::\1/g' src/validation.cpp sed -i 's/^ FlushStateMode::/ /g' src/validation.cpp sed -i 's/enum WitnessMode {/enum class WitnessMode {/g' src/test/script_tests.cpp sed -i 's/WITNESS_\(NONE\|PKH\|SH\)/WitnessMode::\1/g' src/test/script_tests.cpp sed -i 's/^ WitnessMode::/ /g' src/test/script_tests.cpp -END VERIFY SCRIPT-
2018-03-09 15:03:40 +01:00
return DBErrors::LOAD_OK;
}
bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::string& strPurpose)
{
bool fUpdated = false;
bool is_mine;
{
LOCK(cs_wallet);
std::map<CTxDestination, CAddressBookData>::iterator mi = m_address_book.find(address);
fUpdated = (mi != m_address_book.end() && !mi->second.IsChange());
m_address_book[address].SetLabel(strName);
if (!strPurpose.empty()) /* update purpose only if requested */
m_address_book[address].purpose = strPurpose;
is_mine = IsMine(address) != ISMINE_NO;
}
NotifyAddressBookChanged(this, address, strName, is_mine,
strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) );
if (!strPurpose.empty() && !batch.WritePurpose(EncodeDestination(address), strPurpose))
return false;
return batch.WriteName(EncodeDestination(address), strName);
}
bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& strPurpose)
{
WalletBatch batch(GetDatabase());
return SetAddressBookWithDB(batch, address, strName, strPurpose);
}
bool CWallet::DelAddressBook(const CTxDestination& address)
{
bool is_mine;
WalletBatch batch(GetDatabase());
{
LOCK(cs_wallet);
// If we want to delete receiving addresses, we need to take care that DestData "used" (and possibly newer DestData) gets preserved (and the "deleted" address transformed into a change entry instead of actually being deleted)
// NOTE: This isn't a problem for sending addresses because they never have any DestData yet!
// When adding new DestData, it should be considered here whether to retain or delete it (or move it?).
if (IsMine(address)) {
WalletLogPrintf("%s called with IsMine address, NOT SUPPORTED. Please report this bug! %s\n", __func__, PACKAGE_BUGREPORT);
return false;
}
// Delete destdata tuples associated with address
std::string strAddress = EncodeDestination(address);
for (const std::pair<const std::string, std::string> &item : m_address_book[address].destdata)
{
batch.EraseDestData(strAddress, item.first);
}
m_address_book.erase(address);
is_mine = IsMine(address) != ISMINE_NO;
}
NotifyAddressBookChanged(this, address, "", is_mine, "", CT_DELETED);
batch.ErasePurpose(EncodeDestination(address));
return batch.EraseName(EncodeDestination(address));
}
size_t CWallet::KeypoolCountExternalKeys() const
{
AssertLockHeld(cs_wallet);
unsigned int count = 0;
for (auto spk_man : GetActiveScriptPubKeyMans()) {
count += spk_man->KeypoolCountExternalKeys();
}
return count;
}
unsigned int CWallet::GetKeyPoolSize() const
{
AssertLockHeld(cs_wallet);
unsigned int count = 0;
for (auto spk_man : GetActiveScriptPubKeyMans()) {
count += spk_man->GetKeyPoolSize();
}
return count;
}
bool CWallet::TopUpKeyPool(unsigned int kpSize)
{
LOCK(cs_wallet);
bool res = true;
for (auto spk_man : GetActiveScriptPubKeyMans()) {
res &= spk_man->TopUp(kpSize);
}
return res;
}
/// ELEMENTS: get PAK online key
bool CWallet::GetOnlinePakKey(CPubKey& online_pubkey, std::string& error)
{
LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
if (spk_man) {
return spk_man->GetOnlinePakKey(online_pubkey, error);
}
return false;
}
/// end ELEMENTS
bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error, bool add_blinding_key)
{
LOCK(cs_wallet);
error.clear();
bool result = false;
auto spk_man = GetScriptPubKeyMan(type, false /* internal */);
if (spk_man) {
spk_man->TopUp();
result = spk_man->GetNewDestination(type, dest, error);
if (add_blinding_key) {
CPubKey blinding_pubkey = GetBlindingPubKey(GetScriptForDestination(dest));
boost::apply_visitor(SetBlindingPubKeyVisitor(blinding_pubkey), dest);
}
} else {
error = strprintf("Error: No %s addresses available.", FormatOutputType(type));
}
if (result) {
SetAddressBook(dest, label, "receive");
}
return result;
}
bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& dest, std::string& error, bool add_blinding_key)
{
LOCK(cs_wallet);
error.clear();
ReserveDestination reservedest(this, type);
if (!reservedest.GetReservedDestination(dest, true)) {
error = _("Error: Keypool ran out, please call keypoolrefill first").translated;
return false;
}
if (add_blinding_key) {
CPubKey blinding_pubkey = GetBlindingPubKey(GetScriptForDestination(dest));
reservedest.SetBlindingPubKey(blinding_pubkey, dest);
}
reservedest.KeepDestination();
return true;
}
int64_t CWallet::GetOldestKeyPoolTime() const
{
LOCK(cs_wallet);
int64_t oldestKey = std::numeric_limits<int64_t>::max();
for (const auto& spk_man_pair : m_spk_managers) {
oldestKey = std::min(oldestKey, spk_man_pair.second->GetOldestKeyPoolTime());
}
return oldestKey;
}
void CWallet::MarkDestinationsDirty(const std::set<CTxDestination>& destinations) {
for (auto& entry : mapWallet) {
CWalletTx& wtx = entry.second;
if (wtx.m_is_cache_empty) continue;
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
CTxDestination dst;
if (ExtractDestination(wtx.tx->vout[i].scriptPubKey, dst) && destinations.count(dst)) {
wtx.MarkDirty();
break;
}
}
}
}
std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() const
{
std::map<CTxDestination, CAmount> balances;
{
LOCK(cs_wallet);
std::set<uint256> trusted_parents;
for (const auto& walletEntry : mapWallet)
{
const CWalletTx& wtx = walletEntry.second;
if (!IsTrusted(wtx, trusted_parents))
continue;
if (wtx.IsImmatureCoinBase())
continue;
int nDepth = wtx.GetDepthInMainChain();
if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1))
continue;
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
{
CTxDestination addr;
if (!IsMine(wtx.tx->vout[i]))
continue;
if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr))
continue;
CAmount n = IsSpent(walletEntry.first, i) ? 0 : wtx.GetOutputValueOut(i);
2019-03-20 11:01:36 +00:00
if (n < 0) {
continue;
}
balances[addr] += n;
}
}
}
return balances;
}
2020-03-02 17:17:33 +09:00
std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const
{
AssertLockHeld(cs_wallet);
std::set< std::set<CTxDestination> > groupings;
std::set<CTxDestination> grouping;
for (const auto& walletEntry : mapWallet)
{
const CWalletTx& wtx = walletEntry.second;
if (wtx.tx->vin.size() > 0)
{
bool any_mine = false;
// group all input addresses with each other
for (const CTxIn& txin : wtx.tx->vin)
{
CTxDestination address;
if(!IsMine(txin)) /* If this input isn't mine, ignore it */
continue;
if(!ExtractDestination(mapWallet.at(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 (const CTxOut& txout : wtx.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 : wtx.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 (const 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 (const CTxDestination& element : *merged)
setmap[element] = merged;
}
std::set< std::set<CTxDestination> > ret;
for (const std::set<CTxDestination>* uniqueGrouping : uniqueGroupings)
{
ret.insert(*uniqueGrouping);
delete uniqueGrouping;
}
return ret;
}
std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) const
{
LOCK(cs_wallet);
std::set<CTxDestination> result;
for (const std::pair<const CTxDestination, CAddressBookData>& item : m_address_book)
{
if (item.second.IsChange()) continue;
const CTxDestination& address = item.first;
const std::string& strName = item.second.GetLabel();
if (strName == label)
result.insert(address);
}
return result;
}
bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal)
{
m_spk_man = pwallet->GetScriptPubKeyMan(type, internal);
if (!m_spk_man) {
return false;
}
if (nIndex == -1)
{
m_spk_man->TopUp();
CKeyPool keypool;
if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool)) {
return false;
}
fInternal = keypool.fInternal;
}
dest = address;
return true;
}
void ReserveDestination::SetBlindingPubKey(const CPubKey& blinding_pubkey, CTxDestination& dest)
{
boost::apply_visitor(SetBlindingPubKeyVisitor(blinding_pubkey), address);
dest = address;
}
void ReserveDestination::KeepDestination()
{
if (nIndex != -1) {
m_spk_man->KeepDestination(nIndex, type);
}
nIndex = -1;
address = CNoDestination();
}
void ReserveDestination::ReturnDestination()
{
if (nIndex != -1) {
m_spk_man->ReturnDestination(nIndex, fInternal, address);
}
nIndex = -1;
address = CNoDestination();
}
void CWallet::LockCoin(const COutPoint& output)
{
AssertLockHeld(cs_wallet);
setLockedCoins.insert(output);
}
void CWallet::UnlockCoin(const COutPoint& output)
{
AssertLockHeld(cs_wallet);
setLockedCoins.erase(output);
}
void CWallet::UnlockAllCoins()
{
AssertLockHeld(cs_wallet);
setLockedCoins.clear();
}
bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const
{
AssertLockHeld(cs_wallet);
COutPoint outpt(hash, n);
return (setLockedCoins.count(outpt) > 0);
}
void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const
{
AssertLockHeld(cs_wallet);
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<CKeyID, int64_t>& mapKeyBirth) const {
AssertLockHeld(cs_wallet);
mapKeyBirth.clear();
LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
assert(spk_man != nullptr);
LOCK(spk_man->cs_KeyStore);
// get birth times for keys with metadata
for (const auto& entry : spk_man->mapKeyMetadata) {
if (entry.second.nCreateTime) {
mapKeyBirth[entry.first] = entry.second.nCreateTime;
}
}
// map in which we'll infer heights of other keys
std::map<CKeyID, const CWalletTx::Confirmation*> mapKeyFirstBlock;
CWalletTx::Confirmation max_confirm;
max_confirm.block_height = GetLastBlockHeight() > 144 ? GetLastBlockHeight() - 144 : 0; // the tip can be reorganized; use a 144-block safety margin
CHECK_NONFATAL(chain().findAncestorByHeight(GetLastBlockHash(), max_confirm.block_height, FoundBlock().hash(max_confirm.hashBlock)));
for (const CKeyID &keyid : spk_man->GetKeys()) {
if (mapKeyBirth.count(keyid) == 0)
mapKeyFirstBlock[keyid] = &max_confirm;
}
// 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
for (const auto& entry : mapWallet) {
// iterate over all wallet transactions...
const CWalletTx &wtx = entry.second;
if (wtx.m_confirm.status == CWalletTx::CONFIRMED) {
// ... which are already in a block
for (const CTxOut &txout : wtx.tx->vout) {
// iterate over all their outputs
for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *spk_man)) {
// ... and all their affected keys
auto rit = mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() && wtx.m_confirm.block_height < rit->second->block_height) {
rit->second = &wtx.m_confirm;
}
}
}
}
}
// Extract block timestamps for those keys
for (const auto& entry : mapKeyFirstBlock) {
int64_t block_time;
CHECK_NONFATAL(chain().findBlock(entry.second->hashBlock, FoundBlock().time(block_time)));
mapKeyBirth[entry.first] = block_time - 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.isUnconfirmed() && !wtx.isAbandoned()) {
int64_t blocktime;
if (chain().findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(blocktime))) {
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;
if (pwtx == &wtx) {
continue;
}
int64_t nSmartTime;
nSmartTime = pwtx->nTimeSmart;
if (!nSmartTime) {
nSmartTime = pwtx->nTimeReceived;
}
if (nSmartTime <= latestTolerated) {
latestEntry = nSmartTime;
if (nSmartTime > latestNow) {
latestNow = nSmartTime;
}
break;
}
}
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
} else {
WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.m_confirm.hashBlock.ToString());
}
}
return nTimeSmart;
}
bool CWallet::AddDestData(WalletBatch& batch, const CTxDestination &dest, const std::string &key, const std::string &value)
{
if (boost::get<CNoDestination>(&dest))
return false;
m_address_book[dest].destdata.insert(std::make_pair(key, value));
return batch.WriteDestData(EncodeDestination(dest), key, value);
}
bool CWallet::EraseDestData(WalletBatch& batch, const CTxDestination &dest, const std::string &key)
{
if (!m_address_book[dest].destdata.erase(key))
return false;
return batch.EraseDestData(EncodeDestination(dest), key);
}
void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
{
m_address_book[dest].destdata.insert(std::make_pair(key, value));
}
bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const
{
std::map<CTxDestination, CAddressBookData>::const_iterator i = m_address_book.find(dest);
if(i != m_address_book.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
{
std::vector<std::string> values;
for (const auto& address : m_address_book) {
for (const auto& data : address.second.destdata) {
if (!data.first.compare(0, prefix.size(), prefix)) {
values.emplace_back(data.second);
}
}
}
return values;
}
std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error_string)
{
// Do some checking on wallet path. It should be either a:
//
// 1. Path where a directory can be created.
// 2. Path to an existing directory.
// 3. Path to a symlink to a directory.
// 4. For backwards compatibility, the name of a data file in -walletdir.
const fs::path& wallet_path = fs::absolute(name, GetWalletDir());
fs::file_type path_type = fs::symlink_status(wallet_path).type();
if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
(path_type == fs::symlink_file && fs::is_directory(wallet_path)) ||
(path_type == fs::regular_file && fs::path(name).filename() == name))) {
error_string = Untranslated(strprintf(
"Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and "
"database/log.?????????? files can be stored, a location where such a directory could be created, "
"or (for backwards compatibility) the name of an existing data file in -walletdir (%s)",
name, GetWalletDir()));
status = DatabaseStatus::FAILED_BAD_PATH;
return nullptr;
}
return MakeDatabase(wallet_path, options, status, error_string);
}
std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
const std::string& walletFile = database->Filename();
chain.initMessage(_("Loading wallet...").translated);
int64_t nStart = GetTimeMillis();
bool fFirstRun = true;
2018-04-28 22:36:43 +01:00
// TODO: Can't use std::make_shared because we need a custom deleter but
// should be possible to use std::allocate_shared.
std::shared_ptr<CWallet> walletInstance(new CWallet(&chain, name, std::move(database)), ReleaseWallet);
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
if (nLoadWalletRet != DBErrors::LOAD_OK) {
scripted-diff: Convert 11 enums into scoped enums (C++11) -BEGIN VERIFY SCRIPT- sed -i 's/enum DBErrors/enum class DBErrors/g' src/wallet/walletdb.h git grep -l DB_ | xargs sed -i 's/DB_\(LOAD_OK\|CORRUPT\|NONCRITICAL_ERROR\|TOO_NEW\|LOAD_FAIL\|NEED_REWRITE\)/DBErrors::\1/g' sed -i 's/^ DBErrors::/ /g' src/wallet/walletdb.h sed -i 's/enum VerifyResult/enum class VerifyResult/g' src/wallet/db.h sed -i 's/\(VERIFY_OK\|RECOVER_OK\|RECOVER_FAIL\)/VerifyResult::\1/g' src/wallet/db.cpp sed -i 's/enum ThresholdState/enum class ThresholdState/g' src/versionbits.h git grep -l THRESHOLD_ | xargs sed -i 's/THRESHOLD_\(DEFINED\|STARTED\|LOCKED_IN\|ACTIVE\|FAILED\)/ThresholdState::\1/g' sed -i 's/^ ThresholdState::/ /g' src/versionbits.h sed -i 's/enum SigVersion/enum class SigVersion/g' src/script/interpreter.h git grep -l SIGVERSION_ | xargs sed -i 's/SIGVERSION_\(BASE\|WITNESS_V0\)/SigVersion::\1/g' sed -i 's/^ SigVersion::/ /g' src/script/interpreter.h sed -i 's/enum RetFormat {/enum class RetFormat {/g' src/rest.cpp sed -i 's/RF_\(UNDEF\|BINARY\|HEX\|JSON\)/RetFormat::\1/g' src/rest.cpp sed -i 's/^ RetFormat::/ /g' src/rest.cpp sed -i 's/enum HelpMessageMode {/enum class HelpMessageMode {/g' src/init.h git grep -l HMM_ | xargs sed -i 's/HMM_BITCOIN/HelpMessageMode::BITCOIN/g' sed -i 's/^ HelpMessageMode::/ /g' src/init.h sed -i 's/enum FeeEstimateHorizon/enum class FeeEstimateHorizon/g' src/policy/fees.h sed -i 's/enum RBFTransactionState/enum class RBFTransactionState/g' src/policy/rbf.h git grep -l RBF_ | xargs sed -i 's/RBF_TRANSACTIONSTATE_\(UNKNOWN\|REPLACEABLE_BIP125\|FINAL\)/RBFTransactionState::\1/g' sed -i 's/^ RBFTransactionState::/ /g' src/policy/rbf.h sed -i 's/enum BlockSource {/enum class BlockSource {/g' src/qt/clientmodel.h git grep -l BLOCK_SOURCE_ | xargs sed -i 's/BLOCK_SOURCE_\(NONE\|REINDEX\|DISK\|NETWORK\)/BlockSource::\1/g' sed -i 's/^ BlockSource::/ /g' src/qt/clientmodel.h sed -i 's/enum FlushStateMode {/enum class FlushStateMode {/g' src/validation.cpp sed -i 's/FLUSH_STATE_\(NONE\|IF_NEEDED\|PERIODIC\|ALWAYS\)/FlushStateMode::\1/g' src/validation.cpp sed -i 's/^ FlushStateMode::/ /g' src/validation.cpp sed -i 's/enum WitnessMode {/enum class WitnessMode {/g' src/test/script_tests.cpp sed -i 's/WITNESS_\(NONE\|PKH\|SH\)/WitnessMode::\1/g' src/test/script_tests.cpp sed -i 's/^ WitnessMode::/ /g' src/test/script_tests.cpp -END VERIFY SCRIPT-
2018-03-09 15:03:40 +01:00
if (nLoadWalletRet == DBErrors::CORRUPT) {
error = strprintf(_("Error loading %s: Wallet corrupted"), walletFile);
return nullptr;
}
scripted-diff: Convert 11 enums into scoped enums (C++11) -BEGIN VERIFY SCRIPT- sed -i 's/enum DBErrors/enum class DBErrors/g' src/wallet/walletdb.h git grep -l DB_ | xargs sed -i 's/DB_\(LOAD_OK\|CORRUPT\|NONCRITICAL_ERROR\|TOO_NEW\|LOAD_FAIL\|NEED_REWRITE\)/DBErrors::\1/g' sed -i 's/^ DBErrors::/ /g' src/wallet/walletdb.h sed -i 's/enum VerifyResult/enum class VerifyResult/g' src/wallet/db.h sed -i 's/\(VERIFY_OK\|RECOVER_OK\|RECOVER_FAIL\)/VerifyResult::\1/g' src/wallet/db.cpp sed -i 's/enum ThresholdState/enum class ThresholdState/g' src/versionbits.h git grep -l THRESHOLD_ | xargs sed -i 's/THRESHOLD_\(DEFINED\|STARTED\|LOCKED_IN\|ACTIVE\|FAILED\)/ThresholdState::\1/g' sed -i 's/^ ThresholdState::/ /g' src/versionbits.h sed -i 's/enum SigVersion/enum class SigVersion/g' src/script/interpreter.h git grep -l SIGVERSION_ | xargs sed -i 's/SIGVERSION_\(BASE\|WITNESS_V0\)/SigVersion::\1/g' sed -i 's/^ SigVersion::/ /g' src/script/interpreter.h sed -i 's/enum RetFormat {/enum class RetFormat {/g' src/rest.cpp sed -i 's/RF_\(UNDEF\|BINARY\|HEX\|JSON\)/RetFormat::\1/g' src/rest.cpp sed -i 's/^ RetFormat::/ /g' src/rest.cpp sed -i 's/enum HelpMessageMode {/enum class HelpMessageMode {/g' src/init.h git grep -l HMM_ | xargs sed -i 's/HMM_BITCOIN/HelpMessageMode::BITCOIN/g' sed -i 's/^ HelpMessageMode::/ /g' src/init.h sed -i 's/enum FeeEstimateHorizon/enum class FeeEstimateHorizon/g' src/policy/fees.h sed -i 's/enum RBFTransactionState/enum class RBFTransactionState/g' src/policy/rbf.h git grep -l RBF_ | xargs sed -i 's/RBF_TRANSACTIONSTATE_\(UNKNOWN\|REPLACEABLE_BIP125\|FINAL\)/RBFTransactionState::\1/g' sed -i 's/^ RBFTransactionState::/ /g' src/policy/rbf.h sed -i 's/enum BlockSource {/enum class BlockSource {/g' src/qt/clientmodel.h git grep -l BLOCK_SOURCE_ | xargs sed -i 's/BLOCK_SOURCE_\(NONE\|REINDEX\|DISK\|NETWORK\)/BlockSource::\1/g' sed -i 's/^ BlockSource::/ /g' src/qt/clientmodel.h sed -i 's/enum FlushStateMode {/enum class FlushStateMode {/g' src/validation.cpp sed -i 's/FLUSH_STATE_\(NONE\|IF_NEEDED\|PERIODIC\|ALWAYS\)/FlushStateMode::\1/g' src/validation.cpp sed -i 's/^ FlushStateMode::/ /g' src/validation.cpp sed -i 's/enum WitnessMode {/enum class WitnessMode {/g' src/test/script_tests.cpp sed -i 's/WITNESS_\(NONE\|PKH\|SH\)/WitnessMode::\1/g' src/test/script_tests.cpp sed -i 's/^ WitnessMode::/ /g' src/test/script_tests.cpp -END VERIFY SCRIPT-
2018-03-09 15:03:40 +01:00
else if (nLoadWalletRet == DBErrors::NONCRITICAL_ERROR)
{
warnings.push_back(strprintf(_("Error reading %s! All keys read correctly, but transaction data"
" or address book entries might be missing or incorrect."),
walletFile));
}
scripted-diff: Convert 11 enums into scoped enums (C++11) -BEGIN VERIFY SCRIPT- sed -i 's/enum DBErrors/enum class DBErrors/g' src/wallet/walletdb.h git grep -l DB_ | xargs sed -i 's/DB_\(LOAD_OK\|CORRUPT\|NONCRITICAL_ERROR\|TOO_NEW\|LOAD_FAIL\|NEED_REWRITE\)/DBErrors::\1/g' sed -i 's/^ DBErrors::/ /g' src/wallet/walletdb.h sed -i 's/enum VerifyResult/enum class VerifyResult/g' src/wallet/db.h sed -i 's/\(VERIFY_OK\|RECOVER_OK\|RECOVER_FAIL\)/VerifyResult::\1/g' src/wallet/db.cpp sed -i 's/enum ThresholdState/enum class ThresholdState/g' src/versionbits.h git grep -l THRESHOLD_ | xargs sed -i 's/THRESHOLD_\(DEFINED\|STARTED\|LOCKED_IN\|ACTIVE\|FAILED\)/ThresholdState::\1/g' sed -i 's/^ ThresholdState::/ /g' src/versionbits.h sed -i 's/enum SigVersion/enum class SigVersion/g' src/script/interpreter.h git grep -l SIGVERSION_ | xargs sed -i 's/SIGVERSION_\(BASE\|WITNESS_V0\)/SigVersion::\1/g' sed -i 's/^ SigVersion::/ /g' src/script/interpreter.h sed -i 's/enum RetFormat {/enum class RetFormat {/g' src/rest.cpp sed -i 's/RF_\(UNDEF\|BINARY\|HEX\|JSON\)/RetFormat::\1/g' src/rest.cpp sed -i 's/^ RetFormat::/ /g' src/rest.cpp sed -i 's/enum HelpMessageMode {/enum class HelpMessageMode {/g' src/init.h git grep -l HMM_ | xargs sed -i 's/HMM_BITCOIN/HelpMessageMode::BITCOIN/g' sed -i 's/^ HelpMessageMode::/ /g' src/init.h sed -i 's/enum FeeEstimateHorizon/enum class FeeEstimateHorizon/g' src/policy/fees.h sed -i 's/enum RBFTransactionState/enum class RBFTransactionState/g' src/policy/rbf.h git grep -l RBF_ | xargs sed -i 's/RBF_TRANSACTIONSTATE_\(UNKNOWN\|REPLACEABLE_BIP125\|FINAL\)/RBFTransactionState::\1/g' sed -i 's/^ RBFTransactionState::/ /g' src/policy/rbf.h sed -i 's/enum BlockSource {/enum class BlockSource {/g' src/qt/clientmodel.h git grep -l BLOCK_SOURCE_ | xargs sed -i 's/BLOCK_SOURCE_\(NONE\|REINDEX\|DISK\|NETWORK\)/BlockSource::\1/g' sed -i 's/^ BlockSource::/ /g' src/qt/clientmodel.h sed -i 's/enum FlushStateMode {/enum class FlushStateMode {/g' src/validation.cpp sed -i 's/FLUSH_STATE_\(NONE\|IF_NEEDED\|PERIODIC\|ALWAYS\)/FlushStateMode::\1/g' src/validation.cpp sed -i 's/^ FlushStateMode::/ /g' src/validation.cpp sed -i 's/enum WitnessMode {/enum class WitnessMode {/g' src/test/script_tests.cpp sed -i 's/WITNESS_\(NONE\|PKH\|SH\)/WitnessMode::\1/g' src/test/script_tests.cpp sed -i 's/^ WitnessMode::/ /g' src/test/script_tests.cpp -END VERIFY SCRIPT-
2018-03-09 15:03:40 +01:00
else if (nLoadWalletRet == DBErrors::TOO_NEW) {
error = strprintf(_("Error loading %s: Wallet requires newer version of %s"), walletFile, PACKAGE_NAME);
return nullptr;
}
scripted-diff: Convert 11 enums into scoped enums (C++11) -BEGIN VERIFY SCRIPT- sed -i 's/enum DBErrors/enum class DBErrors/g' src/wallet/walletdb.h git grep -l DB_ | xargs sed -i 's/DB_\(LOAD_OK\|CORRUPT\|NONCRITICAL_ERROR\|TOO_NEW\|LOAD_FAIL\|NEED_REWRITE\)/DBErrors::\1/g' sed -i 's/^ DBErrors::/ /g' src/wallet/walletdb.h sed -i 's/enum VerifyResult/enum class VerifyResult/g' src/wallet/db.h sed -i 's/\(VERIFY_OK\|RECOVER_OK\|RECOVER_FAIL\)/VerifyResult::\1/g' src/wallet/db.cpp sed -i 's/enum ThresholdState/enum class ThresholdState/g' src/versionbits.h git grep -l THRESHOLD_ | xargs sed -i 's/THRESHOLD_\(DEFINED\|STARTED\|LOCKED_IN\|ACTIVE\|FAILED\)/ThresholdState::\1/g' sed -i 's/^ ThresholdState::/ /g' src/versionbits.h sed -i 's/enum SigVersion/enum class SigVersion/g' src/script/interpreter.h git grep -l SIGVERSION_ | xargs sed -i 's/SIGVERSION_\(BASE\|WITNESS_V0\)/SigVersion::\1/g' sed -i 's/^ SigVersion::/ /g' src/script/interpreter.h sed -i 's/enum RetFormat {/enum class RetFormat {/g' src/rest.cpp sed -i 's/RF_\(UNDEF\|BINARY\|HEX\|JSON\)/RetFormat::\1/g' src/rest.cpp sed -i 's/^ RetFormat::/ /g' src/rest.cpp sed -i 's/enum HelpMessageMode {/enum class HelpMessageMode {/g' src/init.h git grep -l HMM_ | xargs sed -i 's/HMM_BITCOIN/HelpMessageMode::BITCOIN/g' sed -i 's/^ HelpMessageMode::/ /g' src/init.h sed -i 's/enum FeeEstimateHorizon/enum class FeeEstimateHorizon/g' src/policy/fees.h sed -i 's/enum RBFTransactionState/enum class RBFTransactionState/g' src/policy/rbf.h git grep -l RBF_ | xargs sed -i 's/RBF_TRANSACTIONSTATE_\(UNKNOWN\|REPLACEABLE_BIP125\|FINAL\)/RBFTransactionState::\1/g' sed -i 's/^ RBFTransactionState::/ /g' src/policy/rbf.h sed -i 's/enum BlockSource {/enum class BlockSource {/g' src/qt/clientmodel.h git grep -l BLOCK_SOURCE_ | xargs sed -i 's/BLOCK_SOURCE_\(NONE\|REINDEX\|DISK\|NETWORK\)/BlockSource::\1/g' sed -i 's/^ BlockSource::/ /g' src/qt/clientmodel.h sed -i 's/enum FlushStateMode {/enum class FlushStateMode {/g' src/validation.cpp sed -i 's/FLUSH_STATE_\(NONE\|IF_NEEDED\|PERIODIC\|ALWAYS\)/FlushStateMode::\1/g' src/validation.cpp sed -i 's/^ FlushStateMode::/ /g' src/validation.cpp sed -i 's/enum WitnessMode {/enum class WitnessMode {/g' src/test/script_tests.cpp sed -i 's/WITNESS_\(NONE\|PKH\|SH\)/WitnessMode::\1/g' src/test/script_tests.cpp sed -i 's/^ WitnessMode::/ /g' src/test/script_tests.cpp -END VERIFY SCRIPT-
2018-03-09 15:03:40 +01:00
else if (nLoadWalletRet == DBErrors::NEED_REWRITE)
{
error = strprintf(_("Wallet needed to be rewritten: restart %s to complete"), PACKAGE_NAME);
return nullptr;
}
else {
error = strprintf(_("Error loading %s"), walletFile);
return nullptr;
}
}
if (fFirstRun)
{
// ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key
walletInstance->SetMinVersion(FEATURE_LATEST);
walletInstance->AddWalletFlags(wallet_creation_flags);
// Only create LegacyScriptPubKeyMan when not descriptor wallet
if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
walletInstance->SetupLegacyScriptPubKeyMan();
}
2018-09-13 13:53:19 +09:00
if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
LOCK(walletInstance->cs_wallet);
if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
walletInstance->SetupDescriptorScriptPubKeyMans();
// SetupDescriptorScriptPubKeyMans already calls SetupGeneration for us so we don't need to call SetupGeneration separately
} else {
// Legacy wallets need SetupGeneration here.
for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) {
if (!spk_man->SetupGeneration()) {
error = _("Unable to generate initial keys");
return nullptr;
}
}
}
}
walletInstance->chainStateFlushed(chain.getTipLocator());
} else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) {
// Make it impossible to disable private keys after creation
error = strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile);
return NULL;
} else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) {
if (spk_man->HavePrivateKeys()) {
warnings.push_back(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys"), walletFile));
break;
}
}
}
if (!gArgs.GetArg("-addresstype", "").empty()) {
if (!ParseOutputType(gArgs.GetArg("-addresstype", ""), walletInstance->m_default_address_type)) {
error = strprintf(_("Unknown address type '%s'"), gArgs.GetArg("-addresstype", ""));
return nullptr;
}
}
if (!gArgs.GetArg("-changetype", "").empty()) {
OutputType out_type;
if (!ParseOutputType(gArgs.GetArg("-changetype", ""), out_type)) {
error = strprintf(_("Unknown change type '%s'"), gArgs.GetArg("-changetype", ""));
return nullptr;
}
walletInstance->m_default_change_type = out_type;
}
if (gArgs.IsArgSet("-mintxfee")) {
CAmount n = 0;
if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || 0 == n) {
error = AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""));
return nullptr;
}
if (n > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-mintxfee") + Untranslated(" ") +
_("This is the minimum transaction fee you pay on every transaction."));
}
walletInstance->m_min_fee = CFeeRate(n);
}
if (gArgs.IsArgSet("-maxapsfee")) {
const std::string max_aps_fee{gArgs.GetArg("-maxapsfee", "")};
CAmount n = 0;
if (max_aps_fee == "-1") {
n = -1;
} else if (!ParseMoney(max_aps_fee, n)) {
error = AmountErrMsg("maxapsfee", max_aps_fee);
return nullptr;
}
if (n > HIGH_APS_FEE) {
warnings.push_back(AmountHighWarn("-maxapsfee") + Untranslated(" ") +
_("This is the maximum transaction fee you pay (in addition to the normal fee) to prioritize partial spend avoidance over regular coin selection."));
}
walletInstance->m_max_aps_fee = n;
}
if (gArgs.IsArgSet("-fallbackfee")) {
CAmount nFeePerK = 0;
if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) {
error = strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), gArgs.GetArg("-fallbackfee", ""));
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-fallbackfee") + Untranslated(" ") +
_("This is the transaction fee you may pay when fee estimates are not available."));
}
walletInstance->m_fallback_fee = CFeeRate(nFeePerK);
}
// Disable fallback fee in case value was set to 0, enable if non-null value
walletInstance->m_allow_fallback_fee = walletInstance->m_fallback_fee.GetFeePerK() != 0;
if (gArgs.IsArgSet("-discardfee")) {
CAmount nFeePerK = 0;
if (!ParseMoney(gArgs.GetArg("-discardfee", ""), nFeePerK)) {
error = strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), gArgs.GetArg("-discardfee", ""));
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-discardfee") + Untranslated(" ") +
_("This is the transaction fee you may discard if change is smaller than dust at this level"));
}
walletInstance->m_discard_rate = CFeeRate(nFeePerK);
}
if (gArgs.IsArgSet("-paytxfee")) {
CAmount nFeePerK = 0;
if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) {
error = AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""));
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-paytxfee") + Untranslated(" ") +
_("This is the transaction fee you will pay if you send a transaction."));
}
walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000);
if (walletInstance->m_pay_tx_fee < chain.relayMinFee()) {
error = strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"),
gArgs.GetArg("-paytxfee", ""), chain.relayMinFee().ToString());
return nullptr;
}
}
if (gArgs.IsArgSet("-maxtxfee")) {
CAmount nMaxFee = 0;
if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) {
error = AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""));
return nullptr;
}
if (nMaxFee > HIGH_MAX_TX_FEE) {
warnings.push_back(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction."));
}
if (CFeeRate(nMaxFee, 1000) < chain.relayMinFee()) {
error = strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"),
gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString());
return nullptr;
}
walletInstance->m_default_max_tx_fee = nMaxFee;
}
if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-minrelaytxfee") + Untranslated(" ") +
_("The wallet will avoid paying less than the minimum relay fee."));
}
walletInstance->m_confirm_target = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);
walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
walletInstance->m_signal_rbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);
walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n", GetTimeMillis() - nStart);
// Try to top up keypool. No-op if the wallet is locked.
walletInstance->TopUpKeyPool();
LOCK(walletInstance->cs_wallet);
// Register wallet with validationinterface. It's done before rescan to avoid
// missing block connections between end of rescan and validation subscribing.
// Because of wallet lock being hold, block connection notifications are going to
// be pending on the validation-side until lock release. It's likely to have
// block processing duplicata (if rescan block range overlaps with notification one)
// but we guarantee at least than wallet state is correct after notifications delivery.
// This is temporary until rescan and notifications delivery are unified under same
// interface.
walletInstance->m_chain_notifications_handler = walletInstance->chain().handleNotifications(walletInstance);
int rescan_height = 0;
if (!gArgs.GetBoolArg("-rescan", false))
{
WalletBatch batch(walletInstance->GetDatabase());
CBlockLocator locator;
if (batch.ReadBestBlock(locator)) {
if (const Optional<int> fork_height = chain.findLocatorFork(locator)) {
rescan_height = *fork_height;
}
}
}
const Optional<int> tip_height = chain.getHeight();
if (tip_height) {
walletInstance->m_last_block_processed = chain.getBlockHash(*tip_height);
walletInstance->m_last_block_processed_height = *tip_height;
} else {
walletInstance->m_last_block_processed.SetNull();
walletInstance->m_last_block_processed_height = -1;
}
if (tip_height && *tip_height != rescan_height)
{
// 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 they ran -disablewallet for a longer time, then decided to re-enable
if (chain.havePruned()) {
// Exit early and print an error.
// If a block is pruned after this check, we will load the wallet,
// but fail the rescan with a generic error.
int block_height = *tip_height;
while (block_height > 0 && chain.haveBlockOnDisk(block_height - 1) && rescan_height != block_height) {
--block_height;
}
if (rescan_height != block_height) {
error = _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)");
return nullptr;
}
}
chain.initMessage(_("Rescanning...").translated);
walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", *tip_height - rescan_height, rescan_height);
// No need to read and scan block if block was created before
// our wallet birthday (as adjusted for block time variability)
Optional<int64_t> time_first_key;
for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) {
int64_t time = spk_man->GetTimeFirstKey();
if (!time_first_key || time < *time_first_key) time_first_key = time;
}
if (time_first_key) {
chain.findFirstBlockWithTimeAndHeight(*time_first_key - TIMESTAMP_WINDOW, rescan_height, FoundBlock().height(rescan_height));
}
{
WalletRescanReserver reserver(*walletInstance);
if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, {} /* max height */, reserver, true /* update */).status)) {
error = _("Failed to rescan the wallet during initialization");
return nullptr;
}
}
walletInstance->chainStateFlushed(chain.getTipLocator());
walletInstance->GetDatabase().IncrementUpdateCounter();
}
{
LOCK(cs_wallets);
for (auto& load_wallet : g_load_wallet_fns) {
load_wallet(interfaces::MakeWallet(walletInstance));
}
}
walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
{
walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size());
walletInstance->WalletLogPrintf("m_address_book.size() = %u\n", walletInstance->m_address_book.size());
}
return walletInstance;
}
const CAddressBookData* CWallet::FindAddressBookEntry(const CTxDestination& dest, bool allow_change) const
{
const auto& address_book_it = m_address_book.find(dest);
if (address_book_it == m_address_book.end()) return nullptr;
if ((!allow_change) && address_book_it->second.IsChange()) {
return nullptr;
}
return &address_book_it->second;
}
bool CWallet::UpgradeWallet(int version, bilingual_str& error)
{
int prev_version = GetVersion();
if (version == 0) {
WalletLogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST);
version = FEATURE_LATEST;
} else {
WalletLogPrintf("Allowing wallet upgrade up to %i\n", version);
}
if (version < prev_version) {
error = strprintf(_("Cannot downgrade wallet from version %i to version %i. Wallet version unchanged."), prev_version, version);
return false;
}
LOCK(cs_wallet);
// Do not upgrade versions to any version between HD_SPLIT and FEATURE_PRE_SPLIT_KEYPOOL unless already supporting HD_SPLIT
if (!CanSupportFeature(FEATURE_HD_SPLIT) && version >= FEATURE_HD_SPLIT && version < FEATURE_PRE_SPLIT_KEYPOOL) {
error = strprintf(_("Cannot upgrade a non HD split wallet from version %i to version %i without upgrading to support pre-split keypool. Please use version %i or no version specified."), prev_version, version, FEATURE_PRE_SPLIT_KEYPOOL);
return false;
}
// Permanently upgrade to the version
SetMinVersion(GetClosestWalletFeature(version));
for (auto spk_man : GetActiveScriptPubKeyMans()) {
if (!spk_man->Upgrade(prev_version, version, error)) {
return false;
}
}
return true;
}
void CWallet::postInitProcess()
{
LOCK(cs_wallet);
// Add wallet transactions that aren't already in a block to mempool
// Do this here as mempool requires genesis block to be loaded
ReacceptWalletTransactions();
// Update wallet transactions with current mempool transactions.
chain().requestMempoolTransactions(*this);
}
2020-03-02 17:23:34 +09:00
bool CWallet::BackupWallet(const std::string& strDest) const
{
return GetDatabase().Backup(strDest);
}
CKeyPool::CKeyPool()
{
nTime = GetTime();
fInternal = false;
m_pre_split = false;
}
CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
{
nTime = GetTime();
vchPubKey = vchPubKeyIn;
fInternal = internalIn;
m_pre_split = false;
}
int CWalletTx::GetDepthInMainChain() const
{
assert(pwallet != nullptr);
AssertLockHeld(pwallet->cs_wallet);
if (isUnconfirmed() || isAbandoned()) return 0;
return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) * (isConflicted() ? -1 : 1);
}
int CWalletTx::GetBlocksToMaturity() const
{
if (!IsCoinBase())
return 0;
int chain_depth = GetDepthInMainChain();
assert(chain_depth >= 0); // coinbase tx should not be conflicted
return std::max(0, (COINBASE_MATURITY+1) - chain_depth);
}
bool CWalletTx::IsImmatureCoinBase() const
{
// note GetBlocksToMaturity is 0 for non-coinbase tx
return GetBlocksToMaturity() > 0;
}
std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin, const size_t max_ancestors) const {
2018-07-19 11:43:03 +09:00
std::vector<OutputGroup> groups;
2019-03-20 11:01:36 +00:00
std::map<std::pair<CAsset, CTxDestination>, OutputGroup> gmap;
std::set<std::pair<CAsset, CTxDestination>> full_groups;
2018-07-19 11:43:03 +09:00
for (const auto& output : outputs) {
if (output.fSpendable) {
std::pair<CAsset, CTxDestination> dst;
2018-07-19 11:43:03 +09:00
CInputCoin input_coin = output.GetInputCoin();
2019-03-20 11:01:36 +00:00
dst.first = input_coin.asset;
2018-07-19 11:43:03 +09:00
size_t ancestors, descendants;
chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
2019-03-20 11:01:36 +00:00
if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, dst.second)) {
auto it = gmap.find(dst);
if (it != gmap.end()) {
// Limit output groups to no more than OUTPUT_GROUP_MAX_ENTRIES
// number of entries, to protect against inadvertently creating
// a too-large transaction when using -avoidpartialspends to
// prevent breaking consensus or surprising users with a very
// high amount of fees.
if (it->second.m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
groups.push_back(it->second);
it->second = OutputGroup{};
full_groups.insert(dst);
}
it->second.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
} else {
gmap[dst].Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
2018-07-19 11:43:03 +09:00
}
} else {
groups.emplace_back(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
}
}
}
if (!single_coin) {
for (auto& it : gmap) {
auto& group = it.second;
if (full_groups.count(it.first) > 0) {
// Make this unattractive as we want coin selection to avoid it if possible
group.m_ancestors = max_ancestors - 1;
}
groups.push_back(group);
}
2018-07-19 11:43:03 +09:00
}
return groups;
}
bool CWallet::IsCrypted() const
{
return HasEncryptionKeys();
}
bool CWallet::IsLocked() const
{
if (!IsCrypted()) {
return false;
}
LOCK(cs_wallet);
return vMasterKey.empty();
}
bool CWallet::Lock()
{
if (!IsCrypted())
return false;
{
LOCK(cs_wallet);
vMasterKey.clear();
}
NotifyStatusChanged(this);
return true;
}
bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys)
{
{
LOCK(cs_wallet);
for (const auto& spk_man_pair : m_spk_managers) {
if (!spk_man_pair.second->CheckDecryptionKey(vMasterKeyIn, accept_no_keys)) {
return false;
}
}
vMasterKey = vMasterKeyIn;
}
NotifyStatusChanged(this);
return true;
}
std::set<ScriptPubKeyMan*> CWallet::GetActiveScriptPubKeyMans() const
{
std::set<ScriptPubKeyMan*> spk_mans;
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
auto spk_man = GetScriptPubKeyMan(t, internal);
if (spk_man) {
spk_mans.insert(spk_man);
}
}
}
return spk_mans;
}
std::set<ScriptPubKeyMan*> CWallet::GetAllScriptPubKeyMans() const
{
std::set<ScriptPubKeyMan*> spk_mans;
for (const auto& spk_man_pair : m_spk_managers) {
spk_mans.insert(spk_man_pair.second.get());
}
return spk_mans;
}
ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const OutputType& type, bool internal) const
{
const std::map<OutputType, ScriptPubKeyMan*>& spk_managers = internal ? m_internal_spk_managers : m_external_spk_managers;
std::map<OutputType, ScriptPubKeyMan*>::const_iterator it = spk_managers.find(type);
if (it == spk_managers.end()) {
WalletLogPrintf("%s scriptPubKey Manager for output type %d does not exist\n", internal ? "Internal" : "External", static_cast<int>(type));
return nullptr;
}
return it->second;
}
std::set<ScriptPubKeyMan*> CWallet::GetScriptPubKeyMans(const CScript& script, SignatureData& sigdata) const
{
std::set<ScriptPubKeyMan*> spk_mans;
for (const auto& spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script, sigdata)) {
spk_mans.insert(spk_man_pair.second.get());
}
}
return spk_mans;
}
ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const CScript& script) const
{
SignatureData sigdata;
for (const auto& spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script, sigdata)) {
return spk_man_pair.second.get();
}
}
return nullptr;
}
const CKeyingMaterial& CWallet::GetEncryptionKey() const
{
return vMasterKey;
}
bool CWallet::HasEncryptionKeys() const
{
return !mapMasterKeys.empty();
}
ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const uint256& id) const
{
if (m_spk_managers.count(id) > 0) {
return m_spk_managers.at(id).get();
}
return nullptr;
}
std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript& script) const
{
SignatureData sigdata;
return GetSolvingProvider(script, sigdata);
}
std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript& script, SignatureData& sigdata) const
{
for (const auto& spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script, sigdata)) {
return spk_man_pair.second->GetSolvingProvider(script);
}
}
return nullptr;
}
LegacyScriptPubKeyMan* CWallet::GetLegacyScriptPubKeyMan() const
{
if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
return nullptr;
}
// Legacy wallets only have one ScriptPubKeyMan which is a LegacyScriptPubKeyMan.
// Everything in m_internal_spk_managers and m_external_spk_managers point to the same legacyScriptPubKeyMan.
auto it = m_internal_spk_managers.find(OutputType::LEGACY);
if (it == m_internal_spk_managers.end()) return nullptr;
return dynamic_cast<LegacyScriptPubKeyMan*>(it->second);
}
LegacyScriptPubKeyMan* CWallet::GetOrCreateLegacyScriptPubKeyMan()
{
SetupLegacyScriptPubKeyMan();
return GetLegacyScriptPubKeyMan();
}
void CWallet::SetupLegacyScriptPubKeyMan()
{
if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() || !m_spk_managers.empty() || IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
return;
}
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new LegacyScriptPubKeyMan(*this));
for (const auto& type : OUTPUT_TYPES) {
m_internal_spk_managers[type] = spk_manager.get();
m_external_spk_managers[type] = spk_manager.get();
}
m_spk_managers[spk_manager->GetID()] = std::move(spk_manager);
}
2019-03-20 11:01:36 +00:00
//
// ELEMENTS WALLET ADDITIONS
//
bool CWallet::SetOnlinePubKey(const CPubKey& online_key_in)
{
LOCK(cs_wallet);
if (!WalletBatch(GetDatabase()).WriteOnlineKey(online_key_in)) {
return false;
}
online_key = online_key_in;
return true;
}
bool CWallet::SetOfflineXPubKey(const CExtPubKey& offline_xpub_in)
{
LOCK(cs_wallet);
if (!WalletBatch(GetDatabase()).WriteOfflineXPubKey(offline_xpub_in)) {
return false;
}
offline_xpub = offline_xpub_in;
return true;
}
bool CWallet::SetOfflineDescriptor(const std::string& offline_desc_in)
{
LOCK(cs_wallet);
if (!WalletBatch(GetDatabase()).WriteOfflineDescriptor(offline_desc_in)) {
return false;
}
offline_desc = offline_desc_in;
return true;
}
bool CWallet::SetOfflineCounter(int counter) {
LOCK(cs_wallet);
if (!WalletBatch(GetDatabase()).WriteOfflineCounter(counter)) {
return false;
}
offline_counter = counter;
return true;
2019-03-20 11:01:36 +00:00
}
unsigned int CWalletTx::GetPseudoInputOffset(const unsigned int input_index, const bool reissuance_token) const
{
// There is no mapValue space for null issuances
assert(reissuance_token ? !tx->vin[input_index].assetIssuance.nInflationKeys.IsNull() : !tx->vin[input_index].assetIssuance.nAmount.IsNull());
unsigned int mapvalue_loc = 0;
for (unsigned int i = 0; i < tx->vin.size()*2; i++) {
if (input_index == i/2 && (reissuance_token ? 1 : 0) == i % 2) {
break;
}
if (!tx->vin[i/2].assetIssuance.IsNull()) {
if ((i % 2 == 0 && !tx->vin[i/2].assetIssuance.nAmount.IsNull()) ||
(i % 2 == 1 && !tx->vin[i/2].assetIssuance.nInflationKeys.IsNull())) {
mapvalue_loc++;
}
}
}
return mapvalue_loc;
}
void CWalletTx::SetBlindingData(const unsigned int map_index, const CPubKey& blinding_pubkey, const CAmount value, const uint256& value_factor, const CAsset& asset, const uint256& asset_factor)
{
if (mapValue["blindingdata"].size() < (map_index + 1) * 138) {
mapValue["blindingdata"].resize((tx->vout.size() + GetNumIssuances(*tx)) * 138);
}
unsigned char* it = (unsigned char*)(&mapValue["blindingdata"][0]) + 138 * map_index;
*it = 1;
memcpy(&*(it + 1), &value, 8);
memcpy(&*(it + 9), value_factor.begin(), 32);
memcpy(&*(it + 41), asset_factor.begin(), 32);
memcpy(&*(it + 73), asset.begin(), 32);
if (blinding_pubkey.IsFullyValid()) {
memcpy(&*(it + 105), blinding_pubkey.begin(), 33);
} else {
memset(&*(it + 105), 0, 33);
}
}
void CWalletTx::GetBlindingData(const unsigned int map_index, const std::vector<unsigned char>& vchRangeproof, const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CConfidentialNonce nonce, const CScript& scriptPubKey, CPubKey* blinding_pubkey_out, CAmount* value_out, uint256* value_factor_out, CAsset* asset_out, uint256* asset_factor_out) const
{
// Blinding data is cached in a serialized record mapWallet["blindingdata"].
// It contains a concatenation byte vectors, 74 bytes per txout or pseudo-input.
// Each consists of:
// * 1 byte boolean marker (has the output been computed)?
// * 8 bytes value (-1 if unknown)
// * 32 bytes value blinding factor
// * 32 bytes asset blinding factor
// * 32 bytes asset
// * 33 bytes blinding pubkey (ECDH pubkey of the destination)
// This is really ugly, and should use CDataStream serialization instead.
if (mapValue["blindingdata"].size() < (map_index + 1) * 138) {
mapValue["blindingdata"].resize((tx->vout.size() + GetNumIssuances(*tx)) * 138);
}
unsigned char* it = (unsigned char*)(&mapValue["blindingdata"][0]) + 138 * map_index;
CAmount amount = -1;
CPubKey pubkey;
uint256 value_factor;
CAsset asset_tag;
uint256 asset_factor;
if (*it == 1) {
memcpy(&amount, &*(it + 1), 8);
memcpy(value_factor.begin(), &*(it + 9), 32);
memcpy(asset_factor.begin(), &*(it + 41), 32);
memcpy(asset_tag.begin(), &*(it + 73), 32);
pubkey.Set(it + 105, it + 138);
if (conf_value.IsExplicit()) {
assert(conf_value.GetAmount() == amount);
}
} else {
pwallet->ComputeBlindingData(conf_value, conf_asset, nonce, scriptPubKey, vchRangeproof, amount, pubkey, value_factor, asset_tag, asset_factor);
*it = 1;
memcpy(&*(it + 1), &amount, 8);
memcpy(&*(it + 9), value_factor.begin(), 32);
memcpy(&*(it + 41), asset_factor.begin(), 32);
memcpy(&*(it + 73), asset_tag.begin(), 32);
if (pubkey.IsFullyValid()) {
memcpy(&*(it + 105), pubkey.begin(), 33);
} else {
memset(&*(it + 105), 0, 33);
}
}
if (value_out) *value_out = amount;
if (blinding_pubkey_out) *blinding_pubkey_out = pubkey;
if (value_factor_out) *value_factor_out = value_factor;
if (asset_factor_out) *asset_factor_out = asset_factor;
if (asset_out) *asset_out = asset_tag;
}
void CWalletTx::GetNonIssuanceBlindingData(const unsigned int output_index, CPubKey* blinding_pubkey_out, CAmount* value_out, uint256* value_factor_out, CAsset* asset_out, uint256* asset_factor_out) const {
2019-03-20 11:01:36 +00:00
assert(output_index < tx->vout.size());
const CTxOut& out = tx->vout[output_index];
const CTxWitness& wit = tx->witness;
GetBlindingData(output_index, wit.vtxoutwit.size() <= output_index ? std::vector<unsigned char>() : wit.vtxoutwit[output_index].vchRangeproof, out.nValue, out.nAsset, out.nNonce, out.scriptPubKey,
blinding_pubkey_out, value_out, value_factor_out, asset_out, asset_factor_out);
}
void CWallet::ConnectScriptPubKeyManNotifiers()
{
for (const auto& spk_man : GetActiveScriptPubKeyMans()) {
spk_man->NotifyWatchonlyChanged.connect(NotifyWatchonlyChanged);
spk_man->NotifyCanGetAddressesChanged.connect(NotifyCanGetAddressesChanged);
}
}
void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
{
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
m_spk_managers[id] = std::move(spk_manager);
}
void CWallet::SetupDescriptorScriptPubKeyMans()
{
AssertLockHeld(cs_wallet);
// Make a seed
CKey seed_key;
seed_key.MakeNewKey(true);
CPubKey seed = seed_key.GetPubKey();
assert(seed_key.VerifyPubKey(seed));
// Get the extended key
CExtKey master_key;
master_key.SetSeed(seed_key.begin(), seed_key.size());
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal));
if (IsCrypted()) {
if (IsLocked()) {
throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
}
if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) {
throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
}
}
spk_manager->SetupDescriptorGeneration(master_key, t);
uint256 id = spk_manager->GetID();
m_spk_managers[id] = std::move(spk_manager);
AddActiveScriptPubKeyMan(id, t, internal);
}
}
}
void CWallet::AddActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal)
{
WalletBatch batch(GetDatabase());
if (!batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(type), id, internal)) {
throw std::runtime_error(std::string(__func__) + ": writing active ScriptPubKeyMan id failed");
}
LoadActiveScriptPubKeyMan(id, type, internal);
}
void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal)
{
WalletLogPrintf("Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
auto spk_man = m_spk_managers.at(id).get();
spk_man->SetInternal(internal);
spk_mans[type] = spk_man;
NotifyCanGetAddressesChanged();
}
bool CWallet::IsLegacy() const
{
if (m_internal_spk_managers.count(OutputType::LEGACY) == 0) {
return false;
}
auto spk_man = dynamic_cast<LegacyScriptPubKeyMan*>(m_internal_spk_managers.at(OutputType::LEGACY));
return spk_man != nullptr;
}
DescriptorScriptPubKeyMan* CWallet::GetDescriptorScriptPubKeyMan(const WalletDescriptor& desc) const
{
for (auto& spk_man_pair : m_spk_managers) {
// Try to downcast to DescriptorScriptPubKeyMan then check if the descriptors match
DescriptorScriptPubKeyMan* spk_manager = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man_pair.second.get());
if (spk_manager != nullptr && spk_manager->HasWalletDescriptor(desc)) {
return spk_manager;
}
}
return nullptr;
}
ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const FlatSigningProvider& signing_provider, const std::string& label, bool internal)
{
if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
WalletLogPrintf("Cannot add WalletDescriptor to a non-descriptor wallet\n");
return nullptr;
}
LOCK(cs_wallet);
auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
// If we already have this descriptor, remove it from the maps but add the existing cache to desc
auto old_spk_man = GetDescriptorScriptPubKeyMan(desc);
if (old_spk_man) {
WalletLogPrintf("Update existing descriptor: %s\n", desc.descriptor->ToString());
{
LOCK(old_spk_man->cs_desc_man);
new_spk_man->SetCache(old_spk_man->GetWalletDescriptor().cache);
}
// Remove from maps of active spkMans
auto old_spk_man_id = old_spk_man->GetID();
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
auto active_spk_man = GetScriptPubKeyMan(t, internal);
if (active_spk_man && active_spk_man->GetID() == old_spk_man_id) {
if (internal) {
m_internal_spk_managers.erase(t);
} else {
m_external_spk_managers.erase(t);
}
break;
}
}
}
m_spk_managers.erase(old_spk_man_id);
}
// Add the private keys to the descriptor
for (const auto& entry : signing_provider.keys) {
const CKey& key = entry.second;
new_spk_man->AddDescriptorKey(key, key.GetPubKey());
}
// Top up key pool, the manager will generate new scriptPubKeys internally
if (!new_spk_man->TopUp()) {
WalletLogPrintf("Could not top up scriptPubKeys\n");
return nullptr;
}
// Apply the label if necessary
// Note: we disable labels for ranged descriptors
if (!desc.descriptor->IsRange()) {
auto script_pub_keys = new_spk_man->GetScriptPubKeys();
if (script_pub_keys.empty()) {
WalletLogPrintf("Could not generate scriptPubKeys (cache is empty)\n");
return nullptr;
}
CTxDestination dest;
if (!internal && ExtractDestination(script_pub_keys.at(0), dest)) {
SetAddressBook(dest, label, "receive");
}
}
// Save the descriptor to memory
auto ret = new_spk_man.get();
m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
// Save the descriptor to DB
ret->WriteDescriptor();
return ret;
}
CAmount CWalletTx::GetOutputValueOut(unsigned int output_index) const {
2019-03-20 11:01:36 +00:00
CAmount ret;
GetNonIssuanceBlindingData(output_index, nullptr, &ret, nullptr, nullptr, nullptr);
2019-03-20 11:01:36 +00:00
return ret;
}
uint256 CWalletTx::GetOutputAmountBlindingFactor(unsigned int output_index) const {
uint256 ret;
GetNonIssuanceBlindingData(output_index, nullptr, nullptr, &ret, nullptr, nullptr);
2019-03-20 11:01:36 +00:00
return ret;
}
uint256 CWalletTx::GetOutputAssetBlindingFactor(unsigned int output_index) const {
uint256 ret;
GetNonIssuanceBlindingData(output_index, nullptr, nullptr, nullptr, nullptr, &ret);
2019-03-20 11:01:36 +00:00
return ret;
}
CAsset CWalletTx::GetOutputAsset(unsigned int output_index) const {
CAsset ret;
GetNonIssuanceBlindingData(output_index, nullptr, nullptr, nullptr, &ret, nullptr);
2019-03-20 11:01:36 +00:00
return ret;
}
CPubKey CWalletTx::GetOutputBlindingPubKey(unsigned int output_index) const {
CPubKey ret;
GetNonIssuanceBlindingData(output_index, &ret, nullptr, nullptr, nullptr, nullptr);
2019-03-20 11:01:36 +00:00
return ret;
}
void CWalletTx::GetIssuanceAssets(unsigned int input_index, CAsset* out_asset, CAsset* out_reissuance_token) const {
assert(input_index < tx->vin.size());
const CAssetIssuance& issuance = tx->vin[input_index].assetIssuance;
if (out_asset && issuance.nAmount.IsNull()) {
out_asset->SetNull();
out_asset = nullptr;
}
if (out_reissuance_token && issuance.nInflationKeys.IsNull()) {
out_reissuance_token->SetNull();
out_reissuance_token = nullptr;
}
if (!(out_asset || out_reissuance_token)) return;
if (issuance.assetBlindingNonce.IsNull()) {
uint256 entropy;
GenerateAssetEntropy(entropy, tx->vin[input_index].prevout, issuance.assetEntropy);
if (out_reissuance_token) {
CalculateReissuanceToken(*out_reissuance_token, entropy, issuance.nAmount.IsCommitment());
}
if (out_asset) {
CalculateAsset(*out_asset, entropy);
}
}
else {
if (out_reissuance_token) {
// Re-issuances don't emit issuance tokens
out_reissuance_token->SetNull();
}
if (out_asset) {
CalculateAsset(*out_asset, issuance.assetEntropy);
}
}
}
2019-03-20 11:01:36 +00:00
uint256 CWalletTx::GetIssuanceBlindingFactor(unsigned int input_index, bool reissuance_token) const {
assert(input_index < tx->vin.size());
CAsset asset;
const CAssetIssuance& issuance = tx->vin[input_index].assetIssuance;
const CTxWitness& wit = tx->witness;
GetIssuanceAssets(input_index, reissuance_token ? nullptr : &asset, reissuance_token ? &asset : nullptr);
if (asset.IsNull()) {
2019-03-20 11:01:36 +00:00
return uint256();
}
const std::vector<unsigned char>& rangeproof = wit.vtxinwit.size() <= input_index ? std::vector<unsigned char>() : (reissuance_token ? wit.vtxinwit[input_index].vchInflationKeysRangeproof : wit.vtxinwit[input_index].vchIssuanceAmountRangeproof);
unsigned int mapValueInd = GetPseudoInputOffset(input_index, reissuance_token)+tx->vout.size();
uint256 ret;
CScript blindingScript(CScript() << OP_RETURN << std::vector<unsigned char>(tx->vin[input_index].prevout.hash.begin(), tx->vin[input_index].prevout.hash.end()) << tx->vin[input_index].prevout.n);
GetBlindingData(mapValueInd, rangeproof, reissuance_token ? issuance.nInflationKeys : issuance.nAmount, CConfidentialAsset(asset), CConfidentialNonce(), blindingScript, nullptr, nullptr, &ret, nullptr, nullptr);
return ret;
}
CAmount CWalletTx::GetIssuanceAmount(unsigned int input_index, bool reissuance_token) const {
assert(input_index < tx->vin.size());
CAsset asset;
const CAssetIssuance& issuance = tx->vin[input_index].assetIssuance;
const CTxWitness& wit = tx->witness;
GetIssuanceAssets(input_index, reissuance_token ? nullptr : &asset, reissuance_token ? &asset : nullptr);
if (asset.IsNull()) {
return -1;
2019-03-20 11:01:36 +00:00
}
unsigned int mapValueInd = GetPseudoInputOffset(input_index, reissuance_token)+tx->vout.size();
const std::vector<unsigned char>& rangeproof = wit.vtxinwit.size() <= input_index ? std::vector<unsigned char>() : (reissuance_token ? wit.vtxinwit[input_index].vchInflationKeysRangeproof : wit.vtxinwit[input_index].vchIssuanceAmountRangeproof);
CAmount ret;
CScript blindingScript(CScript() << OP_RETURN << std::vector<unsigned char>(tx->vin[input_index].prevout.hash.begin(), tx->vin[input_index].prevout.hash.end()) << tx->vin[input_index].prevout.n);
GetBlindingData(mapValueInd, rangeproof, reissuance_token ? issuance.nInflationKeys : issuance.nAmount, CConfidentialAsset(asset), CConfidentialNonce(), blindingScript, nullptr, &ret, nullptr, nullptr, nullptr);
2019-03-20 11:01:36 +00:00
return ret;
}
void CWallet::ComputeBlindingData(const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CConfidentialNonce& nonce, const CScript& scriptPubKey, const std::vector<unsigned char>& vchRangeproof, CAmount& value, CPubKey& blinding_pubkey, uint256& value_factor, CAsset& asset, uint256& asset_factor) const
{
if (conf_value.IsExplicit() && conf_asset.IsExplicit()) {
value = conf_value.GetAmount();
asset = conf_asset.GetAsset();
blinding_pubkey = CPubKey();
value_factor.SetNull();
asset_factor.SetNull();
return;
}
CKey blinding_key;
if ((blinding_key = GetBlindingKey(&scriptPubKey)).IsValid()) {
// For outputs using derived blinding.
if (UnblindConfidentialPair(blinding_key, conf_value, conf_asset, nonce, scriptPubKey, vchRangeproof, value, value_factor, asset, asset_factor)) {
// TODO: make sure SetBlindingData sets it as receiver's blinding pubkey
blinding_pubkey = blinding_key.GetPubKey();
return;
}
}
value = -1;
blinding_pubkey = CPubKey();
value_factor.SetNull();
asset.SetNull();
asset_factor.SetNull();
}
void CWalletTx::WipeUnknownBlindingData()
{
for (unsigned int n = 0; n < tx->vout.size(); n++) {
if (GetOutputValueOut(n) == -1) {
mapValue["blindingdata"][138 * n] = 0;
}
}
for (unsigned int n = 0; n < tx->vin.size(); n++) {
if (!tx->vin[n].assetIssuance.nAmount.IsNull()) {
if (GetIssuanceAmount(n, false) == -1) {
mapValue["blindingdata"][138 * (tx->vout.size() + GetPseudoInputOffset(n, false))] = 0;
}
}
if (!tx->vin[n].assetIssuance.nInflationKeys.IsNull()) {
if (GetIssuanceAmount(n, true) == -1) {
mapValue["blindingdata"][138 * (tx->vout.size() + GetPseudoInputOffset(n, true))] = 0;
}
}
}
}
std::map<uint256, std::pair<CAsset, CAsset> > CWallet::GetReissuanceTokenTypes() const {
std::map<uint256, std::pair<CAsset, CAsset> > tokenMap;
{
2019-05-01 20:51:19 +01:00
LOCK(cs_wallet);
2019-03-20 11:01:36 +00:00
for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) {
const CWalletTx* pcoin = &(*it).second;
CAsset asset;
CAsset token;
uint256 entropy;
for (unsigned int input_index = 0; input_index < pcoin->tx->vin.size(); input_index++) {
const CAssetIssuance& issuance = pcoin->tx->vin[input_index].assetIssuance;
if (issuance.IsNull()) {
continue;
}
// Only looking at initial issuances
if (issuance.assetBlindingNonce.IsNull()) {
GenerateAssetEntropy(entropy, pcoin->tx->vin[input_index].prevout, issuance.assetEntropy);
CalculateAsset(asset, entropy);
// TODO handle the case with null nAmount (not decided yet)
CalculateReissuanceToken(token, entropy, issuance.nAmount.IsCommitment());
tokenMap[entropy] = std::make_pair(token, asset);
}
}
}
}
return tokenMap;
}
CKey CWallet::GetBlindingKey(const CScript* script) const
{
CKey key;
if (script != NULL) {
std::map<CScriptID, uint256>::const_iterator it = mapSpecificBlindingKeys.find(CScriptID(*script));
if (it != mapSpecificBlindingKeys.end()) {
key.Set(it->second.begin(), it->second.end(), true);
if (key.IsValid()) {
return key;
}
}
}
if (script != NULL && !blinding_derivation_key.IsNull()) {
unsigned char vch[32];
CHMAC_SHA256(blinding_derivation_key.begin(), blinding_derivation_key.size()).Write(&((*script)[0]), script->size()).Finalize(vch);
key.Set(&vch[0], &vch[32], true);
if (key.IsValid()) {
return key;
}
}
return CKey();
}
CPubKey CWallet::GetBlindingPubKey(const CScript& script) const
{
CKey key = GetBlindingKey(&script);
if (key.IsValid()) {
return key.GetPubKey();
}
return CPubKey();
}
bool CWallet::LoadSpecificBlindingKey(const CScriptID& scriptid, const uint256& key)
{
AssertLockHeld(cs_wallet); // mapSpecificBlindingKeys
mapSpecificBlindingKeys[scriptid] = key;
return true;
}
bool CWallet::AddSpecificBlindingKey(const CScriptID& scriptid, const uint256& key)
{
AssertLockHeld(cs_wallet); // mapSpecificBlindingKeys
if (!LoadSpecificBlindingKey(scriptid, key))
return false;
return WalletBatch(GetDatabase()).WriteSpecificBlindingKey(scriptid, key);
2019-03-20 11:01:36 +00:00
}
bool CWallet::SetMasterBlindingKey(const uint256& key)
{
AssertLockHeld(cs_wallet);
if (!WalletBatch(GetDatabase()).WriteBlindingDerivationKey(key)) {
2019-03-20 11:01:36 +00:00
return false;
}
blinding_derivation_key = key;
return true;
}
// END ELEMENTS
//