Files
dragonx/src/wallet/wallet.cpp
T
DanSandClaude Opus 5 c1040028e4 wallet: checkpoint rescan progress when the scan is interrupted
An aborted or shutdown-interrupted rescan discarded all of its progress: nothing
advanced the on-disk best-block locator, so the next run started over. On a large
shielded wallet that is a multi-minute witness rebuild repeated from scratch.

Writes the locator ONCE, at the interrupt, rather than periodically through the
scan. That delivers the whole stated benefit -- "resume where it stopped" -- at
the cost of exactly one SetBestChain per interrupted scan. A periodic checkpoint
would additionally survive SIGKILL and power loss, a much weaker requirement, and
it is the part that carries all the cost: SetBestChainINTERNAL is O(whole wallet)
regardless of progress, so on the reported 5.3k-tx / 7.5k-note wallet a checkpoint
every 2500 blocks would plausibly cost more than the rebuild it was meant to save.
If someone later demonstrates a need for crash resilience mid-scan, add it then,
with a measured interval and fFlush=false -- not before.

Two guards, both necessary:

  - CONTIGUITY. The RPC entry points (rescan / importprivkey / z_importkey /
    z_importviewingkey) take a caller-supplied start height validated only against
    chainActive.Height(), never against the wallet's own persisted locator. A scan
    beginning above that locator must not checkpoint at all, or it would record
    the skipped range as scanned and hide any funds in it. Logged once when it
    applies, so an operator can see why an interrupt did not persist.

  - MONOTONICITY. A checkpoint may only advance the locator, never move it back.

The locator points at the last FULLY PROCESSED block -- the parent of the block we
were about to scan -- so resume restarts one block early. CChain::GetLocator pushes
its argument first and FindForkInGlobalIndex returns that same block, so resume
begins AT it; the one-block overlap is deliberate and idempotent, since AddToWallet
only takes its merge path when the transaction is already present.

No witness work is done at the interrupt, which answers the two "//TODO: should we
update witness caches?" comments this replaces: witnesses are re-derived from each
note's own witnessHeight independently of the locator, and witnessRootValidated is
never serialized, so every note is revalidated against hashFinalSaplingRoot on the
next start. A mid-scan checkpoint does capture notes at mixed witnessHeights -- the
in-loop BuildWitnessCache(pindex, true) returns before the extension phase and only
seeds new notes at their own transaction's height -- but that state is recoverable:
the post-loop BuildWitnessCache(tip, false) levels every note, and it now occurs at
most once per scan instead of hundreds of times.

Uses SetBestChainNoFlush so the checkpoint does not trigger a full BDB
txn_checkpoint over the whole cache, and reports failure rather than silently
leaving the operator to discover the replay.

Depends on the fLastRescanCompleted gate in the previous commit: without it
init.cpp would overwrite this checkpoint with a tip locator immediately after the
scan returns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo
2026-08-31 20:54:37 -05:00

5828 lines
214 KiB
C++

// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2016-2024 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
* See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at *
* the top-level directory of this distribution for the individual copyright *
* holder information and the developer policies on copyright and licensing. *
* *
* Unless otherwise agreed in a custom licensing agreement, no part of the *
* SuperNET software, including this file may be copied, modified, propagated *
* or distributed except according to the terms contained in the LICENSE file *
* *
* Removal or modification of this copyright notice is prohibited. *
* *
******************************************************************************/
#include "wallet/wallet.h"
#include "asyncrpcqueue.h"
#include "checkpoints.h"
#include "coincontrol.h"
#include "consensus/upgrades.h"
#include "consensus/validation.h"
#include "consensus/consensus.h"
#include "init.h"
#include "key_io.h"
#include "main.h"
#include "net.h"
#include "rpc/protocol.h"
#include "script/script.h"
#include "script/sign.h"
#include "timedata.h"
#include "utilmoneystr.h"
#include "zcash/Note.hpp"
#include "crypter.h"
#include "wallet/mnemonic.h"
#include "coins.h"
#include "wallet/asyncrpcoperation_saplingconsolidation.h"
#include "wallet/asyncrpcoperation_sweep.h"
#include "wallet/asyncrpcoperation_autoshieldcoinbase.h"
#include <random>
#include <limits>
#include <thread>
#include <atomic>
#include <mutex>
#include "zcash/zip32.h"
#include "cc/CCinclude.h"
#include <assert.h>
#include <boost/algorithm/string/replace.hpp>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#if defined(__GLIBC__)
#include <malloc.h>
#endif
using namespace std;
using namespace libzcash;
/**
* Settings
*/
const char * DEFAULT_WALLET_DAT = "wallet.dat";
CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET;
bool bSpendZeroConfChange = true;
bool fSendFreeTransactions = false;
bool fPayAtLeastCustomFee = true;
#include "hush_defs.h"
CBlockIndex *hush_chainactive(int32_t height);
extern int32_t HUSH_LOADINGBLOCKS;
int32_t hush_dpowconfs(int32_t height,int32_t numconfs);
int tx_height( const uint256 &hash );
bool fTxDeleteEnabled = false;
bool fTxConflictDeleteEnabled = false;
int fDeleteInterval = DEFAULT_TX_DELETE_INTERVAL;
unsigned int fDeleteTransactionsAfterNBlocks = DEFAULT_TX_RETENTION_BLOCKS;
unsigned int fKeepLastNTransactions = DEFAULT_TX_RETENTION_LASTTX;
/**
* Fees smaller than this (in satoshi) are considered zero fee (for transaction creation)
* Override with -mintxfee
*/
CFeeRate CWallet::minTxFee = CFeeRate(1000);
/** @defgroup mapWallet
*
* @{
*/
struct CompareValueOnly
{
bool operator()(const pair<CAmount, pair<const CWalletTx*, unsigned int> >& t1,
const pair<CAmount, pair<const CWalletTx*, unsigned int> >& t2) const
{
return t1.first < t2.first;
}
};
std::string JSOutPoint::ToString() const
{
return strprintf("JSOutPoint(%s, %d, %d)", hash.ToString().substr(0,10), js, n);
}
std::string COutput::ToString() const
{
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->vout[i].nValue));
}
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
{
LOCK(cs_wallet);
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(hash);
if (it == mapWallet.end())
return NULL;
return &(it->second);
}
// Generate a new Sapling spending key and return its public payment address
SaplingPaymentAddress CWallet::GenerateNewSaplingZKey(bool addToWallet)
{
AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata
// Create new metadata
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
// Try to get the seed
HDSeed seed;
if (!GetHDSeedForDerivation(seed))
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): HD seed not found");
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
uint32_t bip44CoinType = Params().BIP44CoinType();
// We use a fixed keypath scheme of m/32'/coin_type'/account'
// Derive m/32'
auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT);
// Derive m/32'/coin_type'
auto m_32h_cth = m_32h.Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT);
// Derive account key at next index, skip keys already known to the wallet
libzcash::SaplingExtendedSpendingKey xsk;
do
{
xsk = m_32h_cth.Derive(hdChain.saplingAccountCounter | ZIP32_HARDENED_KEY_LIMIT);
metadata.hdKeypath = "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(hdChain.saplingAccountCounter) + "'";
metadata.seedFp = hdChain.seedFp;
// Increment childkey index
hdChain.saplingAccountCounter++;
} while (HaveSaplingSpendingKey(xsk.expsk.full_viewing_key()));
// Update the chain model in the database
if (fFileBacked && !CWalletDB(strWalletFile).WriteHDChain(hdChain))
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): Writing HD chain model failed");
auto ivk = xsk.expsk.full_viewing_key().in_viewing_key();
mapSaplingZKeyMetadata[ivk] = metadata;
auto addr = xsk.DefaultAddress();
if (addToWallet && !AddSaplingZKey(xsk, addr)) {
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): AddSaplingZKey failed");
}
// return default sapling payment address.
return addr;
}
// Add spending key to keystore
bool CWallet::AddSaplingZKey(
const libzcash::SaplingExtendedSpendingKey &sk,
const libzcash::SaplingPaymentAddress &defaultAddr)
{
AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata
if (!CCryptoKeyStore::AddSaplingSpendingKey(sk, defaultAddr)) {
return false;
}
nTimeFirstKey = 1; // No birthday information for viewing keys.
if (!fFileBacked) {
return true;
}
if (!IsCrypted()) {
auto ivk = sk.expsk.full_viewing_key().in_viewing_key();
return CWalletDB(strWalletFile).WriteSaplingZKey(ivk, sk, mapSaplingZKeyMetadata[ivk]);
}
return true;
}
// Add payment address -> incoming viewing key map entry
bool CWallet::AddSaplingIncomingViewingKey(
const libzcash::SaplingIncomingViewingKey &ivk,
const libzcash::SaplingPaymentAddress &addr)
{
AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata
if (!CCryptoKeyStore::AddSaplingIncomingViewingKey(ivk, addr)) {
return false;
}
if (!fFileBacked) {
return true;
}
if (!IsCrypted()) {
return CWalletDB(strWalletFile).WriteSaplingPaymentAddress(addr, ivk);
}
return true;
}
CPubKey CWallet::GenerateNewKey()
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
CKey secret;
// Create new metadata
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
// Derive the transparent key deterministically from the HD seed when the
// feature is enabled, so it can be recovered from the seed alone. Otherwise
// fall back to a random key (e.g. legacy wallets that have no HD seed).
if (IsHDTransparentEnabled()) {
DeriveNewChildKey(metadata, secret);
fCompressed = true; // BIP32-derived keys are always compressed
} else {
secret.MakeNewKey(fCompressed);
}
// Compressed public keys were introduced in version 0.6.0
if (fCompressed)
SetMinVersion(FEATURE_COMPRPUBKEY);
CPubKey pubkey = secret.GetPubKey();
assert(secret.VerifyPubKey(pubkey));
mapKeyMetadata[pubkey.GetID()] = metadata;
if (!nTimeFirstKey || nCreationTime < nTimeFirstKey)
nTimeFirstKey = nCreationTime;
if (!AddKeyPubKey(secret, pubkey))
throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed");
return pubkey;
}
// Derive a new transparent key from the HD seed along the BIP44 external chain
// m/44'/coin_type'/0'/0/i. The child index is taken from (and advances)
// hdChain.transparentChildCounter, which is persisted so the same keys can be
// regenerated after a seed-only restore. Mirrors GenerateNewSaplingZKey.
void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secretRet)
{
AssertLockHeld(cs_wallet); // mapKeyMetadata / hdChain
HDSeed seed;
if (!GetHDSeedForDerivation(seed))
throw std::runtime_error("CWallet::DeriveNewChildKey(): HD seed not found");
RawHDSeed rawSeed = seed.RawSeed();
CExtKey masterKey; // m
CExtKey purposeKey; // m/44'
CExtKey coinTypeKey; // m/44'/coin_type'
CExtKey accountKey; // m/44'/coin_type'/0'
CExtKey externalChainKey; // m/44'/coin_type'/0'/0
CExtKey childKey; // m/44'/coin_type'/0'/0/i
masterKey.SetMaster(rawSeed.data(), rawSeed.size());
uint32_t bip44CoinType = Params().BIP44CoinType();
// BIP44 path, single account (0'), external chain (0). On this ac_private=1
// chain the internal/change chain can never hold value, so it is unused.
masterKey.Derive(purposeKey, 44 | BIP32_HARDENED_KEY_LIMIT);
purposeKey.Derive(coinTypeKey, bip44CoinType | BIP32_HARDENED_KEY_LIMIT);
coinTypeKey.Derive(accountKey, 0 | BIP32_HARDENED_KEY_LIMIT);
accountKey.Derive(externalChainKey, 0);
// Derive the next child index, skipping any key already in the wallet.
do {
externalChainKey.Derive(childKey, hdChain.transparentChildCounter);
metadata.hdKeypath = "m/44'/" + std::to_string(bip44CoinType) + "'/0'/0/" + std::to_string(hdChain.transparentChildCounter);
metadata.seedFp = hdChain.seedFp;
hdChain.transparentChildCounter++;
} while (HaveKey(childKey.key.GetPubKey().GetID()));
secretRet = childKey.key;
// Bump a legacy v1 chain to v2 so the transparent counter gets persisted.
if (hdChain.nVersion < CHDChain::VERSION_HD_TRANSPARENT)
hdChain.nVersion = CHDChain::VERSION_HD_TRANSPARENT;
// Persist the advanced counter so restarts / restores don't reuse indices.
if (fFileBacked && !CWalletDB(strWalletFile).WriteHDChain(hdChain))
throw std::runtime_error("CWallet::DeriveNewChildKey(): Writing HD chain model failed");
}
bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey))
return false;
// check if we need to remove from watch-only
CScript script;
script = GetScriptForDestination(pubkey.GetID());
if (HaveWatchOnly(script))
RemoveWatchOnly(script);
if (!fFileBacked)
return true;
if (!IsCrypted()) {
return CWalletDB(strWalletFile).WriteKey(pubkey,
secret.GetPrivKey(),
mapKeyMetadata[pubkey.GetID()]);
}
return true;
}
bool CWallet::AddCryptedKey(const CPubKey &vchPubKey,
const vector<unsigned char> &vchCryptedSecret)
{
if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret))
return false;
if (!fFileBacked)
return true;
{
LOCK(cs_wallet);
if (pwalletdbEncryption)
return pwalletdbEncryption->WriteCryptedKey(vchPubKey,
vchCryptedSecret,
mapKeyMetadata[vchPubKey.GetID()]);
else
return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey,
vchCryptedSecret,
mapKeyMetadata[vchPubKey.GetID()]);
}
return false;
}
bool CWallet::AddCryptedSaplingSpendingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk,
const std::vector<unsigned char> &vchCryptedSecret,
const libzcash::SaplingPaymentAddress &defaultAddr)
{
if (!CCryptoKeyStore::AddCryptedSaplingSpendingKey(extfvk, vchCryptedSecret, defaultAddr))
return false;
if (!fFileBacked)
return true;
{
LOCK(cs_wallet);
if (pwalletdbEncryption) {
return pwalletdbEncryption->WriteCryptedSaplingZKey(extfvk,
vchCryptedSecret,
mapSaplingZKeyMetadata[extfvk.fvk.in_viewing_key()]);
} else {
return CWalletDB(strWalletFile).WriteCryptedSaplingZKey(extfvk,
vchCryptedSecret,
mapSaplingZKeyMetadata[extfvk.fvk.in_viewing_key()]);
}
}
return false;
}
bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta)
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
if (meta.nCreateTime && (!nTimeFirstKey || meta.nCreateTime < nTimeFirstKey))
nTimeFirstKey = meta.nCreateTime;
mapKeyMetadata[pubkey.GetID()] = meta;
return true;
}
bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
{
return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret);
}
bool CWallet::LoadCryptedSaplingZKey(
const libzcash::SaplingExtendedFullViewingKey &extfvk,
const std::vector<unsigned char> &vchCryptedSecret)
{
return CCryptoKeyStore::AddCryptedSaplingSpendingKey(extfvk, vchCryptedSecret, extfvk.DefaultAddress());
}
bool CWallet::LoadSaplingZKeyMetadata(const libzcash::SaplingIncomingViewingKey &ivk, const CKeyMetadata &meta)
{
AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata
mapSaplingZKeyMetadata[ivk] = meta;
return true;
}
bool CWallet::LoadSaplingZKey(const libzcash::SaplingExtendedSpendingKey &key)
{
return CCryptoKeyStore::AddSaplingSpendingKey(key, key.DefaultAddress());
}
bool CWallet::LoadSaplingPaymentAddress(
const libzcash::SaplingPaymentAddress &addr,
const libzcash::SaplingIncomingViewingKey &ivk)
{
return CCryptoKeyStore::AddSaplingIncomingViewingKey(ivk, addr);
}
bool CWallet::AddCScript(const CScript& redeemScript)
{
if (!CCryptoKeyStore::AddCScript(redeemScript))
return false;
if (!fFileBacked)
return true;
return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript);
}
bool CWallet::LoadCScript(const CScript& redeemScript)
{
/* A sanity check was added in pull #3843 to avoid adding redeemScripts
* that never can be redeemed. However, old wallets may still contain
* these. Do not add them to the wallet and warn. */
if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE)
{
std::string strAddr = EncodeDestination(CScriptID(redeemScript));
LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n",
__func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr);
return true;
}
return CCryptoKeyStore::AddCScript(redeemScript);
}
bool CWallet::AddWatchOnly(const CScript &dest)
{
if (!CCryptoKeyStore::AddWatchOnly(dest))
return false;
nTimeFirstKey = 1; // No birthday information for watch-only keys.
NotifyWatchonlyChanged(true);
if (!fFileBacked)
return true;
return CWalletDB(strWalletFile).WriteWatchOnly(dest);
}
bool CWallet::RemoveWatchOnly(const CScript &dest)
{
AssertLockHeld(cs_wallet);
if (!CCryptoKeyStore::RemoveWatchOnly(dest))
return false;
if (!HaveWatchOnly())
NotifyWatchonlyChanged(false);
if (fFileBacked)
if (!CWalletDB(strWalletFile).EraseWatchOnly(dest))
return false;
return true;
}
bool CWallet::LoadWatchOnly(const CScript &dest)
{
return CCryptoKeyStore::AddWatchOnly(dest);
}
bool CWallet::Unlock(const SecureString& strWalletPassphrase)
{
CCrypter crypter;
CKeyingMaterial vMasterKey;
{
LOCK(cs_wallet);
BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
{
if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
continue; // try another master key
if (CCryptoKeyStore::Unlock(vMasterKey))
return true;
}
}
return false;
}
bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase)
{
bool fWasLocked = IsLocked();
{
LOCK(cs_wallet);
Lock();
CCrypter crypter;
CKeyingMaterial vMasterKey;
BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
{
if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
return false;
if (CCryptoKeyStore::Unlock(vMasterKey))
{
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations = 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 + pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2;
if (pMasterKey.second.nDeriveIterations < 25000)
pMasterKey.second.nDeriveIterations = 25000;
LogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey))
return false;
CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second);
if (fWasLocked)
Lock();
return true;
}
}
}
return false;
}
void CWallet::ChainTip(const CBlockIndex *pindex,
const CBlock *pblock,
boost::optional<std::pair<SproutMerkleTree, SaplingMerkleTree>> added)
{
if (added) {
bool initialDownloadCheck = IsInitialBlockDownload();
// Prevent witness cache building && consolidation transactions
// from being created when node is syncing after launch,
// and also when node wakes up from suspension/hibernation and incoming blocks are old.
// 144 blocks = 3hrs @ 75s blocktime
if (!initialDownloadCheck &&
pblock->GetBlockTime() > GetTime() - 144*ASSETCHAINS_BLOCKTIME)
{
BuildWitnessCache(pindex, false);
if (fSaplingConsolidationEnabled) {
RunSaplingConsolidation(pindex->GetHeight());
}
if (fSweepEnabled) {
RunSaplingSweep(pindex->GetHeight());
}
if (fAutoShieldEnabled) {
RunAutoShieldCoinbase(pindex->GetHeight());
}
if (fTxDeleteEnabled) {
DeleteWalletTransactions(pindex);
}
} else {
//Build initial witnesses on every block
BuildWitnessCache(pindex, true);
if (fTxDeleteEnabled) {
if (initialDownloadCheck && pindex->GetHeight() % fDeleteInterval == 0) {
DeleteWalletTransactions(pindex);
}
}
}
} else {
DecrementNoteWitnesses(pindex);
UpdateNullifierNoteMapForBlock(pblock);
}
}
void CWallet::RunSaplingSweep(int blockHeight) {
// Sapling is always active since height=1 on DragonX
// if (!NetworkUpgradeActive(blockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
// return;
// }
if (blockHeight == 0)
return;
// Take cs_wallet ourselves: ChainTip (the notify-thread caller) does NOT
// hold it here, and we mutate fSweepRunning/nextSweep/saplingSweepOperationId
// and enqueue below. Matches RunSaplingConsolidation/RunAutoShieldCoinbase.
// (The old AssertLockHeld(cs_wallet) was a no-op in release builds and thus
// masked an unsynchronized mutation.) cs_wallet is recursive, so this is
// safe even on any path that already holds it.
LOCK(cs_wallet);
// Stale-baton guard. A successful-but-incomplete sweep round deliberately
// returns with fSweepRunning still set and nextSweep unadvanced (see
// AsyncRPCOperation_sweep::main), as a "continue draining next block" baton.
// But every early return below leaves that baton set WITHOUT re-dispatching,
// and RunSaplingConsolidation -- which is gated on fSweepRunning -- then
// returns without advancing nextConsolidation, so the "consolidation is
// within 5 blocks" blackout at the top of this function never lifts. That
// is a self-sustaining three-way deadlock: sweep waits on consolidation,
// consolidation waits on sweep, and autoshield shares the same gate, so a
// wedged sweep silently disables coinbase shielding forever.
// Only honour the baton while a sweep operation genuinely is in flight.
if (fSweepRunning) {
std::shared_ptr<AsyncRPCQueue> sweepQueue = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> inFlightSweep =
(sweepQueue != nullptr) ? sweepQueue->getOperationForId(saplingSweepOperationId) : nullptr;
bool inFlight = (inFlightSweep != nullptr) &&
(inFlightSweep->isReady() || inFlightSweep->isExecuting());
if (!inFlight) {
LogPrintf("%s: clearing stale fSweepRunning at blockHeight=%d (no sweep operation in flight)\n",
__func__, blockHeight);
fSweepRunning = false;
}
}
if (!fSweepEnabled) {
return;
}
if (nextSweep > blockHeight) {
LogPrintf("%s: Not time to sweep yet at blockHeight=%d nextSweep=%d\n", __func__, blockHeight, nextSweep);
return;
}
LogPrintf("%s: Sweep enabled at blockHeight=%d nextSweep=%d\n", __func__, blockHeight, nextSweep);
//Don't Run if consolidation will run soon.
if (fSaplingConsolidationEnabled && nextConsolidation - 5 <= blockHeight) {
LogPrintf("%s: not sweeping since next consolidation is within 5 blocks, nextConsolidation=%d , blockHeight=%d\n", __func__, nextConsolidation, blockHeight);
return;
}
//Don't Run While consolidation is running.
if (fConsolidationRunning) {
LogPrintf("%s: not sweeping since consolidation is currently running at height=%d\n", __func__, blockHeight);
return;
}
//Don't Run While auto-shield is running.
if (fAutoShieldRunning) {
LogPrintf("%s: not sweeping since autoshield is currently running at height=%d\n", __func__, blockHeight);
return;
}
fSweepRunning = true;
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> lastOperation = q->getOperationForId(saplingSweepOperationId);
if (lastOperation != nullptr) {
lastOperation->cancel();
// Drop it from the queue's map as well. Nothing else ever removes these:
// popOperationForId is only reached from z_getoperationresult, so on a node
// running this every interval the map grew without bound.
q->popOperationForId(saplingSweepOperationId);
}
pendingSaplingSweepTxs.clear();
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sweep(blockHeight + AUTO_OP_TARGET_HEIGHT_OFFSET));
saplingSweepOperationId = operation->getId();
if (!q->addOperation(operation)) {
// Queue is closing (shutdown). Release the flag we just set, or it stays
// set with no operation in flight and blocks every later round.
LogPrintf("%s: async queue is not accepting operations; skipping this round\n", __func__);
fSweepRunning = false;
return;
}
}
void CWallet::RunSaplingConsolidation(int blockHeight) {
// Sapling is always active on DragonX (activated at height=1)
//if (!NetworkUpgradeActive(blockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
// return;
//}
LOCK(cs_wallet);
if (!fSaplingConsolidationEnabled) {
return;
}
if (nextConsolidation > blockHeight) {
LogPrintf("%s: Not time to consolidate yet at blockHeight=%d nextConsolidation=%d\n", __func__, blockHeight, nextConsolidation);
return;
}
// Self-guard: an op is already in flight (nextConsolidation only advances
// when it completes). Don't cancel + re-enqueue a fresh op every block.
if (fConsolidationRunning) {
return;
}
LogPrintf("%s: consolidation enabled at blockHeight=%d fSweepRunning=%d\n", __func__, blockHeight, fSweepRunning );
if (fSweepRunning) {
LogPrintf("%s: not consolidating since sweep is currently running at height=%d\n", __func__, blockHeight);
return;
}
if (fAutoShieldRunning) {
LogPrintf("%s: not consolidating since autoshield is currently running at height=%d\n", __func__, blockHeight);
return;
}
LogPrintf("%s: creating consolidation operation at blockHeight=%d\n", __func__, blockHeight);
fConsolidationRunning = true;
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> lastOperation = q->getOperationForId(saplingConsolidationOperationId);
if (lastOperation != nullptr) {
lastOperation->cancel();
// Drop it from the queue's map as well. Nothing else ever removes these:
// popOperationForId is only reached from z_getoperationresult, so on a node
// running this every interval the map grew without bound.
q->popOperationForId(saplingConsolidationOperationId);
}
pendingSaplingConsolidationTxs.clear();
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_saplingconsolidation(blockHeight + AUTO_OP_TARGET_HEIGHT_OFFSET));
saplingConsolidationOperationId = operation->getId();
if (!q->addOperation(operation)) {
// Queue is closing (shutdown). Release the flag we just set, or it stays
// set with no operation in flight and blocks every later round.
LogPrintf("%s: async queue is not accepting operations; skipping this round\n", __func__);
fConsolidationRunning = false;
return;
}
}
// Periodically drain matured transparent coinbase into a wallet-owned Sapling
// z-address. Default-ON but conditional: this is enqueue-only (all gathering
// happens on the async worker thread inside the op, which is why we must not
// take cs_main here — ChainTip runs from the wallet-notify context). It is a
// silent no-op wherever it cannot act (locked wallet, no owned coinbase,
// external -mineraddress), so it is safe to run on every node.
void CWallet::RunAutoShieldCoinbase(int blockHeight) {
// Sapling is always active from height 1 on DragonX+HACs.
if (blockHeight == 0)
return;
LOCK(cs_wallet);
if (!fAutoShieldEnabled) {
return;
}
if (nextAutoShield > blockHeight) {
return;
}
// Self-guard: an op is already in flight (nextAutoShield only advances when
// it completes). Don't cancel + re-enqueue a fresh op every block.
if (fAutoShieldRunning) {
return;
}
// Mutual exclusion: sweep/consolidation share the single async worker and
// cs_wallet; don't queue an autoshield in the same connected block.
if (fSweepRunning || fConsolidationRunning) {
LogPrintf("%s: not autoshielding since sweep/consolidation is running at height=%d\n", __func__, blockHeight);
return;
}
// Silent no-op while locked: we can neither sign the shield nor derive a
// destination z-addr. Advance the interval so we don't retry every block.
if (IsLocked()) {
LogPrintf("%s: wallet locked; matured coinbase will accumulate until unlocked (height=%d)\n", __func__, blockHeight);
nextAutoShield = autoShieldInterval + blockHeight;
return;
}
fAutoShieldRunning = true;
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> lastOperation = q->getOperationForId(saplingAutoShieldOperationId);
if (lastOperation != nullptr) {
lastOperation->cancel();
// Drop it from the queue's map as well. Nothing else ever removes these:
// popOperationForId is only reached from z_getoperationresult, so on a node
// running this every interval the map grew without bound.
q->popOperationForId(saplingAutoShieldOperationId);
}
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_autoshieldcoinbase(blockHeight + AUTO_OP_TARGET_HEIGHT_OFFSET));
saplingAutoShieldOperationId = operation->getId();
if (!q->addOperation(operation)) {
// Queue is closing (shutdown). Release the flag we just set, or it stays
// set with no operation in flight and blocks every later round.
LogPrintf("%s: async queue is not accepting operations; skipping this round\n", __func__);
fAutoShieldRunning = false;
return;
}
}
bool CWallet::CommitAutomatedTx(const CTransaction& tx) {
CWalletTx wtx(this, tx);
CReserveKey reservekey(pwalletMain);
// No tx dump here: CommitTransaction already LogPrintf's the same wtx.ToString(),
// and ToString() emits a line per vin, so with the 400-input autoshield cap this
// printed tens of KB to stderr on every automated round.
return CommitTransaction(wtx, reservekey);
}
void CWallet::SetBestChain(const CBlockLocator& loc)
{
// Default ctor => fFlushOnClose=true => ~CDB runs a full BDB txn_checkpoint over the entire
// cache. Fine for the hourly/shutdown callers; ruinous inside a scan (see SetBestChainNoFlush).
CWalletDB walletdb(strWalletFile);
SetBestChainINTERNAL(walletdb, loc);
}
bool CWallet::SetBestChainNoFlush(const CBlockLocator& loc)
{
CWalletDB walletdb(strWalletFile, "r+", false);
return SetBestChainINTERNAL(walletdb, loc);
}
std::set<std::pair<libzcash::PaymentAddress, uint256>> CWallet::GetNullifiersForAddresses(
const std::set<libzcash::PaymentAddress> & addresses)
{
std::set<std::pair<libzcash::PaymentAddress, uint256>> nullifierSet;
// Sapling ivk -> list of addrs map
// (There may be more than one diversified address for a given ivk.)
std::map<libzcash::SaplingIncomingViewingKey, std::vector<libzcash::SaplingPaymentAddress>> ivkMap;
for (const auto & addr : addresses) {
auto saplingAddr = boost::get<libzcash::SaplingPaymentAddress>(&addr);
if (saplingAddr != nullptr) {
libzcash::SaplingIncomingViewingKey ivk;
this->GetSaplingIncomingViewingKey(*saplingAddr, ivk);
ivkMap[ivk].push_back(*saplingAddr);
}
}
for (const auto & txPair : mapWallet) {
// Sapling
for (const auto & noteDataPair : txPair.second.mapSaplingNoteData) {
auto & noteData = noteDataPair.second;
auto & nullifier = noteData.nullifier;
auto & ivk = noteData.ivk;
if (nullifier && ivkMap.count(ivk)) {
for (const auto & addr : ivkMap[ivk]) {
nullifierSet.insert(std::make_pair(addr, nullifier.get()));
}
}
}
}
return nullifierSet;
}
bool CWallet::IsNoteSaplingChange(const std::set<std::pair<libzcash::PaymentAddress, uint256>> & nullifierSet,
const libzcash::PaymentAddress & address,
const SaplingOutPoint & op)
{
// A Note is marked as "change" if the address that received it
// also spent Notes in the same transaction. This will catch,
// for instance:
// - Change created by spending fractions of Notes (because
// z_sendmany sends change to the originating z-address).
// - Notes created by consolidation transactions (e.g. using
// z_mergetoaddress).
// - Notes sent from one address to itself.
for (const SpendDescription &spend : mapWallet[op.hash].vShieldedSpend) {
if (nullifierSet.count(std::make_pair(address, spend.nullifier))) {
return true;
}
}
return false;
}
bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, bool fExplicit)
{
LOCK(cs_wallet); // nWalletVersion
if (nWalletVersion >= nVersion)
return true;
// when doing an explicit upgrade, if we pass the max version permitted, upgrade all the way
if (fExplicit && nVersion > nWalletMaxVersion)
nVersion = FEATURE_LATEST;
nWalletVersion = nVersion;
if (nVersion > nWalletMaxVersion)
nWalletMaxVersion = nVersion;
if (fFileBacked)
{
CWalletDB* pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(strWalletFile);
if (nWalletVersion > 40000)
pwalletdb->WriteMinVersion(nWalletVersion);
if (!pwalletdbIn)
delete pwalletdb;
}
return true;
}
bool CWallet::SetMaxVersion(int nVersion)
{
LOCK(cs_wallet); // nWalletVersion, nWalletMaxVersion
// cannot downgrade below current version
if (nWalletVersion > nVersion)
return false;
nWalletMaxVersion = nVersion;
return true;
}
set<uint256> CWallet::GetConflicts(const uint256& txid) const
{
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;
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
{
if (mapTxSpends.count(txin.prevout) <= 1)
continue; // No conflict if zero or one spends
range = mapTxSpends.equal_range(txin.prevout);
for (TxSpends::const_iterator it = range.first; it != range.second; ++it)
result.insert(it->second);
}
std::pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> range_o;
for (const SpendDescription &spend : wtx.vShieldedSpend) {
uint256 nullifier = spend.nullifier;
if (mapTxSaplingNullifiers.count(nullifier) <= 1) {
continue; // No conflict if zero or one spends
}
range_o = mapTxSaplingNullifiers.equal_range(nullifier);
for (TxNullifiers::const_iterator it = range_o.first; it != range_o.second; ++it) {
result.insert(it->second);
}
}
return result;
}
void CWallet::Flush(bool shutdown)
{
bitdb.Flush(shutdown);
}
bool CWallet::Verify(const string& walletFile, string& warningString, string& errorString)
{
LogPrintf("Using wallet %s\n", walletFile);
uiInterface.InitMessage(_("Verifying wallet..."));
if (walletFile != boost::filesystem::basename(walletFile) + boost::filesystem::extension(walletFile)) {
boost::filesystem::path path(walletFile);
if (path.is_absolute()) {
if (!boost::filesystem::exists(path.parent_path())) {
LogPrintf("Absolute path %s does not exist!", walletFile);
return false;
}
} else {
boost::filesystem::path full_path = GetDataDir() / path;
if (!boost::filesystem::exists(full_path.parent_path())) {
LogPrintf("Relative path %s does not exist!", walletFile);
return false;
}
}
}
if (!bitdb.Open(GetDataDir()))
{
// try moving the database env out of the way
boost::filesystem::path pathDatabase = GetDataDir() / "database";
boost::filesystem::path pathDatabaseBak = GetDataDir() / strprintf("database.%d.bak", GetTime());
try {
boost::filesystem::rename(pathDatabase, pathDatabaseBak);
LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string());
} catch (const boost::filesystem::filesystem_error&) {
// failure is ok (well, not really, but it's not worse than what we started with)
}
// try again
if (!bitdb.Open(GetDataDir())) {
// if it still fails, it probably means we can't even create the database env
string msg = strprintf(_("Error initializing wallet database environment %s!"), GetDataDir());
errorString += msg;
return true;
}
}
if (GetBoolArg("-salvagewallet", false))
{
// Recover readable keypairs:
if (!CWalletDB::Recover(bitdb, walletFile, true))
return false;
}
if (boost::filesystem::exists(GetDataDir() / walletFile))
{
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, CWalletDB::Recover);
if (r == CDBEnv::RECOVER_OK)
{
warningString += strprintf(_("Warning: wallet.dat corrupt, data salvaged!"
" Original wallet.dat saved as wallet.{timestamp}.bak in %s; if"
" your balance or transactions are incorrect you should"
" restore from a backup."), GetDataDir());
}
if (r == CDBEnv::RECOVER_FAIL)
errorString += _("wallet.dat corrupt, salvage failed");
}
return true;
}
template <class T>
void CWallet::SyncMetaData(pair<typename TxSpendMap<T>::iterator, typename TxSpendMap<T>::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 = NULL;
for (typename TxSpendMap<T>::iterator it = range.first; it != range.second; ++it)
{
const uint256& hash = it->second;
int n = mapWallet[hash].nOrderPos;
if (n < nMinOrderPos)
{
nMinOrderPos = n;
copyFrom = &mapWallet[hash];
}
}
// Now copy data from copyFrom to rest:
for (typename TxSpendMap<T>::iterator it = range.first; it != range.second; ++it)
{
const uint256& hash = it->second;
CWalletTx* copyTo = &mapWallet[hash];
if (copyFrom == copyTo) continue;
copyTo->mapValue = copyFrom->mapValue;
// mapSproutNoteData and mapSaplingNoteData not copied on purpose
// (it is always set correctly for each CWalletTx)
copyTo->vOrderForm = copyFrom->vOrderForm;
// fTimeReceivedIsTxTime not copied on purpose
// nTimeReceived not copied on purpose
copyTo->nTimeSmart = copyFrom->nTimeSmart;
copyTo->fFromMe = copyFrom->fFromMe;
copyTo->strFromAccount = copyFrom->strFromAccount;
// nOrderPos not copied on purpose
// cached members not copied on purpose
}
}
/**
* Outpoint is spent if any non-conflicted transaction
* spends it:
*/
bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
{
const COutPoint outpoint(hash, n);
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() && mit->second.GetDepthInMainChain() >= 0)
return true; // Spent
}
return false;
}
unsigned int CWallet::GetSpendDepth(const uint256& hash, unsigned int n) const
{
const COutPoint outpoint(hash, n);
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() && mit->second.GetDepthInMainChain() >= 0)
return mit->second.GetDepthInMainChain(); // Spent
}
return 0;
}
bool CWallet::IsSaplingSpent(const uint256& nullifier) const {
pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> range;
range = mapTxSaplingNullifiers.equal_range(nullifier);
for (TxNullifiers::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() && mit->second.GetDepthInMainChain() >= 0) {
return true; // Spent
}
}
return false;
}
unsigned int CWallet::GetSaplingSpendDepth(const uint256& nullifier) const {
pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> range;
range = mapTxSaplingNullifiers.equal_range(nullifier);
for (TxNullifiers::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() && mit->second.GetDepthInMainChain() >= 0) {
return mit->second.GetDepthInMainChain(); // Spent
}
}
return 0;
}
void CWallet::AddToTransparentSpends(const COutPoint& outpoint, const uint256& wtxid)
{
mapTxSpends.insert(make_pair(outpoint, wtxid));
pair<TxSpends::iterator, TxSpends::iterator> range;
range = mapTxSpends.equal_range(outpoint);
SyncMetaData<COutPoint>(range);
}
void CWallet::AddToSaplingSpends(const uint256& nullifier, const uint256& wtxid)
{
mapTxSaplingNullifiers.insert(make_pair(nullifier, wtxid));
pair<TxNullifiers::iterator, TxNullifiers::iterator> range;
range = mapTxSaplingNullifiers.equal_range(nullifier);
SyncMetaData<uint256>(range);
}
void CWallet::AddToSpends(const uint256& wtxid)
{
assert(mapWallet.count(wtxid));
CWalletTx& thisTx = mapWallet[wtxid];
if (thisTx.IsCoinBase()) // Coinbases don't spend anything!
return;
for (const CTxIn& txin : thisTx.vin) {
AddToTransparentSpends(txin.prevout, wtxid);
}
for (const SpendDescription &spend : thisTx.vShieldedSpend) {
AddToSaplingSpends(spend.nullifier, wtxid);
}
}
std::set<uint256> CWallet::GetNullifiers()
{
std::set<uint256> nullifierSet;
for (const auto & txPair : mapWallet) {
// Sapling
for (const auto & noteDataPair : txPair.second.mapSaplingNoteData) {
auto & noteData = noteDataPair.second;
auto & nullifier = noteData.nullifier;
if (nullifier) {
nullifierSet.insert(nullifier.get());
}
}
}
return nullifierSet;
}
int64_t CWallet::NullifierCount()
{
LOCK(cs_wallet);
if(fZdebug) {
}
return pcoinsTip->getNullifiers().size();
}
void CWallet::ClearNoteWitnessCache()
{
LOCK(cs_wallet);
int notes = 0;
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
for (mapSaplingNoteData_t::value_type& item : wtxItem.second.mapSaplingNoteData) {
item.second.witnesses.clear();
item.second.witnessHeight = -1;
notes++;
}
}
LogPrintf("%s: Cleared witness data from %d wallet items and %d SaplingNotes\n", __func__, mapWallet.size(), notes);
}
void CWallet::DecrementNoteWitnesses(const CBlockIndex* pindex)
{
extern int32_t HUSH_REWIND;
LOCK2(cs_main, cs_wallet);
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
//Sapling
for (auto& item : wtxItem.second.mapSaplingNoteData) {
auto* nd = &(item.second);
if (nd->nullifier && pwalletMain->GetSaplingSpendDepth(*item.second.nullifier) <= WITNESS_CACHE_SIZE) {
// Only decrement witnesses that are not above the current height
if (nd->witnessHeight <= pindex->GetHeight()) {
//PART B1: a rolled-back note must re-validate on reconnect (flag is in-memory only).
nd->witnessRootValidated = false;
if (nd->witnesses.size() > 1) {
// indexHeight is the height of the block being removed, so
// the new witness cache height is one below it.
nd->witnesses.pop_front();
nd->witnessHeight = pindex->GetHeight() - 1;
} else {
//PART B1: with only the base witness left we cannot pop_front without emptying
//the cache, but we must NOT leave witnessHeight stranded above the disconnected
//tip (that high-height/stale-witness state is the desync originator). Force a
//clean reseed on reconnect instead of lying about the height. (Inlined because
//ClearSingleNoteWitnessCache is defined later in this file.)
nd->witnesses.clear();
nd->witnessHeight = -1;
nd->witnessRootValidated = false;
}
}
}
}
}
assert(HUSH_REWIND != 0 || WITNESS_CACHE_SIZE != _COINBASE_MATURITY+10);
}
template<typename NoteData>
void ClearSingleNoteWitnessCache(NoteData* nd)
{
nd->witnesses.clear();
nd->witnessHeight = -1;
nd->witnessRootValidated = false;
}
int CWallet::SaplingWitnessMinimumHeight(const uint256& nullifier, int nWitnessHeight, int nMinimumHeight)
{
if (GetSaplingSpendDepth(nullifier) <= WITNESS_CACHE_SIZE) {
nMinimumHeight = min(nWitnessHeight, nMinimumHeight);
}
return nMinimumHeight;
}
int CWallet::VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessOnly)
{
LOCK2(cs_main, cs_wallet);
int nWitnessTxIncrement = 0;
int nWitnessTotalTxCount = mapWallet.size();
int nMinimumHeight = pindex->GetHeight();
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
nWitnessTxIncrement += 1;
if (wtxItem.second.mapSaplingNoteData.empty())
continue;
if (wtxItem.second.GetDepthInMainChain() > 0) {
auto wtxHash = wtxItem.second.GetHash();
int wtxHeight = mapBlockIndex[wtxItem.second.hashBlock]->GetHeight();
for (mapSaplingNoteData_t::value_type& item : wtxItem.second.mapSaplingNoteData) {
auto op = item.first;
auto* nd = &(item.second);
CBlockIndex* pblockindex;
uint256 blockRoot;
uint256 witnessRoot;
if (!nd->nullifier)
::ClearSingleNoteWitnessCache(nd);
if (!nd->witnesses.empty() && nd->witnessHeight > 0) {
//Skip all functions for validated witness while witness only = true
if (nd->witnessRootValidated && witnessOnly)
continue;
//Skip Validation when witness root has been validated
if (nd->witnessRootValidated) {
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
continue;
}
//PART A: a witness whose height is at/above the build height must NOT be blindly trusted.
//Validate its root against the canonical sapling root at witnessHeight when that block is
//on the active chain. Match -> safe to skip. Mismatch (witnessHeight advanced past the real
//witness state = the desync signature) -> fall through to ClearSingleNoteWitnessCache + reseed.
if (nd->witnessHeight > pindex->GetHeight() - 1) {
CBlockIndex* whIndex = chainActive[nd->witnessHeight];
if (whIndex == NULL) {
//witnessHeight strictly above the active chain (transient catch-up): cannot validate yet
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
continue;
}
if (nd->witnesses.front().root() == whIndex->hashFinalSaplingRoot) {
nd->witnessRootValidated = true;
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
continue;
}
//root mismatch on the active chain -> desynced; fall through to rebuild below
}
//Validate the witness at the witness height
witnessRoot = nd->witnesses.front().root();
pblockindex = chainActive[nd->witnessHeight];
blockRoot = pblockindex->hashFinalSaplingRoot;
if (witnessRoot == blockRoot) {
nd->witnessRootValidated = true;
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
continue;
}
}
//Clear witness Cache for all other scenarios
pblockindex = chainActive[wtxHeight];
::ClearSingleNoteWitnessCache(nd);
LogPrintf("%s: Setting Initial Sapling Witness for tx %s, %i of %i\n", __func__, wtxHash.ToString(), nWitnessTxIncrement, nWitnessTotalTxCount);
SaplingMerkleTree saplingTree;
blockRoot = pblockindex->pprev->hashFinalSaplingRoot;
pcoinsTip->GetSaplingAnchorAt(blockRoot, saplingTree);
//Cycle through blocks and transactions building sapling tree until the commitment needed is reached
const CBlock* pblock;
CBlock block;
if (!ReadBlockFromDisk(block, pblockindex, 1)) {
throw std::runtime_error(
strprintf("Cannot read block height %d (%s) from disk", pindex->GetHeight(), pindex->GetBlockHash().GetHex()));
}
pblock = &block;
for (const CTransaction& tx : block.vtx) {
auto hash = tx.GetHash();
// Sapling
for (uint32_t i = 0; i < tx.vShieldedOutput.size(); i++) {
const uint256& note_commitment = tx.vShieldedOutput[i].cm;
// Increment existing witness until the end of the block
if (!nd->witnesses.empty()) {
nd->witnesses.front().append(note_commitment);
}
//Only needed for intial witness
if (nd->witnesses.empty()) {
saplingTree.append(note_commitment);
// If this is our note, witness it
if (hash == wtxHash) {
SaplingOutPoint outPoint {hash, i};
if (op == outPoint) {
nd->witnesses.push_front(saplingTree.witness());
}
}
}
}
}
nd->witnessHeight = pblockindex->GetHeight();
UpdateSaplingNullifierNoteMapWithTx(wtxItem.second);
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
}
}
}
if(fZdebug)
LogPrintf("%s: nMinimumHeight=%d\n",__func__, nMinimumHeight);
return nMinimumHeight;
}
void CWallet::BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly)
{
LOCK2(cs_main, cs_wallet);
// The Phase-1 loop below walks the ACTIVE chain (chainActive.Next) and sizes blockCms from
// pindex->GetHeight(), terminating only on pbi==pindex. If pindex was reorged OFF the active
// chain (a reorg landed while ThreadNotifyWallets drained its connect backlog with cs_main
// released), the loop never reaches pindex and, once the active tip passes pindex's height, it
// writes blockCms[h-startHeight] out of bounds -> heap overflow. Rebuilding witnesses for an
// abandoned block is meaningless; the ChainTip for the new active tip re-drives this. cs_main is
// held for the whole function, so this check cannot race the loop below.
if (pindex != chainActive[pindex->GetHeight()]) {
if (fZdebug)
LogPrintf("%s: pindex height=%d not on active chain (reorg); skipping witness rebuild\n",
__func__, pindex->GetHeight());
return;
}
int startHeight = VerifyAndSetInitialWitness(pindex, witnessOnly) + 1;
if (startHeight > pindex->GetHeight() || witnessOnly) {
return;
}
if(fZdebug)
LogPrintf("%s: startHeight=%d, tip=%d\n", __func__, startHeight, chainActive.Height());
// Tier 1 optimization: build the set of notes that still need extension ONCE instead of
// rescanning all of mapWallet for every block. A note's gate conditions (nullifier present,
// spend depth <= WITNESS_CACHE_SIZE, tx confirmed) are invariant across this loop (the active
// chain is fixed under cs_main), so they are evaluated once here rather than per block. A note
// becomes "active" at the block where witnessHeight == GetHeight()-1 (exactly the original
// per-block gate) and is then extended every subsequent block, so the produced witnesses are
// byte-for-byte identical to the original full-rescan implementation -- only the bookkeeping
// cost changes from O(blocks * walletSize) to O(blocks + activeNotes).
struct PendingNote { int startWitnessHeight; SaplingNoteData* nd; };
std::vector<PendingNote> pending;
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
if (wtxItem.second.mapSaplingNoteData.empty())
continue;
if (wtxItem.second.GetDepthInMainChain() <= 0)
continue;
for (mapSaplingNoteData_t::value_type& item : wtxItem.second.mapSaplingNoteData) {
SaplingNoteData* nd = &(item.second);
if (!nd->nullifier)
continue;
if (nd->witnesses.empty()) // cannot extend (front() would be UB); the original gate also never matched these in practice
continue;
if (GetSaplingSpendDepth(*nd->nullifier) > WITNESS_CACHE_SIZE)
continue;
// Only notes that still lag the build target need extension. The lowest such witnessHeight
// is exactly startHeight-1 (startHeight = nMinimumHeight+1 from SaplingWitnessMinimumHeight).
if (nd->witnessHeight >= startHeight - 1 && nd->witnessHeight <= pindex->GetHeight() - 1)
pending.push_back({ nd->witnessHeight, nd });
}
}
std::sort(pending.begin(), pending.end(),
[](const PendingNote& a, const PendingNote& b) { return a.startWitnessHeight < b.startWitnessHeight; });
// Phase 1 (serial, main thread under cs_main/cs_wallet): extract the per-block Sapling
// commitments for [startHeight, tip] into memory. This is the only part touching chain/disk;
// profiling showed it is ~1% of rebuild time. ~9MB for a full-chain range.
const int tipHeight = pindex->GetHeight();
const int rangeLen = tipHeight - startHeight + 1;
std::vector<std::vector<uint256>> blockCms(rangeLen > 0 ? rangeLen : 0);
int64_t tRead = 0;
{
int64_t r0 = GetTimeMicros();
CBlockIndex* pbi = chainActive[startHeight];
while (pbi) {
if (ShutdownRequested()) {
LogPrintf("%s: shutdown requested, aborting witness rebuild\n", __func__);
return;
}
if (pwalletMain->fAbortRescan) {
// Do NOT clear fRescanning here: a witness rebuild is not a rescan, and clearing it from
// this path desynchronises the flag from ScanForWalletTransactions, which owns it.
LogPrintf("%s: rescan aborted during witness rebuild\n", __func__);
return;
}
int h = pbi->GetHeight();
if (h % 5000 == 0 && h < tipHeight - 5)
LogPrintf("Reading blocks for witness rebuild: %d / %d\n", h - startHeight, rangeLen);
CBlock block;
if (!ReadBlockFromDisk(block, pbi, 1)) {
throw std::runtime_error(strprintf("Cannot read block height %d from disk", h));
}
std::vector<uint256>& cms = blockCms[h - startHeight];
for (const CTransaction& tx : block.vtx)
for (uint32_t i = 0; i < tx.vShieldedOutput.size(); i++)
cms.push_back(tx.vShieldedOutput[i].cm);
if (pbi == pindex) break;
pbi = chainActive.Next(pbi);
}
tRead = GetTimeMicros() - r0;
}
// Phase 2 (parallel): each note's witness extension is independent, so partition the lagging
// notes across worker threads. Workers touch ONLY their own notes' witness lists plus the
// read-only commitment cache -- no locks, no chain access -- while the main thread keeps
// cs_main/cs_wallet held throughout. Round-robin assignment over the start-sorted `pending`
// spreads the long (low-start) notes across threads. Produces witnesses byte-identical to the
// serial path: every note is extended over exactly its [witnessHeight+1, tip] block range, in
// order, appending each block's commitments.
std::vector<SaplingNoteData*> work;
work.reserve(pending.size());
for (const PendingNote& p : pending) work.push_back(p.nd);
// Only a substantial bulk rebuild (e.g. a one-time post-upgrade repair) is worth parallelizing
// and logging; routine 1-block tip extension runs serially to avoid per-block thread-spawn
// overhead and log spam.
const bool bulkRebuild = (rangeLen > 100);
int nPar = (int)GetArg("-witnessbuildthreads", 0);
if (nPar <= 0) nPar = (int)std::thread::hardware_concurrency();
if (nPar <= 0) nPar = 1;
if (nPar > (int)work.size()) nPar = (int)work.size();
if (nPar < 1) nPar = 1;
if (!bulkRebuild) nPar = 1;
std::atomic<bool> failed(false);
std::mutex failMtx;
std::string failMsg;
const int CACHE = (int)WITNESS_CACHE_SIZE;
// Tier 2b: advance one witness in place for the deep part (no per-block heap clone), then
// materialize only the final CACHE snapshots. -witnessfastrebuild=0 forces the reference
// clone-every-block path for A/B verification.
bool fastRebuild = GetBoolArg("-witnessfastrebuild", true);
auto worker = [&](int tid) {
try {
for (size_t k = (size_t)tid; k < work.size(); k += (size_t)nPar) {
SaplingNoteData* nd = work[k];
int startH = nd->witnessHeight;
int nBlocks = tipHeight - startH;
if (nBlocks <= 0)
continue;
if (!fastRebuild || nBlocks <= CACHE) {
// Reference / shallow path: clone every block (preserves pre-existing older snapshots).
for (int h = startH + 1; h <= tipHeight; h++) {
nd->witnesses.push_front(nd->witnesses.front());
while ((int)nd->witnesses.size() > CACHE)
nd->witnesses.pop_back();
const std::vector<uint256>& cms = blockCms[h - startHeight];
for (size_t c = 0; c < cms.size(); c++)
nd->witnesses.front().append(cms[c]);
nd->witnessHeight = h;
}
} else {
// Deep fast path: advance ONE witness in place through [startH+1, tipHeight-CACHE]
// with no per-block clone, then build only the final CACHE snapshots. The reference
// loop pops all but the last CACHE snapshots, so the resulting deque is identical
// ([W@tip .. W@(tip-CACHE+1)]), but with ~CACHE heap allocations instead of ~nBlocks.
int deepEnd = tipHeight - CACHE;
{
SaplingWitness& w = nd->witnesses.front();
for (int h = startH + 1; h <= deepEnd; h++) {
const std::vector<uint256>& cms = blockCms[h - startHeight];
for (size_t c = 0; c < cms.size(); c++)
w.append(cms[c]);
}
}
// Drop pre-existing older snapshots; keep only the advanced front (W@deepEnd).
while (nd->witnesses.size() > 1)
nd->witnesses.pop_back();
// Materialize the last CACHE snapshots (heights deepEnd+1 .. tip).
for (int h = deepEnd + 1; h <= tipHeight; h++) {
nd->witnesses.push_front(nd->witnesses.front());
const std::vector<uint256>& cms = blockCms[h - startHeight];
for (size_t c = 0; c < cms.size(); c++)
nd->witnesses.front().append(cms[c]);
}
while ((int)nd->witnesses.size() > CACHE)
nd->witnesses.pop_back();
nd->witnessHeight = tipHeight;
}
}
} catch (const std::exception& e) {
std::lock_guard<std::mutex> lk(failMtx);
if (failMsg.empty()) failMsg = e.what();
failed = true;
} catch (...) {
failed = true;
}
};
int64_t e0 = GetTimeMicros();
if (nPar <= 1) {
worker(0);
} else {
std::vector<std::thread> threads;
threads.reserve(nPar);
for (int t = 0; t < nPar; t++) threads.emplace_back(worker, t);
for (std::thread& th : threads) th.join();
}
int64_t tExtend = GetTimeMicros() - e0;
if (failed)
throw std::runtime_error(std::string("Witness rebuild worker failed: ") + (failMsg.empty() ? "unknown" : failMsg));
// Latch the rebuilt notes as validated so they are not re-validated and reseeded on every
// subsequent block connect. Without this, any note whose witness cannot be reconstructed to
// the canonical anchor (e.g. legacy corruption) would be reseeded and fully replayed on every
// block forever. A note whose rebuilt root still disagrees with the canonical finalsaplingroot
// is unrecoverable here: it is left flagged (the GetSaplingNoteWitnesses majority-anchor guard
// skips it for spends) and reported, rather than spun on indefinitely. witnessRootValidated is
// in-memory only, so a fresh validation pass still runs on each restart and after any reorg
// (DecrementNoteWitnesses clears it), keeping the heal self-correcting.
if (!work.empty()) {
const uint256& canonicalRoot = pindex->hashFinalSaplingRoot;
int nUnrecoverable = 0;
for (SaplingNoteData* nd : work) {
if (nd->witnesses.empty() || nd->witnesses.front().root() != canonicalRoot)
nUnrecoverable++;
nd->witnessRootValidated = true;
}
if (bulkRebuild) {
LogPrintf("%s: rebuilt %u note witness cache(s) to height %d in %ldms using %d thread(s)%s\n",
__func__, (unsigned)work.size(), tipHeight, (long)((tRead + tExtend) / 1000), nPar,
nUnrecoverable
? strprintf(" [WARNING: %d note(s) could not be rebuilt to the canonical anchor and were skipped]", nUnrecoverable).c_str()
: "");
}
}
}
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
{
if (IsCrypted())
return false;
CKeyingMaterial vMasterKey;
vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
GetRandBytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
CMasterKey kMasterKey;
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
GetRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
CCrypter crypter;
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2;
if (kMasterKey.nDeriveIterations < 25000)
kMasterKey.nDeriveIterations = 25000;
LogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod))
return false;
if (!crypter.Encrypt(vMasterKey, kMasterKey.vchCryptedKey))
return false;
{
LOCK(cs_wallet);
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
if (fFileBacked)
{
assert(!pwalletdbEncryption);
pwalletdbEncryption = new CWalletDB(strWalletFile);
if (!pwalletdbEncryption->TxnBegin()) {
delete pwalletdbEncryption;
pwalletdbEncryption = NULL;
return false;
}
pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
}
if (!EncryptKeys(vMasterKey))
{
if (fFileBacked) {
pwalletdbEncryption->TxnAbort();
delete pwalletdbEncryption;
}
// We now probably have half of our keys encrypted in memory, and half not...
// die and let the user reload the unencrypted wallet.
assert(false);
}
// Encryption was introduced in version 0.4.0
SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true);
if (fFileBacked)
{
if (!pwalletdbEncryption->TxnCommit()) {
delete pwalletdbEncryption;
// We now have keys encrypted in memory, but not on disk...
// die to avoid confusion and let the user reload the unencrypted wallet.
assert(false);
}
delete pwalletdbEncryption;
pwalletdbEncryption = NULL;
}
Lock();
Unlock(strWalletPassphrase);
NewKeyPool();
Lock();
// Need to completely rewrite the wallet file; if we don't, bdb might keep
// bits of the unencrypted private key in slack space in the database file.
CDB::Rewrite(strWalletFile);
}
NotifyStatusChanged(this);
return true;
}
int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb)
{
AssertLockHeld(cs_wallet); // nOrderPosNext
int64_t nRet = nOrderPosNext++;
if (pwalletdb) {
pwalletdb->WriteOrderPosNext(nOrderPosNext);
} else {
CWalletDB(strWalletFile).WriteOrderPosNext(nOrderPosNext);
}
return nRet;
}
CWallet::TxItems CWallet::OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount)
{
AssertLockHeld(cs_wallet); // mapWallet
CWalletDB walletdb(strWalletFile);
// First: get all CWalletTx and CAccountingEntry into a sorted-by-order multimap.
TxItems txOrdered;
// Note: maintaining indices in the database of (account,time) --> txid and (account, time) --> acentry
// would make this much faster for applications that do this a lot.
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
CWalletTx* wtx = &((*it).second);
txOrdered.insert(make_pair(wtx->nOrderPos, TxPair(wtx, (CAccountingEntry*)0)));
}
acentries.clear();
walletdb.ListAccountCreditDebit(strAccount, acentries);
BOOST_FOREACH(CAccountingEntry& entry, acentries)
{
txOrdered.insert(make_pair(entry.nOrderPos, TxPair((CWalletTx*)0, &entry)));
}
return txOrdered;
}
void CWallet::MarkDirty()
{
{
LOCK(cs_wallet);
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
item.second.MarkDirty();
}
}
/**
* Ensure that every note in the wallet (for which we possess a spending key)
* has a cached nullifier.
*/
bool CWallet::UpdateNullifierNoteMap()
{
{
LOCK(cs_wallet);
if (IsLocked())
return false;
ZCNoteDecryption dec;
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
// TODO: Sapling. This method is only called from RPC walletpassphrase, which is currently unsupported
// as RPC encryptwallet is hidden behind two flags: -developerencryptwallet -experimentalfeatures
UpdateNullifierNoteMapWithTx(wtxItem.second);
}
}
return true;
}
/**
* Update mapSaplingNullifiersToNotes
* with the cached nullifiers in this tx.
*/
void CWallet::UpdateNullifierNoteMapWithTx(const CWalletTx& wtx)
{
{
LOCK(cs_wallet);
for (const mapSaplingNoteData_t::value_type& item : wtx.mapSaplingNoteData) {
if (item.second.nullifier) {
mapSaplingNullifiersToNotes[*item.second.nullifier] = item.first;
}
}
}
}
/**
* Update mapSaplingNullifiersToNotes, computing the nullifier from a cached witness if necessary.
*/
void CWallet::UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx) {
LOCK(cs_wallet);
for (mapSaplingNoteData_t::value_type &item : wtx.mapSaplingNoteData) {
SaplingOutPoint op = item.first;
SaplingNoteData nd = item.second;
if (nd.witnesses.empty()) {
// If there are no witnesses, erase the nullifier and associated mapping.
if (item.second.nullifier) {
mapSaplingNullifiersToNotes.erase(item.second.nullifier.get());
}
item.second.nullifier = boost::none;
}
else {
uint64_t position = nd.witnesses.front().position();
// Skip if we only have incoming viewing key
if (mapSaplingFullViewingKeys.count(nd.ivk) != 0) {
SaplingFullViewingKey fvk = mapSaplingFullViewingKeys.at(nd.ivk);
OutputDescription output = wtx.vShieldedOutput[op.n];
auto optPlaintext = SaplingNotePlaintext::decrypt(output.encCiphertext, nd.ivk, output.ephemeralKey, output.cm);
if (!optPlaintext) {
// An item in mapSaplingNoteData must have already been successfully decrypted,
// otherwise the item would not exist in the first place.
assert(false);
}
auto optNote = optPlaintext.get().note(nd.ivk);
if (!optNote) {
assert(false);
}
auto optNullifier = optNote.get().nullifier(fvk, position);
if (!optNullifier) {
// This should not happen. If it does, maybe the position has been corrupted or miscalculated?
assert(false);
}
uint256 nullifier = optNullifier.get();
mapSaplingNullifiersToNotes[nullifier] = op;
item.second.nullifier = nullifier;
}
}
}
}
/**
* Iterate over transactions in a block and update the cached Sapling nullifiers
* for transactions which belong to the wallet.
*/
void CWallet::UpdateNullifierNoteMapForBlock(const CBlock *pblock) {
LOCK(cs_wallet);
for (const CTransaction& tx : pblock->vtx) {
auto hash = tx.GetHash();
bool txIsOurs = mapWallet.count(hash);
if (txIsOurs) {
UpdateSaplingNullifierNoteMapWithTx(mapWallet[hash]);
}
}
}
bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb)
{
uint256 hash = wtxIn.GetHash();
if (fFromLoadWallet)
{
mapWallet[hash] = wtxIn;
mapWallet[hash].BindWallet(this);
UpdateNullifierNoteMapWithTx(mapWallet[hash]);
AddToSpends(hash);
}
else
{
LOCK(cs_wallet);
// Inserts only if not already there, returns tx inserted or tx found
pair<map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.insert(make_pair(hash, wtxIn));
CWalletTx& wtx = (*ret.first).second;
wtx.BindWallet(this);
UpdateNullifierNoteMapWithTx(wtx);
bool fInsertedNew = ret.second;
if (fInsertedNew)
{
wtx.nTimeReceived = GetTime();
wtx.nOrderPos = IncOrderPosNext(pwalletdb);
wtx.nTimeSmart = wtx.nTimeReceived;
if (!wtxIn.hashBlock.IsNull())
{
if (mapBlockIndex.count(wtxIn.hashBlock))
{
int64_t latestNow = wtx.nTimeReceived;
int64_t latestEntry = 0;
{
// Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future
int64_t latestTolerated = latestNow + 300;
std::list<CAccountingEntry> acentries;
TxItems txOrdered = OrderedTxItems(acentries);
for (TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
{
CWalletTx *const pwtx = (*it).second.first;
if (pwtx == &wtx)
continue;
CAccountingEntry *const pacentry = (*it).second.second;
int64_t nSmartTime;
if (pwtx)
{
nSmartTime = pwtx->nTimeSmart;
if (!nSmartTime)
nSmartTime = pwtx->nTimeReceived;
}
else
nSmartTime = pacentry->nTime;
if (nSmartTime <= latestTolerated)
{
latestEntry = nSmartTime;
if (nSmartTime > latestNow)
latestNow = nSmartTime;
break;
}
}
}
int64_t blocktime = mapBlockIndex[wtxIn.hashBlock]->GetBlockTime();
wtx.nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
}
else
LogPrintf("AddToWallet(): found %s in block %s not in index\n",
wtxIn.GetHash().ToString(),
wtxIn.hashBlock.ToString());
}
AddToSpends(hash);
}
bool fUpdated = false;
if (!fInsertedNew)
{
// Merge
if (!wtxIn.hashBlock.IsNull() && wtxIn.hashBlock != wtx.hashBlock)
{
wtx.hashBlock = wtxIn.hashBlock;
fUpdated = true;
}
if (wtxIn.nIndex != -1 && (wtxIn.vMerkleBranch != wtx.vMerkleBranch || wtxIn.nIndex != wtx.nIndex))
{
wtx.vMerkleBranch = wtxIn.vMerkleBranch;
wtx.nIndex = wtxIn.nIndex;
fUpdated = true;
}
if (UpdatedNoteData(wtxIn, wtx)) {
fUpdated = true;
}
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
{
wtx.fFromMe = wtxIn.fFromMe;
fUpdated = true;
}
}
//// debug print
LogPrintf("AddToWallet %s at height %d %s%s\n", wtxIn.GetHash().ToString(), hush_blockheight(wtxIn.hashBlock), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
// Write to disk
if (fInsertedNew || fUpdated)
if (!wtx.WriteToDisk(pwalletdb))
return false;
// Break debit/credit balance caches:
wtx.MarkDirty();
// Notify UI of new or updated transaction
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
// notify an external script when a wallet transaction comes in or is updated
std::string strCmd = GetArg("-walletnotify", "");
if ( !strCmd.empty())
{
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
boost::thread t(runCommand, strCmd); // thread runs free
}
}
return true;
}
bool CWallet::UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx)
{
bool unchangedSaplingFlag = (wtxIn.mapSaplingNoteData.empty() || wtxIn.mapSaplingNoteData == wtx.mapSaplingNoteData);
if (!unchangedSaplingFlag) {
auto tmp = wtxIn.mapSaplingNoteData;
// Ensure we keep any cached witnesses we may already have
for (const std::pair <SaplingOutPoint, SaplingNoteData> nd : wtx.mapSaplingNoteData) {
if (tmp.count(nd.first) && nd.second.witnesses.size() > 0) {
tmp.at(nd.first).witnesses.assign(
nd.second.witnesses.cbegin(), nd.second.witnesses.cend());
//PART B2: only carry over witnessHeight TOGETHER with the witnesses it describes.
//Copying it unconditionally (when witnesses are NOT copied) advances the height past
//the actual witness state for a whole tx's notes at once = the batch desync originator.
tmp.at(nd.first).witnessHeight = nd.second.witnessHeight;
}
}
// Now copy over the updated note data
wtx.mapSaplingNoteData = tmp;
}
return !unchangedSaplingFlag;
}
/**
* Add a transaction to the wallet, or update it.
* pblock is optional, but should be provided if the transaction is known to be in a block.
* If fUpdate is true, existing transactions will be updated.
*/
bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate)
{
if(fDebug)
fprintf(stderr,"%s: tx=%s\n", __func__, tx.GetHash().ToString().c_str() );
{
AssertLockHeld(cs_wallet);
if ( tx.IsCoinBase() && tx.vout[0].nValue == 0 )
return false;
bool fExisted = mapWallet.count(tx.GetHash()) != 0;
if (fExisted && !fUpdate) return false;
auto saplingNoteDataAndAddressesToAdd = FindMySaplingNotes(tx);
auto saplingNoteData = saplingNoteDataAndAddressesToAdd.first;
auto addressesToAdd = saplingNoteDataAndAddressesToAdd.second;
for (const auto &addressToAdd : addressesToAdd) {
if (!AddSaplingIncomingViewingKey(addressToAdd.second, addressToAdd.first)) {
return false;
}
}
static std::string NotaryAddress; static bool didinit;
if ( !didinit && NotaryAddress.empty() && NOTARY_PUBKEY33[0] != 0 ) {
didinit = true;
char Raddress[64];
pubkey2addr((char *)Raddress,(uint8_t *)NOTARY_PUBKEY33);
NotaryAddress.assign(Raddress);
vAllowListAddress = mapMultiArgs["-allowlistaddress"];
if ( !vAllowListAddress.empty() )
{
LogPrintf("Activated Wallet Filter \n Notary Address: %s \n Adding allowlist address's:\n", NotaryAddress.c_str());
for ( auto wladdr : vAllowListAddress )
LogPrintf(" %s\n", wladdr.c_str());
}
}
if (fExisted || IsMine(tx) || IsFromMe(tx) || saplingNoteData.size() > 0) {
// wallet filter for notary nodes. Enables by setting -allowlistaddress= as startup param or in conf file (works same as -addnode but with taddr)
if ( !tx.IsCoinBase() && !vAllowListAddress.empty() && !NotaryAddress.empty() ) {
int numvinIsOurs = 0, numvinIsAllowList = 0;
for (size_t i = 0; i < tx.vin.size(); i++)
{
uint256 hash; CTransaction txin; CTxDestination address;
if ( myGetTransaction(tx.vin[i].prevout.hash,txin,hash) && ExtractDestination(txin.vout[tx.vin[i].prevout.n].scriptPubKey, address) )
{
if ( CBitcoinAddress(address).ToString() == NotaryAddress )
numvinIsOurs++;
for ( auto wladdr : vAllowListAddress )
{
if ( CBitcoinAddress(address).ToString() == wladdr )
{
numvinIsAllowList++;
}
}
}
}
// Now we know if it was a tx sent to us, by either a allowlisted address, or ourself.
if ( numvinIsOurs != 0 )
LogPrintf("We sent from address: %s vins: %d\n",NotaryAddress.c_str(),numvinIsOurs);
if ( numvinIsOurs == 0 && numvinIsAllowList == 0 )
return false;
}
CWalletTx wtx(this,tx);
if (saplingNoteData.size() > 0) {
wtx.SetSaplingNoteData(saplingNoteData);
}
// Get merkle branch if transaction was found in a block
if (pblock)
wtx.SetMerkleBranch(*pblock);
// Do not flush the wallet here for performance reasons
// this is safe, as in case of a crash, we rescan the necessary blocks on startup through our SetBestChain-mechanism
CWalletDB walletdb(strWalletFile, "r+", false);
return AddToWallet(wtx, false, &walletdb);
}
}
return false;
}
void CWallet::SyncTransaction(const CTransaction& tx, const CBlock* pblock)
{
LOCK(cs_wallet);
if(fDebug)
fprintf(stderr,"%s: tx=%s\n", __func__, tx.GetHash().ToString().c_str() );
if (!AddToWalletIfInvolvingMe(tx, pblock, true))
return; // Not one of ours
MarkAffectedTransactionsDirty(tx);
}
void CWallet::MarkAffectedTransactionsDirty(const CTransaction& tx)
{
if(fDebug)
fprintf(stderr,"%s: tx=%s\n", __func__, tx.GetHash().ToString().c_str() );
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be
// recomputed, also:
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
if (mapWallet.count(txin.prevout.hash))
mapWallet[txin.prevout.hash].MarkDirty();
}
for (const SpendDescription &spend : tx.vShieldedSpend) {
uint256 nullifier = spend.nullifier;
if (mapSaplingNullifiersToNotes.count(nullifier) &&
mapWallet.count(mapSaplingNullifiersToNotes[nullifier].hash)) {
mapWallet[mapSaplingNullifiersToNotes[nullifier].hash].MarkDirty();
}
}
if(fDebug)
fprintf(stderr,"%s: finished marking dirty transactions\n", __func__);
}
void CWallet::EraseFromWallet(const uint256 &hash)
{
if (!fFileBacked)
return;
{
LOCK(cs_wallet);
if (mapWallet.erase(hash))
CWalletDB(strWalletFile).EraseTx(hash);
}
LogPrintf("%s: erased txid %s\n", __func__, hash.ToString().c_str() );
return;
}
void CWallet::RescanWallet()
{
if (needsRescan)
{
CBlockIndex *start = chainActive.Height() > 0 ? chainActive[1] : NULL;
if (start)
ScanForWalletTransactions(start, true);
needsRescan = false;
}
}
/**
* Finds all output notes in the given transaction that have been sent to
* SaplingPaymentAddresses in this wallet.
*
* It should never be necessary to call this method with a CWalletTx, because
* the result of FindMySaplingNotes (for the addresses available at the time) will
* already have been cached in CWalletTx.mapSaplingNoteData.
*/
std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> CWallet::FindMySaplingNotes(const CTransaction &tx) const
{
LOCK(cs_SpendingKeyStore);
uint256 hash = tx.GetHash();
uint32_t nZouts = tx.vShieldedOutput.size();
if(fDebug && (nZouts > 0)) {
LogPrintf("%s: zouts=%d in tx=%s\n",__func__,nZouts, hash.ToString().c_str());
}
mapSaplingNoteData_t noteData;
SaplingIncomingViewingKeyMap viewingKeysToAdd;
// Protocol Spec: 4.19 Block Chain Scanning (Sapling)
for (uint32_t i = 0; i < nZouts; ++i) {
const OutputDescription output = tx.vShieldedOutput[i];
bool found = false;
for (auto it = mapSaplingFullViewingKeys.begin(); it != mapSaplingFullViewingKeys.end(); ++it) {
SaplingIncomingViewingKey ivk = it->first;
auto result = SaplingNotePlaintext::decrypt(output.encCiphertext, ivk, output.ephemeralKey, output.cm);
if (result) {
auto address = ivk.address(result.get().d);
if (address && mapSaplingIncomingViewingKeys.count(address.get()) == 0) {
viewingKeysToAdd[address.get()] = ivk;
}
// We don't cache the nullifier here as computing it requires knowledge of the note position
// in the commitment tree, which can only be determined when the transaction has been mined.
SaplingOutPoint op {hash, i};
SaplingNoteData nd;
nd.ivk = ivk;
noteData.insert(std::make_pair(op, nd));
found = true;
break;
}
}
if (!found) {
for (auto it = mapSaplingIncomingViewingKeys.begin(); it != mapSaplingIncomingViewingKeys.end(); ++it) {
SaplingIncomingViewingKey ivk = it-> second;
auto result = SaplingNotePlaintext::decrypt(output.encCiphertext, ivk, output.ephemeralKey, output.cm);
if (!result) {
continue;
}
// We don't cache the nullifier here as computing it requires knowledge of the note position
// in the commitment tree, which can only be determined when the transaction has been mined.
SaplingOutPoint op {hash, i};
SaplingNoteData nd;
nd.ivk = ivk;
noteData.insert(std::make_pair(op, nd));
break;
}
}
}
return std::make_pair(noteData, viewingKeysToAdd);
}
bool CWallet::IsSaplingNullifierFromMe(const uint256& nullifier) const
{
{
LOCK(cs_wallet);
if (mapSaplingNullifiersToNotes.count(nullifier) &&
mapWallet.count(mapSaplingNullifiersToNotes.at(nullifier).hash)) {
return true;
}
}
return false;
}
void CWallet::GetSaplingNoteWitnesses(std::vector<SaplingOutPoint> notes,
std::vector<boost::optional<SaplingWitness>>& witnesses,
uint256 &final_anchor)
{
LOCK(cs_wallet);
witnesses.clear();
witnesses.resize(notes.size());
// Pass 1: collect each note's most-recent cached witness and tally roots. Use find() so we do
// NOT default-construct mapWallet / mapSaplingNoteData entries (the original indexed
// mapWallet[note.hash] BEFORE its own count() guard, silently inserting empty entries).
std::vector<boost::optional<SaplingWitness>> cand(notes.size());
std::map<uint256, int> rootVotes;
for (size_t i = 0; i < notes.size(); i++) {
const SaplingOutPoint& note = notes[i];
auto wi = mapWallet.find(note.hash);
if (wi == mapWallet.end())
continue;
const mapSaplingNoteData_t& noteData = wi->second.mapSaplingNoteData;
auto ni = noteData.find(note);
if (ni == noteData.end() || ni->second.witnesses.empty())
continue;
cand[i] = ni->second.witnesses.front();
rootVotes[cand[i]->root()]++;
}
// Choose the anchor that the most witnesses agree on (robust even when the FIRST note is the
// desynced one - the original code blindly took the first note's root as the anchor).
boost::optional<uint256> anchor;
int bestVotes = 0;
for (const std::pair<uint256, int>& rv : rootVotes) {
if (rv.second > bestVotes) { bestVotes = rv.second; anchor = rv.first; }
}
// Pass 2: only emit witnesses whose root matches the common anchor. A desynced witness is left
// as boost::none rather than returned: handing the spend prover a witness whose root disagrees
// with the anchor guarantees a "Failed to build transaction". Note selection / callers skip
// notes that have no usable witness (see asyncrpcoperation_*: "Missing witness for Sapling note").
for (size_t i = 0; i < notes.size(); i++) {
if (cand[i] && anchor && cand[i]->root() == *anchor) {
witnesses[i] = cand[i];
} else {
if (cand[i])
LogPrintf("%s: note %s has a desynced witness (root=%s != anchor=%s); skipping it\n",
__func__, notes[i].hash.ToString().substr(0, 16).c_str(),
cand[i]->root().ToString().c_str(),
anchor ? anchor->ToString().c_str() : "none");
witnesses[i] = boost::none;
}
}
if (anchor)
final_anchor = *anchor;
}
isminetype CWallet::IsMine(const CTxIn &txin) const
{
{
LOCK(cs_wallet);
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.vout.size())
return (::IsMine(*this, prev.vout[txin.prevout.n].scriptPubKey));
}
}
return ISMINE_NO;
}
CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const
{
{
LOCK(cs_wallet);
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.vout.size())
if (::IsMine(*this, prev.vout[txin.prevout.n].scriptPubKey) & filter)
return prev.vout[txin.prevout.n].nValue;
}
}
return 0;
}
isminetype CWallet::IsMine(const CTxOut& txout) const
{
return ::IsMine(*this, txout.scriptPubKey);
}
CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) const
{
if (!MoneyRange(txout.nValue))
throw std::runtime_error("CWallet::GetCredit(): value out of range");
return ((IsMine(txout) & filter) ? txout.nValue : 0);
}
bool CWallet::IsChange(const CTxOut& txout) const
{
// TODO: fix handling of 'change' outputs. The assumption is that any
// payment to a script that is ours, but is not in the address book
// is change. That assumption is likely to break when we implement multisignature
// wallets that return change back into a multi-signature-protected address;
// a better way of identifying which outputs are 'the send' and which are
// 'the change' will need to be implemented (maybe extend CWalletTx to remember
// which output, if any, was change).
if (::IsMine(*this, txout.scriptPubKey))
{
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address))
return true;
LOCK(cs_wallet);
if (!mapAddressBook.count(address))
return true;
}
return false;
}
CAmount CWallet::GetChange(const CTxOut& txout) const
{
if (!MoneyRange(txout.nValue))
throw std::runtime_error("CWallet::GetChange(): value out of range");
return (IsChange(txout) ? txout.nValue : 0);
}
typedef vector<unsigned char> valtype;
unsigned int HaveKeys(const vector<valtype>& pubkeys, const CKeyStore& keystore);
bool CWallet::IsMine(const CTransaction& tx)
{
for (int i = 0; i < tx.vout.size(); i++)
{
if (IsMine(tx, i))
return true;
}
return false;
}
// special case handling for non-standard OP_RETURN script outputs, which need the transaction
// to determine ownership
isminetype CWallet::IsMine(const CTransaction& tx, uint32_t voutNum)
{
vector<valtype> vSolutions;
txnouttype whichType;
const CScript scriptPubKey = CScript(tx.vout[voutNum].scriptPubKey);
if (!Solver(scriptPubKey, whichType, vSolutions)) {
if (this->HaveWatchOnly(scriptPubKey))
return ISMINE_WATCH_ONLY;
return ISMINE_NO;
}
CKeyID keyID;
CScriptID scriptID;
CScript subscript;
int voutNext = voutNum + 1;
switch (whichType)
{
case TX_NONSTANDARD:
case TX_NULL_DATA:
break;
case TX_PUBKEY:
keyID = CPubKey(vSolutions[0]).GetID();
if (this->HaveKey(keyID))
return ISMINE_SPENDABLE;
break;
case TX_PUBKEYHASH:
keyID = CKeyID(uint160(vSolutions[0]));
if (this->HaveKey(keyID))
return ISMINE_SPENDABLE;
break;
case TX_SCRIPTHASH:
scriptID = CScriptID(uint160(vSolutions[0]));
//TODO: evaluate whether this CLTV timelock handling is needed on DragonX
if (this->GetCScript(scriptID, subscript))
{
// if this is a CLTV, handle it differently
if (subscript.IsCheckLockTimeVerify())
{
return (::IsMine(*this, subscript));
}
else
{
isminetype ret = ::IsMine(*this, subscript);
if (ret == ISMINE_SPENDABLE)
return ret;
}
}
else if (tx.vout.size() > (voutNext = voutNum + 1) &&
tx.vout[voutNext].scriptPubKey.size() > 7 &&
tx.vout[voutNext].scriptPubKey[0] == OP_RETURN)
{
// get the opret script from next vout, verify that the front is CLTV and hash matches
// if so, remove it and use the solver
opcodetype op;
std::vector<uint8_t> opretData;
CScript::const_iterator it = tx.vout[voutNext].scriptPubKey.begin() + 1;
if (tx.vout[voutNext].scriptPubKey.GetOp2(it, op, &opretData))
{
if (opretData.size() > 0 && opretData[0] == OPRETTYPE_TIMELOCK)
{
CScript opretScript = CScript(opretData.begin() + 1, opretData.end());
if (CScriptID(opretScript) == scriptID &&
opretScript.IsCheckLockTimeVerify())
{
// if we find that this is ours, we need to add this script to the wallet,
// and we can then recognize this transaction
isminetype t = ::IsMine(*this, opretScript);
if (t != ISMINE_NO)
{
this->AddCScript(opretScript);
}
return t;
}
}
}
}
break;
case TX_MULTISIG:
// Only consider transactions "mine" if we own ALL the
// keys involved. Multi-signature transactions that are
// partially owned (somebody else has a key that can spend
// them) enable spend-out-from-under-you attacks, especially
// in shared-wallet situations.
vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1);
if (HaveKeys(keys, *this) == keys.size())
return ISMINE_SPENDABLE;
break;
}
if (this->HaveWatchOnly(scriptPubKey))
return ISMINE_WATCH_ONLY;
return ISMINE_NO;
}
bool CWallet::IsFromMe(const CTransaction& tx) const
{
if (GetDebit(tx, ISMINE_ALL) > 0) {
return true;
}
for (const SpendDescription &spend : tx.vShieldedSpend) {
if (IsSaplingNullifierFromMe(spend.nullifier)) {
return true;
}
}
return false;
}
CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) const
{
CAmount nDebit = 0;
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
nDebit += GetDebit(txin, filter);
if (!MoneyRange(nDebit))
throw std::runtime_error("CWallet::GetDebit(): value out of range");
}
return nDebit;
}
CAmount CWallet::GetCredit(const CTransaction& tx, int32_t voutNum, const isminefilter& filter) const
{
if (voutNum >= tx.vout.size() || !MoneyRange(tx.vout[voutNum].nValue))
throw std::runtime_error("CWallet::GetCredit(): value out of range");
return ((IsMine(tx.vout[voutNum]) & filter) ? tx.vout[voutNum].nValue : 0);
}
CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const
{
CAmount nCredit = 0;
for (int i = 0; i < tx.vout.size(); i++)
{
nCredit += GetCredit(tx, i, filter);
}
return nCredit;
}
CAmount CWallet::GetChange(const CTransaction& tx) const
{
CAmount nChange = 0;
BOOST_FOREACH(const CTxOut& txout, tx.vout)
{
nChange += GetChange(txout);
if (!MoneyRange(nChange))
throw std::runtime_error("CWallet::GetChange(): value out of range");
}
return nChange;
}
bool CWallet::IsHDFullyEnabled() const
{
// Both Sapling and transparent addresses are HD when transparent HD is on.
return IsHDTransparentEnabled();
}
bool CWallet::IsHDTransparentEnabled() const
{
// Transparent keys are HD-derived when the wallet has an HD seed and the
// feature is enabled (default on). Legacy wallets keep any pre-existing
// random t-keys; only newly generated keys become HD (and those old random
// keys are NOT seed-recoverable, so wallet.dat backups remain necessary).
return !hdChain.seedFp.IsNull() && GetBoolArg("-hdtransparent", true);
}
void CWallet::GenerateNewSeed()
{
LOCK(cs_wallet);
int64_t nCreationTime = GetTime();
// Opt-in: create the wallet from a fresh BIP39 mnemonic so its 24 words can
// be exported (z_exportmnemonic) and used in SilentDragonXLite.
//
// NO SILENT FALLBACK. Falling back to a random seed here produced a wallet
// that looks mnemonic-capable but whose words can never be exported
// (z_exportmnemonic refuses non-mnemonic wallets, rpcdump.cpp:1031+) and
// that no seed phrase can restore. A user who asked for -usemnemonic must
// get that or a hard failure.
if (GetBoolArg("-usemnemonic", true)) {
RawHDSeed entropy;
if (!GenerateMnemonicEntropy(256, entropy))
throw std::runtime_error(std::string(__func__) + ": -usemnemonic entropy generation failed");
// Store the EXPANDED 64-byte BIP39 seed as the HD seed, with
// fMnemonic = false. Every binary -- old or new -- then feeds the stored
// bytes straight into derivation, so the key tree is identical
// everywhere and no CHDChain version bump or minversion fence is needed.
// The 32-byte entropy is kept in a separate, display-only record purely
// so the phrase can be reprinted. Addresses are unchanged from the
// previous format, which expanded the stored entropy on every read.
RawHDSeed seed64;
if (!Bip39SeedFromEntropy(entropy, seed64))
throw std::runtime_error(std::string(__func__) + ": BIP39 seed expansion failed");
HDSeed seed(seed64);
// ORDER IS LOAD-BEARING: seed first, entropy second, never the reverse.
// A crash between the two leaves a wallet with a seed and no phrase --
// recoverable via z_exportwallet, merely inconvenient. The reverse order
// would leave entropy with no seed; the next start would mint a
// DIFFERENT seed while the wallet still held a phrase for the old one.
// (GetMnemonicPhrase cross-checks the two and would refuse to print it,
// but do not rely on that here.)
if (!InstallHDSeed(seed, false, nCreationTime))
throw std::runtime_error(std::string(__func__) + ": installing the mnemonic HD seed failed");
if (!SetMnemonicEntropy(entropy))
throw std::runtime_error(std::string(__func__) + ": storing the mnemonic entropy failed");
return;
}
// If the wallet is encrypted and locked, this will fail.
auto seed = HDSeed::Random(HD_WALLET_SEED_LENGTH);
if (!InstallHDSeed(seed, false, nCreationTime))
throw std::runtime_error(std::string(__func__) + ": SetHDSeed failed");
}
bool CWallet::SetHDSeed(const HDSeed& seed)
{
if (!CCryptoKeyStore::SetHDSeed(seed)) {
return false;
}
if (!fFileBacked) {
return true;
}
{
LOCK(cs_wallet);
if (!IsCrypted()) {
return CWalletDB(strWalletFile).WriteHDSeed(seed);
}
}
return true;
}
bool CWallet::SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret)
{
if (!CCryptoKeyStore::SetCryptedHDSeed(seedFp, vchCryptedSecret)) {
return false;
}
if (!fFileBacked) {
return true;
}
{
LOCK(cs_wallet);
// Write the encrypted record, then drop the plaintext one. Both go
// through the same CWalletDB (and therefore the same transaction when
// EncryptWallet supplied pwalletdbEncryption), because CDB::Rewrite at
// the end of EncryptWallet copies every surviving record into the fresh
// file -- a leftover plaintext "hdseed" would keep the unencrypted seed
// on disk for the life of the wallet.
//
// The erase is deliberately best-effort: a hard failure here propagates
// into CCryptoKeyStore::EncryptKeys, which CWallet::EncryptWallet turns
// into assert(false) with half the keys encrypted in memory. A logged
// warning is strictly better than that.
if (pwalletdbEncryption) {
if (!pwalletdbEncryption->WriteCryptedHDSeed(seedFp, vchCryptedSecret))
return false;
if (!pwalletdbEncryption->EraseHDSeed(seedFp))
LogPrintf("%s: WARNING: could not erase the plaintext hdseed record; "
"the unencrypted HD seed may remain in wallet.dat\n", __func__);
return true;
} else {
CWalletDB walletdb(strWalletFile);
if (!walletdb.WriteCryptedHDSeed(seedFp, vchCryptedSecret))
return false;
if (!walletdb.EraseHDSeed(seedFp))
LogPrintf("%s: WARNING: could not erase the plaintext hdseed record; "
"the unencrypted HD seed may remain in wallet.dat\n", __func__);
return true;
}
}
return false;
}
void CWallet::SetHDChain(const CHDChain& chain, bool memonly)
{
LOCK(cs_wallet);
if (!memonly && fFileBacked && !CWalletDB(strWalletFile).WriteHDChain(chain))
throw std::runtime_error(std::string(__func__) + ": writing chain failed");
hdChain = chain;
}
void CWallet::SetHDSeedOrigin(int origin)
{
LOCK(cs_wallet);
hdSeedOrigin = origin;
// Deliberately non-fatal, unlike SetHDChain: losing this record must never
// stop a node from starting. The cost of a failed write is that the next
// start re-classifies the wallet, and re-classification of an already-seeded
// wallet yields HDSEED_ORIGIN_UNKNOWN, i.e. the safe answer.
if (fFileBacked && !CWalletDB(strWalletFile).WriteHDSeedOrigin((int64_t)origin))
LogPrintf("%s: WARNING: could not record HD seed origin %d in wallet.dat\n", __func__, origin);
}
bool CWallet::LoadHDSeed(const HDSeed& seed)
{
return CBasicKeyStore::SetHDSeed(seed);
}
bool CWallet::LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& seed)
{
return CCryptoKeyStore::SetCryptedHDSeed(seedFp, seed);
}
bool CWallet::SetMnemonicEntropy(const RawHDSeed& entropy)
{
if (!CCryptoKeyStore::SetMnemonicEntropy(entropy)) {
return false;
}
if (!fFileBacked) {
return true;
}
{
LOCK(cs_wallet);
if (!IsCrypted()) {
// Keyed by fingerprint exactly as "hdseed" is, so ReadKeyValue can
// integrity-check it and EraseMnemonicEntropy can find it later.
return CWalletDB(strWalletFile).WriteMnemonicEntropy(
MnemonicEntropyFingerprint(entropy), entropy);
}
}
return true;
}
bool CWallet::SetCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector<unsigned char>& vchCryptedSecret)
{
if (!CCryptoKeyStore::SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret)) {
return false;
}
if (!fFileBacked) {
return true;
}
{
LOCK(cs_wallet);
// Same write-then-erase discipline as SetCryptedHDSeed: CDB::Rewrite at
// the end of EncryptWallet copies every surviving record, so a leftover
// plaintext "mnementropy" would keep the seed phrase recoverable from an
// encrypted wallet.dat. The erase is best-effort for the same reason: a
// hard failure would propagate into EncryptKeys -> assert(false).
if (pwalletdbEncryption) {
if (!pwalletdbEncryption->WriteCryptedMnemonicEntropy(entropyFp, vchCryptedSecret))
return false;
if (!pwalletdbEncryption->EraseMnemonicEntropy(entropyFp))
LogPrintf("%s: WARNING: could not erase the plaintext mnementropy record\n", __func__);
return true;
} else {
CWalletDB walletdb(strWalletFile);
if (!walletdb.WriteCryptedMnemonicEntropy(entropyFp, vchCryptedSecret))
return false;
if (!walletdb.EraseMnemonicEntropy(entropyFp))
LogPrintf("%s: WARNING: could not erase the plaintext mnementropy record\n", __func__);
return true;
}
}
return false;
}
bool CWallet::LoadMnemonicEntropy(const RawHDSeed& entropy)
{
return CBasicKeyStore::SetMnemonicEntropy(entropy);
}
bool CWallet::LoadCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector<unsigned char>& vchCryptedSecret)
{
return CCryptoKeyStore::SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret);
}
bool CWallet::InstallHDSeed(const HDSeed& seed, bool fMnemonic, int64_t nCreateTime)
{
AssertLockHeld(cs_wallet);
// Chain BEFORE seed. A crash between the two records must never leave a
// wallet that holds a seed with no hdchain: on the next load hdChain would
// silently revert to its defaults, clearing fMnemonicSeed (which switches
// the derivation input, wallet.cpp:2615-2633) and resetting
// saplingAccountCounter. The opposite torn state — chain without seed — is
// harmless and self-healing: HaveHDSeed() is false, so init installs a seed
// again and overwrites the chain.
CHDChain newHdChain;
newHdChain.nVersion = fMnemonic ? CHDChain::VERSION_HD_MNEMONIC
: CHDChain::VERSION_HD_TRANSPARENT;
newHdChain.seedFp = seed.Fingerprint();
newHdChain.nCreateTime = nCreateTime;
newHdChain.fMnemonicSeed = fMnemonic;
SetHDChain(newHdChain, false); // throws if the write fails
if (!SetHDSeed(seed))
return false;
return true;
}
bool CWallet::SetHDSeedFromHex(const std::string& seedHex)
{
LOCK(cs_wallet);
// Refuse to clobber an existing seed (the keystore refuses too); restore
// must run on a fresh/empty wallet.
if (HaveHDSeed())
return false;
if (!IsHex(seedHex))
return false;
std::vector<unsigned char> raw = ParseHex(seedHex);
// 32 = legacy raw seed; 64 = BIP39-derived seed (as exported by a mnemonic
// wallet). Either is used directly for derivation (fMnemonicSeed = false).
if (raw.size() != 32 && raw.size() != 64)
return false;
RawHDSeed rawSeed(raw.begin(), raw.end());
HDSeed seed(rawSeed);
return InstallHDSeed(seed, false, 1); // birthday = genesis for a restore
}
bool CWallet::SetHDSeedFromMnemonic(const std::string& phrase)
{
LOCK(cs_wallet);
if (HaveHDSeed())
return false;
RawHDSeed entropy;
if (!MnemonicToEntropy(phrase, entropy))
return false;
// Store the EXPANDED 64-byte BIP39 seed as the HD seed (fMnemonic = false);
// the entropy goes in its own record and is used only to reprint the phrase.
// Derivation therefore reads the stored bytes directly on any binary, and
// the resulting addresses are byte-identical to the previous format, which
// expanded the stored entropy on every derivation. SilentDragonXLite
// interop is unaffected: the same words still yield the same seed64.
RawHDSeed seed64;
if (!Bip39SeedFromEntropy(entropy, seed64))
return false;
HDSeed seed(seed64);
// Seed first, entropy second -- see the ordering note in GenerateNewSeed.
if (!InstallHDSeed(seed, false, 1)) // birthday = genesis for a restore
return false;
// Non-fatal on a restore, unlike GenerateNewSeed: the user already holds the
// phrase (they just typed it), the seed is installed and the wallet is fully
// functional; only z_exportmnemonic is lost.
if (!SetMnemonicEntropy(entropy)) {
LogPrintf("%s: WARNING: HD seed installed but the mnemonic entropy record could not be "
"stored; z_exportmnemonic will be unavailable on this wallet\n", __func__);
}
return true;
}
bool CWallet::GetHDSeedForDerivation(HDSeed& seedOut) const
{
// Single choke point for every HD derivation in the wallet. When the hdchain record could not
// be trusted at load time we do not know whether fMnemonicSeed should be true, and guessing
// wrong derives into an entirely different key tree -- so refuse rather than guess. Callers
// surface this as a clean error (z_getnewaddress, sendmany, shieldcoinbase) or skip
// (autoshield). Transparent address generation falls back to the legacy random-key path,
// because hdChain.seedFp stays null and so IsHDTransparentEnabled() is false.
if (fHDChainUnproven)
return false;
HDSeed stored;
if (!GetHDSeed(stored))
return false;
if (!hdChain.fMnemonicSeed) {
seedOut = stored; // legacy / hex seed: fed to derivation directly
return true;
}
// Mnemonic wallet: the stored seed is the 32-byte BIP39 entropy. Expand it
// to the 64-byte BIP39 seed exactly as SilentDragonXLite does.
RawHDSeed seed64;
if (!Bip39SeedFromEntropy(stored.RawSeed(), seed64))
return false;
seedOut = HDSeed(seed64);
return true;
}
bool CWallet::GetMnemonicPhrase(std::string& phraseOut) const
{
// Preferred form: the HD seed is the EXPANDED 64-byte BIP39 seed and the
// entropy sits in its own record.
RawHDSeed entropy;
if (GetMnemonicEntropy(entropy)) { // false on an encrypted+locked wallet
// NEVER hand out a phrase that does not restore THIS wallet. Prove the
// entropy expands to the exact bytes derivation consumes; if it does
// not (a torn install, a wallet.dat edited by hand, an entropy record
// paired with a different seed), refuse rather than print a phrase that
// silently restores someone else's key tree. Costs one PBKDF2 on a
// user-initiated RPC.
RawHDSeed seed64;
if (!Bip39SeedFromEntropy(entropy, seed64))
return false;
HDSeed derivationSeed;
if (!GetHDSeedForDerivation(derivationSeed))
return false;
if (derivationSeed.RawSeed() != seed64) {
LogPrintf("%s: refusing to export a seed phrase: the stored mnemonic entropy does not "
"expand to this wallet's HD seed\n", __func__);
return false;
}
return EntropyToMnemonic(entropy, phraseOut);
}
// Legacy form (earlier builds of this branch): the stored HD seed IS the
// 32-byte BIP39 entropy, expanded on every derivation. Consistent by
// construction, so no cross-check is possible or needed.
if (!hdChain.fMnemonicSeed)
return false;
HDSeed stored;
if (!GetHDSeed(stored)) // fails on an encrypted+locked wallet
return false;
return EntropyToMnemonic(stored.RawSeed(), phraseOut);
}
void CWallet::TopUpHDTransparentKeys(unsigned int count, int64_t nBirthday)
{
LOCK(cs_wallet);
if (!IsHDTransparentEnabled())
return;
for (unsigned int i = 0; i < count; i++) {
CKey secret;
CKeyMetadata metadata(nBirthday);
DeriveNewChildKey(metadata, secret);
CPubKey pubkey = secret.GetPubKey();
assert(secret.VerifyPubKey(pubkey));
mapKeyMetadata[pubkey.GetID()] = metadata;
// Keep the birthday floor at nBirthday so the rescan is not clipped
// (derived keys are stamped nBirthday, not "now", precisely for this).
if (!nTimeFirstKey || nBirthday < nTimeFirstKey)
nTimeFirstKey = nBirthday;
if (!AddKeyPubKey(secret, pubkey))
throw std::runtime_error("CWallet::TopUpHDTransparentKeys(): AddKeyPubKey failed");
}
}
void CWalletTx::SetSaplingNoteData(mapSaplingNoteData_t &noteData)
{
mapSaplingNoteData.clear();
for (const std::pair<SaplingOutPoint, SaplingNoteData> nd : noteData) {
if (nd.first.n < vShieldedOutput.size()) {
mapSaplingNoteData[nd.first] = nd.second;
} else {
throw std::logic_error("CWalletTx::SetSaplingNoteData(): Invalid note");
}
}
}
boost::optional<std::pair<
SaplingNotePlaintext,
SaplingPaymentAddress>> CWalletTx::DecryptSaplingNote(SaplingOutPoint op) const
{
// Check whether we can decrypt this SaplingOutPoint
if (this->mapSaplingNoteData.count(op) == 0) {
return boost::none;
}
auto output = this->vShieldedOutput[op.n];
auto nd = this->mapSaplingNoteData.at(op);
auto maybe_pt = SaplingNotePlaintext::decrypt(
output.encCiphertext,
nd.ivk,
output.ephemeralKey,
output.cm);
assert(static_cast<bool>(maybe_pt));
auto notePt = maybe_pt.get();
auto maybe_pa = nd.ivk.address(notePt.d);
assert(static_cast<bool>(maybe_pa));
auto pa = maybe_pa.get();
return std::make_pair(notePt, pa);
}
boost::optional<std::pair<
SaplingNotePlaintext,
SaplingPaymentAddress>> CWalletTx::RecoverSaplingNote(
SaplingOutPoint op, std::set<uint256>& ovks) const
{
auto output = this->vShieldedOutput[op.n];
for (auto ovk : ovks) {
auto outPt = SaplingOutgoingPlaintext::decrypt(
output.outCiphertext,
ovk,
output.cv,
output.cm,
output.ephemeralKey);
if (!outPt) {
continue;
}
auto maybe_pt = SaplingNotePlaintext::decrypt(
output.encCiphertext,
output.ephemeralKey,
outPt->esk,
outPt->pk_d,
output.cm);
assert(static_cast<bool>(maybe_pt));
auto notePt = maybe_pt.get();
return std::make_pair(notePt, SaplingPaymentAddress(notePt.d, outPt->pk_d));
}
// Couldn't recover with any of the provided OutgoingViewingKeys
return boost::none;
}
int64_t CWalletTx::GetTxTime() const
{
int64_t n = nTimeSmart;
return n ? n : nTimeReceived;
}
// GetAmounts will determine the transparent debits and credits for a given wallet tx.
void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
list<COutputEntry>& listSent, CAmount& nFee, string& strSentAccount, const isminefilter& filter) const
{
nFee = 0;
listReceived.clear();
listSent.clear();
strSentAccount = strFromAccount;
// Is this tx sent/signed by me?
CAmount nDebit = GetDebit(filter);
bool isFromMyTaddr = nDebit > 0; // debit>0 means we signed/sent this transaction
// Compute fee if we sent this transaction.
if (isFromMyTaddr) {
CAmount nValueOut = GetValueOut(); // transparent outputs plus all Sprout vpub_old and negative Sapling valueBalance
CAmount nValueIn = GetShieldedValueIn();
nFee = nDebit - nValueOut + nValueIn;
}
// Create output entry for vpub_old/new, if we sent utxos from this transaction
if (isFromMyTaddr) {
CAmount myVpubOld = 0;
CAmount myVpubNew = 0;
// Create an output for the value taken from or added to the transparent value pool by JoinSplits
if (myVpubOld > myVpubNew) {
COutputEntry output = {CNoDestination(), myVpubOld - myVpubNew, (int)vout.size()};
listSent.push_back(output);
} else if (myVpubNew > myVpubOld) {
COutputEntry output = {CNoDestination(), myVpubNew - myVpubOld, (int)vout.size()};
listReceived.push_back(output);
}
}
// If we sent utxos from this transaction, create output for value taken from (negative valueBalance)
// or added (positive valueBalance) to the transparent value pool by Sapling shielding and unshielding.
if (isFromMyTaddr) {
if (valueBalance < 0) {
COutputEntry output = {CNoDestination(), -valueBalance, (int) vout.size()};
listSent.push_back(output);
} else if (valueBalance > 0) {
COutputEntry output = {CNoDestination(), valueBalance, (int) vout.size()};
listReceived.push_back(output);
}
}
// Sent/received.
int32_t oneshot = 0;
for (unsigned int i = 0; i < vout.size(); ++i)
{
const CTxOut& txout = vout[i];
isminetype fIsMine = pwallet->IsMine(txout);
// Only need to handle txouts if AT LEAST one of these is true:
// 1) they debit from us (sent)
// 2) the output is to us (received)
if (nDebit > 0)
{
// Don't report 'change' txouts
if (!(filter & ISMINE_CHANGE) && pwallet->IsChange(txout))
{
if ( oneshot++ > 1 )
{
continue;
}
}
}
else if (!(fIsMine & filter))
{
continue;
}
// In either case, we need to get the destination address
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address))
{
//LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",this->GetHash().ToString()); complains on the opreturns
address = CNoDestination();
}
COutputEntry output = {address, txout.nValue, (int)i};
// If we are debited by the transaction, add the output as a "sent" entry
if (nDebit > 0)
listSent.push_back(output);
//else fprintf(stderr,"not sent vout %d %d\n",(int32_t)fIsMine,(int32_t)filter);
// If we are receiving the output, add it as a "received" entry
if (fIsMine & filter)
listReceived.push_back(output);
//else fprintf(stderr,"not received vout %d %d\n",(int32_t)fIsMine,(int32_t)filter);
}
}
void CWalletTx::GetAccountAmounts(const string& strAccount, CAmount& nReceived,
CAmount& nSent, CAmount& nFee, const isminefilter& filter) const
{
nReceived = nSent = nFee = 0;
CAmount allFee;
string strSentAccount;
list<COutputEntry> listReceived;
list<COutputEntry> listSent;
GetAmounts(listReceived, listSent, allFee, strSentAccount, filter);
if (strAccount == strSentAccount)
{
BOOST_FOREACH(const COutputEntry& s, listSent)
nSent += s.amount;
nFee = allFee;
}
{
LOCK(pwallet->cs_wallet);
BOOST_FOREACH(const COutputEntry& r, listReceived)
{
if (pwallet->mapAddressBook.count(r.destination))
{
map<CTxDestination, CAddressBookData>::const_iterator mi = pwallet->mapAddressBook.find(r.destination);
if (mi != pwallet->mapAddressBook.end() && (*mi).second.name == strAccount)
nReceived += r.amount;
}
else if (strAccount.empty())
{
nReceived += r.amount;
}
}
}
}
bool CWalletTx::WriteToDisk(CWalletDB *pwalletdb)
{
return pwalletdb->WriteTx(GetHash(), *this);
}
/**
* Reorder the transactions based on block hieght and block index.
* Transactions can get out of order when they are deleted and subsequently
* re-added during intial load rescan.
*/
void CWallet::ReorderWalletTransactions(std::map<std::pair<int,int>, CWalletTx*> &mapSorted, int64_t &maxOrderPos)
{
LOCK2(cs_main, cs_wallet);
if(fZdebug)
fprintf(stderr,"%s: maxOrderPos=%li\n",__func__, maxOrderPos);
int maxSortNumber = chainActive.Tip()->GetHeight() + 1;
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
CWalletTx* pwtx = &(it->second);
int confirms = pwtx->GetDepthInMainChain();
maxOrderPos = max(maxOrderPos, pwtx->nOrderPos);
if (confirms > 0) {
int wtxHeight = mapBlockIndex[pwtx->hashBlock]->GetHeight();
auto key = std::make_pair(wtxHeight, pwtx->nIndex);
mapSorted.insert(make_pair(key, pwtx));
} else {
auto key = std::make_pair(maxSortNumber, 0);
mapSorted.insert(std::make_pair(key, pwtx));
maxSortNumber++;
}
}
if(fZdebug)
fprintf(stderr,"%s: mapSorted.size=%lu\n",__func__, mapSorted.size());
}
/**Update the nOrderPos with passed in ordered map.
*/
void CWallet::UpdateWalletTransactionOrder(std::map<std::pair<int,int>, CWalletTx*> &mapSorted, bool resetOrder) {
LOCK2(cs_main, cs_wallet);
int64_t previousPosition = 0;
std::map<const uint256, CWalletTx*> mapUpdatedTxs;
if(fZdebug)
fprintf(stderr,"%s: maxSorted.size=%li resetOrder=%d\n",__func__, mapSorted.size(),resetOrder);
//Check the position of each transaction relative to the previous one.
for (map<std::pair<int,int>, CWalletTx*>::iterator it = mapSorted.begin(); it != mapSorted.end(); ++it) {
CWalletTx* pwtx = it->second;
const uint256 wtxid = pwtx->GetHash();
if (pwtx->nOrderPos <= previousPosition || resetOrder) {
previousPosition++;
pwtx->nOrderPos = previousPosition;
mapUpdatedTxs.insert(std::make_pair(wtxid, pwtx));
} else {
previousPosition = pwtx->nOrderPos;
}
}
if(fZdebug)
fprintf(stderr,"%s: updating %li changed transactions\n",__func__, mapUpdatedTxs.size() );
//Update transactions nOrderPos for transactions that changed
CWalletDB walletdb(strWalletFile, "r+", false);
for (map<const uint256, CWalletTx*>::iterator it = mapUpdatedTxs.begin(); it != mapUpdatedTxs.end(); ++it) {
CWalletTx* pwtx = it->second;
LogPrintf("%s: Updating Position to %i for Tx %s\n ", __func__, pwtx->nOrderPos, pwtx->GetHash().ToString());
bool ret = pwtx->WriteToDisk(&walletdb);
if(fZdebug)
fprintf(stderr,"%s: wrote data to disk at %s for tx=%s ret=%d\n",__func__, strWalletFile.c_str(), pwtx->GetHash().ToString().c_str(), ret );
mapWallet[pwtx->GetHash()].nOrderPos = pwtx->nOrderPos;
}
if(fZdebug)
fprintf(stderr,"%s: updated nOrderPos on %lu transactions\n",__func__, mapUpdatedTxs.size() );
//Update Next Wallet Tx Position
nOrderPosNext = previousPosition++;
CWalletDB(strWalletFile).WriteOrderPosNext(nOrderPosNext);
if(fZdebug)
fprintf(stderr,"%s: wrote data to disk at %s nOrderPosNext=%li\n",__func__, strWalletFile.c_str(), nOrderPosNext );
LogPrintf("%s: Total Transactions Reordered %i, Next Position %i\n ", __func__, mapUpdatedTxs.size(), nOrderPosNext);
}
/**
* Delete transactions from the Wallet
*/
void CWallet::DeleteTransactions(std::vector<uint256> &removeTxs) {
LOCK(cs_wallet);
int numTx = removeTxs.size();
if(fZdebug)
fprintf(stderr,"%s: removeTxs.size=%d\n", __func__, numTx);
CWalletDB walletdb(strWalletFile, "r+", false);
for (int i = 0; i< numTx; i++) {
if (mapWallet.erase(removeTxs[i])) {
walletdb.EraseTx(removeTxs[i]);
LogPrintf("%s: Deleted tx %s, %i.\n", __func__, removeTxs[i].ToString(),i);
} else {
LogPrintf("%s: Deleting tx %s failed.\n", __func__, removeTxs[i].ToString());
return;
}
}
//TODO: the build system should check for malloc_trim support
#if defined(__linux__)
malloc_trim(0);
#else
// On Mac and Win memory isn't kept back upon vector or list member erase, different garbage collector strategy. No need to force trimming.
#endif
if(fZdebug)
fprintf(stderr,"%s: finished deleting %d transactions\n", __func__, numTx);
}
void CWallet::DeleteWalletTransactions(const CBlockIndex* pindex) {
LOCK2(cs_main, cs_wallet);
int nDeleteAfter = (int)fDeleteTransactionsAfterNBlocks;
bool runCompact = false;
if(fZdebug)
fprintf(stderr,"%s: nDeleteAfter=%d\n",__func__,nDeleteAfter);
if (pindex && fTxDeleteEnabled) {
//Check for acentries - exit function if found
{
std::list<CAccountingEntry> acentries;
CWalletDB walletdb(strWalletFile);
walletdb.ListAccountCreditDebit("*", acentries);
if (acentries.size() > 0) {
LogPrintf("deletetx not compatible to account entries\n");
return;
}
}
//delete transactions
//Sort Transactions by block and block index
int64_t maxOrderPos = 0;
std::map<std::pair<int,int>, CWalletTx*> mapSorted;
ReorderWalletTransactions(mapSorted, maxOrderPos);
if (maxOrderPos > int64_t(mapSorted.size())*10) {
//reset the postion when the max postion is 10x bigger than the
//number of transactions in the wallet
LogPrintf("%s: Reorder Tx - maxOrderPos %i mapSorted Size %i\n", __func__, maxOrderPos, int64_t(mapSorted.size())*10);
UpdateWalletTransactionOrder(mapSorted, true);
} else {
UpdateWalletTransactionOrder(mapSorted, false);
}
//Process Transactions in sorted order
int txConflictCount = 0;
int txUnConfirmed = 0;
int txCount = 0;
int txSaveCount = 0;
std::vector<uint256> removeTxs;
for (auto & item : mapSorted)
{
CWalletTx* pwtx = item.second;
const uint256& wtxid = pwtx->GetHash();
bool deleteTx = true;
txCount += 1;
int wtxDepth = pwtx->GetDepthInMainChain();
//Keep anything newer than N Blocks
if (wtxDepth == 0)
txUnConfirmed++;
if (wtxDepth < nDeleteAfter && wtxDepth >= 0) {
if(fZdebug)
LogPrintf("%s: Transaction above minimum depth, tx %s\n", __func__, pwtx->GetHash().ToString());
deleteTx = false;
txSaveCount++;
continue;
} else if (wtxDepth == -1) {
//Enabled by default
if (!fTxConflictDeleteEnabled) {
if(fZdebug)
LogPrintf("%s: Conflict delete is not enabled tx %s\n", __func__, pwtx->GetHash().ToString());
deleteTx = false;
txSaveCount++;
continue;
} else {
txConflictCount++;
}
} else {
//Check for unspent inputs or spend less than N Blocks ago. (Sapling)
for (auto & pair : pwtx->mapSaplingNoteData) {
SaplingNoteData nd = pair.second;
if (!nd.nullifier || pwalletMain->GetSaplingSpendDepth(*nd.nullifier) <= fDeleteTransactionsAfterNBlocks) {
if(fZdebug)
LogPrintf("%s: Unspent sapling input tx %s\n", __func__, pwtx->GetHash().ToString());
deleteTx = false;
continue;
}
}
if (!deleteTx) {
txSaveCount++;
continue;
}
if(fZdebug)
LogPrintf("%s: Unspent sapling input tx %s\n", __func__, pwtx->GetHash().ToString());
//Check for outputs that no longer have parents in the wallet. Exclude parents that are in the same transaction. (Sapling)
for (int i = 0; i < pwtx->vShieldedSpend.size(); i++) {
const SpendDescription& spendDesc = pwtx->vShieldedSpend[i];
if (pwalletMain->IsSaplingNullifierFromMe(spendDesc.nullifier)) {
const uint256& parentHash = pwalletMain->mapSaplingNullifiersToNotes[spendDesc.nullifier].hash;
const CWalletTx* parent = pwalletMain->GetWalletTx(parentHash);
if (parent != NULL && parentHash != wtxid) {
if(fZdebug)
LogPrintf("%s: Parent of sapling tx %s found\n", __func__, pwtx->GetHash().ToString());
deleteTx = false;
continue;
}
}
}
if (!deleteTx) {
txSaveCount++;
continue;
}
if(fZdebug)
LogPrintf("%s: Checking for unspent transparent inputs or spends less than %d Blocks ago\n",__func__, fDeleteTransactionsAfterNBlocks);
for (unsigned int i = 0; i < pwtx->vout.size(); i++) {
CTxDestination address;
ExtractDestination(pwtx->vout[i].scriptPubKey, address);
if(IsMine(pwtx->vout[i])) {
if (pwalletMain->GetSpendDepth(pwtx->GetHash(), i) <= fDeleteTransactionsAfterNBlocks) {
if(fZdebug)
LogPrintf("%s: Unspent transparent input tx %s\n", __func__, pwtx->GetHash().ToString());
deleteTx = false;
continue;
}
}
}
if (!deleteTx) {
txSaveCount++;
continue;
}
if(fZdebug)
LogPrintf("%s: Checking for transparent outputs that no longer have parents in the wallet\n",__func__);
for (int i = 0; i < pwtx->vin.size(); i++) {
const CTxIn& txin = pwtx->vin[i];
const uint256& parentHash = txin.prevout.hash;
const CWalletTx* parent = pwalletMain->GetWalletTx(txin.prevout.hash);
if (parent != NULL && parentHash != wtxid) {
if(fZdebug)
LogPrintf("%s: Parent of transparent tx %s found\n", __func__, pwtx->GetHash().ToString());
deleteTx = false;
continue;
}
}
if (!deleteTx) {
txSaveCount++;
continue;
}
//Keep Last N Transactions
if (mapSorted.size() - txCount < fKeepLastNTransactions + txConflictCount + txUnConfirmed) {
if(fZdebug)
LogPrint("%s: Transaction set position %i, tx %s\n", __func__, mapSorted.size() - txCount, wtxid.ToString());
deleteTx = false;
txSaveCount++;
continue;
}
}
//Collect everything else for deletion
if (deleteTx && int(removeTxs.size()) < MAX_DELETE_TX_SIZE) {
removeTxs.push_back(wtxid);
runCompact = true;
}
}
//Delete Transactions from wallet
DeleteTransactions(removeTxs);
LogPrintf("%s: Total Transaction Count %i, Transactions Deleted %i\n ", __func__, txCount, int(removeTxs.size()));
//Compress Wallet
if (runCompact) {
if(fZdebug)
fprintf(stderr,"%s: compacting wallet\n",__func__);
CWalletDB::Compact(bitdb,strWalletFile);
}
}
}
/**
* Scan the block chain (starting in pindexStart) for transactions
* from or to us. If fUpdate is true, found transactions that already
* exist in the wallet will be updated.
*/
int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
{
int ret = 0;
int64_t nNow = GetTime();
const CChainParams& chainParams = Params();
if(fZdebug)
LogPrintf("%s: fUpdate=%d now=%li\n",__func__,fUpdate,nNow);
// fAbortRescan is sticky: nothing else in the tree ever clears it, so without this a single
// `abortrescan` RPC would disable every later scan AND every later BuildWitnessCache for the
// lifetime of the process (BuildWitnessCache bails on the same flag), freezing witness heights
// while the chain advances and progressively rendering notes unspendable.
pwalletMain->fAbortRescan = false;
pwalletMain->fLastRescanCompleted = false;
pwalletMain->fRescanning = true;
CBlockIndex* pindex = pindexStart;
pwalletMain->rescanStartHeight = pindex->GetHeight();
pwalletMain->rescanHeight = pwalletMain->rescanStartHeight;
{
LOCK2(cs_main, cs_wallet);
// no need to read and scan block, if block was created before
// our wallet birthday (as adjusted for block time variability)
while (pindex && nTimeFirstKey && (pindex->GetBlockTime() < (nTimeFirstKey - 7200))) {
pindex = chainActive.Next(pindex);
pwalletMain->rescanHeight = pindex ? pindex->GetHeight() : 0;
}
// --- interrupt checkpoint setup -------------------------------------------------------
// Where does the wallet currently believe it has scanned to? A checkpoint may only ever
// ADVANCE that point, and only if this scan is contiguous with it. The RPC entry points
// (rescan / importprivkey / z_importkey / z_importviewingkey) take a caller-supplied start
// height validated only against chainActive.Height(), so a scan can legitimately begin far
// ABOVE the persisted locator -- writing a checkpoint from such a scan would mark the
// skipped range as scanned and hide any funds in it.
CBlockIndex* pindexPersisted = NULL;
{
CWalletDB walletdb(strWalletFile, "r+", false);
CBlockLocator locPersisted;
if (walletdb.ReadBestBlock(locPersisted))
pindexPersisted = FindForkInGlobalIndex(chainActive, locPersisted);
}
const bool fMayCheckpoint = pindexPersisted != NULL &&
pindexStart->GetHeight() <= pindexPersisted->GetHeight() + 1;
if (!fMayCheckpoint) {
LogPrintf("%s: scan starts at %d but the wallet is persisted at %d; progress will NOT be "
"checkpointed on interrupt (a non-contiguous scan cannot safely advance the locator)\n",
__func__, pindexStart->GetHeight(),
pindexPersisted ? pindexPersisted->GetHeight() : -1);
}
// Persist progress when the scan is cut short. `pindexStopped` is the block we were ABOUT to
// scan, so the last fully-processed block is its parent. Resume restarts AT the locator's own
// block (CChain::GetLocator pushes it first; FindForkInGlobalIndex returns it), giving one
// block of deliberate overlap -- idempotent, because AddToWallet only merges when the tx is
// already present. No witness work is needed: witnesses are re-derived from each note's own
// witnessHeight, and witnessRootValidated is in-memory-only so every note is revalidated
// against hashFinalSaplingRoot on the next start.
auto checkpointProgress = [&](const CBlockIndex* pindexStopped) {
if (!fMayCheckpoint || !pindexStopped || !pindexStopped->pprev)
return;
const CBlockIndex* pindexDone = pindexStopped->pprev;
if (pindexDone->GetHeight() <= pindexPersisted->GetHeight())
return; // never move the locator backwards
if (SetBestChainNoFlush(chainActive.GetLocator(pindexDone))) {
LogPrintf("%s: checkpointed scan progress at height %d\n", __func__, pindexDone->GetHeight());
} else {
LogPrintf("%s: FAILED to checkpoint scan progress at height %d; the scan will replay "
"from height %d on the next start\n", __func__, pindexDone->GetHeight(),
pindexPersisted->GetHeight());
}
};
ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
double dProgressStart = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex, false);
double dProgressTip = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.LastTip(), false);
while (pindex)
{
pwalletMain->rescanHeight = pindex->GetHeight();
if(pwalletMain->fAbortRescan) {
// The witness caches do NOT need updating here: on resume each note's witnesses are
// re-derived from its own witnessHeight, independently of the locator, and
// witnessRootValidated is in-memory-only so a full validation pass runs next boot.
// What DOES need saving is the locator -- see the checkpoint below.
pwalletMain->fRescanning = false;
pwalletMain->fAbortRescan = false; // consume it; see the note at scan entry
LogPrintf("%s: Rescan aborted at block %d\n", __func__, pwalletMain->rescanHeight);
checkpointProgress(pindex);
return ret;
}
if (ShutdownRequested()) {
pwalletMain->fRescanning = false;
LogPrintf("%s: Rescan interrupted by shutdown request at block %d\n", __func__, pwalletMain->rescanHeight);
checkpointProgress(pindex);
return ret;
}
if (pindex->GetHeight() % 100 == 0 && dProgressTip - dProgressStart > 0.0)
ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex, false) - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
CBlock block;
bool involvesMe = false;
if (!ReadBlockFromDisk(block, pindex,1)) {
throw std::runtime_error(
strprintf("Cannot read block height %d (%s) from disk", pindex->GetHeight(), pindex->GetBlockHash().GetHex()));
}
BOOST_FOREACH(CTransaction& tx, block.vtx)
{
if (AddToWalletIfInvolvingMe(tx, &block, fUpdate)) {
involvesMe = true;
ret++;
}
}
SaplingMerkleTree saplingTree;
// This should never fail: we should always be able to get the tree
// state on the path to the tip of our chain
if (pindex->pprev) {
const bool sapling = pindex->GetHeight() >= 2 ? true : false;
if (sapling) { // NetworkUpgradeActive(pindex->pprev->GetHeight(), Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
assert(pcoinsTip->GetSaplingAnchorAt(pindex->pprev->hashFinalSaplingRoot, saplingTree));
}
}
// Build initial witness caches for blocks involving one of our addresses
if (involvesMe) {
LogPrintf("%s: block has one of our transactions, building witness cache\n", __func__);
BuildWitnessCache(pindex, true);
}
//Delete Transactions
if (fTxDeleteEnabled) {
if (pindex->GetHeight() % fDeleteInterval == 0)
DeleteWalletTransactions(pindex);
}
if (GetTime() >= nNow + 60) {
nNow = GetTime();
LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->GetHeight(), Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex));
}
// update rescan height before we scan the next block
pwalletMain->rescanHeight = pindex->GetHeight();
pindex = chainActive.Next(pindex);
}
//Update all witness caches
BuildWitnessCache(chainActive.Tip(), false);
ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
}
// we are no longer rescanning
pwalletMain->fRescanning = false;
// Reached only by running the loop to the end of the active chain. Callers use this to decide
// whether it is honest to record the wallet as scanned up to the tip.
pwalletMain->fLastRescanCompleted = true;
return ret;
}
void CWallet::ReacceptWalletTransactions()
{
int64_t nNow = GetTime();
if(fZdebug)
LogPrintf("%s: now=%li\n",__func__,nNow);
if ( IsInitialBlockDownload() )
return;
// If transactions aren't being broadcasted, don't let them into local mempool either
if (!fBroadcastTransactions)
return;
LOCK2(cs_main, cs_wallet);
std::map<int64_t, CWalletTx*> mapSorted;
// Sort pending wallet transactions based on their initial wallet insertion order
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
{
const uint256& wtxid = item.first;
CWalletTx& wtx = item.second;
if(wtx.GetHash() != wtxid) {
LogPrintf("%s: Something funky going on, skipping this tx. wtx.GetHash() != wtxid (%s != %s)\n", __func__, wtx.GetHash().ToString().c_str(), wtxid.ToString().c_str() );
continue;
}
// Crashing the node because of this is lame
// assert(wtx.GetHash() == wtxid);
int nDepth = wtx.GetDepthInMainChain();
if (!wtx.IsCoinBase() && nDepth < 0) {
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
}
}
std::vector<uint256> vwtxh;
// Try to add wallet transactions to memory pool
BOOST_FOREACH(PAIRTYPE(const int64_t, CWalletTx*)& item, mapSorted)
{
CWalletTx& wtx = *(item.second);
LOCK(mempool.cs);
CValidationState state;
// attempt to add them, but don't set any DOS level
if (!::AcceptToMemoryPool(mempool, state, wtx, false, NULL, true, 0))
{
int nDoS;
bool invalid = state.IsInvalid(nDoS);
// log rejection and deletion
if (!wtx.IsCoinBase() && invalid && nDoS > 0 && state.GetRejectReason() != "tx-overwinter-expired")
{
LogPrintf("%s: erasing transaction %s\n", __func__, wtx.GetHash().GetHex().c_str());
vwtxh.push_back(wtx.GetHash());
}
}
}
for (auto hash : vwtxh)
{
EraseFromWallet(hash);
}
}
bool CWalletTx::RelayWalletTransaction()
{
int64_t nNow = GetTime();
if ( pwallet == 0 )
{
return(false);
}
assert(pwallet->GetBroadcastTransactions());
if (!IsCoinBase())
{
if (GetDepthInMainChain() == 0)
{
// if tx is expired, dont relay
LogPrintf("Relaying wtx %s\n", GetHash().ToString());
RelayTransaction((CTransaction)*this);
return true;
}
}
return false;
}
set<uint256> CWalletTx::GetConflicts() const
{
set<uint256> result;
if (pwallet != NULL)
{
uint256 myHash = GetHash();
result = pwallet->GetConflicts(myHash);
result.erase(myHash);
}
return result;
}
CAmount CWalletTx::GetDebit(const isminefilter& filter) const
{
if (vin.empty())
return 0;
CAmount debit = 0;
if(filter & ISMINE_SPENDABLE)
{
if (fDebitCached)
debit += nDebitCached;
else
{
nDebitCached = pwallet->GetDebit(*this, ISMINE_SPENDABLE);
fDebitCached = true;
debit += nDebitCached;
}
}
if(filter & ISMINE_WATCH_ONLY)
{
if(fWatchDebitCached)
debit += nWatchDebitCached;
else
{
nWatchDebitCached = pwallet->GetDebit(*this, ISMINE_WATCH_ONLY);
fWatchDebitCached = true;
debit += nWatchDebitCached;
}
}
return debit;
}
CAmount CWalletTx::GetCredit(const isminefilter& filter) const
{
// Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsCoinBase() && GetBlocksToMaturity() > 0)
return 0;
int64_t credit = 0;
if (filter & ISMINE_SPENDABLE)
{
// GetBalance can assume transactions in mapWallet won't change
if (fCreditCached)
credit += nCreditCached;
else
{
nCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE);
fCreditCached = true;
credit += nCreditCached;
}
}
if (filter & ISMINE_WATCH_ONLY)
{
if (fWatchCreditCached)
credit += nWatchCreditCached;
else
{
nWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
fWatchCreditCached = true;
credit += nWatchCreditCached;
}
}
return credit;
}
CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
{
if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
{
if (fUseCache && fImmatureCreditCached)
return nImmatureCreditCached;
nImmatureCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE);
fImmatureCreditCached = true;
return nImmatureCreditCached;
}
return 0;
}
CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const
{
if (pwallet == 0)
return 0;
// Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsCoinBase() && GetBlocksToMaturity() > 0)
return 0;
if (fUseCache && fAvailableCreditCached)
return nAvailableCreditCached;
CAmount nCredit = 0;
uint256 hashTx = GetHash();
for (unsigned int i = 0; i < vout.size(); i++)
{
if (!pwallet->IsSpent(hashTx, i))
{
nCredit += pwallet->GetCredit(*this, i, ISMINE_SPENDABLE);
if (!MoneyRange(nCredit))
throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
}
}
nAvailableCreditCached = nCredit;
fAvailableCreditCached = true;
return nCredit;
}
CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool& fUseCache) const
{
if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
{
if (fUseCache && fImmatureWatchCreditCached)
return nImmatureWatchCreditCached;
nImmatureWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
fImmatureWatchCreditCached = true;
return nImmatureWatchCreditCached;
}
return 0;
}
CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const
{
if (pwallet == 0)
return 0;
// Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsCoinBase() && GetBlocksToMaturity() > 0)
return 0;
if (fUseCache && fAvailableWatchCreditCached)
return nAvailableWatchCreditCached;
CAmount nCredit = 0;
for (unsigned int i = 0; i < vout.size(); i++)
{
if (!pwallet->IsSpent(GetHash(), i))
{
nCredit += pwallet->GetCredit(*this, i, ISMINE_WATCH_ONLY);
if (!MoneyRange(nCredit))
throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
}
}
nAvailableWatchCreditCached = nCredit;
fAvailableWatchCreditCached = true;
return nCredit;
}
CAmount CWalletTx::GetChange() const
{
if (fChangeCached)
return nChangeCached;
nChangeCached = pwallet->GetChange(*this);
fChangeCached = true;
return nChangeCached;
}
bool CWalletTx::IsTrusted() const
{
// Quick answer in most cases
if (!CheckFinalTx(*this))
return false;
int nDepth = GetDepthInMainChain();
if (nDepth >= 1)
return true;
if (nDepth < 0)
return false;
if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
return false;
// Trusted if all inputs are from us and are in the mempool:
BOOST_FOREACH(const CTxIn& txin, vin)
{
// Transactions not sent by us: not trusted
const CWalletTx* parent = pwallet->GetWalletTx(txin.prevout.hash);
if (parent == NULL)
return false;
const CTxOut& parentOut = parent->vout[txin.prevout.n];
if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE)
return false;
}
return true;
}
std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime)
{
std::vector<uint256> result;
LOCK(cs_wallet);
// Sort them in chronological order
multimap<unsigned int, CWalletTx*> mapSorted;
uint32_t now = (uint32_t)time(NULL);
LogPrintf("%s: nTime=%ld now=%d\n", __func__, nTime, now);
// vector of wallet transactions to delete
std::vector<uint256> vwtxh;
uint32_t erased = 0, skipped = 0;
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
{
CWalletTx& wtx = item.second;
// Don't rebroadcast if newer than nTime:
if (wtx.nTimeReceived > nTime)
continue;
// Do not relay expired transactions, to avoid other nodes banning us
// Current code will not ban nodes relaying expired txs but older nodes will
if (wtx.nExpiryHeight > 0 && wtx.nExpiryHeight < chainActive.LastTip()->GetHeight()) {
LogPrintf("%s: ignoring expired tx %s with expiry %d at height %d\n", __func__, wtx.GetHash().ToString().c_str(), wtx.nExpiryHeight, chainActive.LastTip()->GetHeight() );
// TODO: expired detection doesn't seem to work right
// append to list of txs to delete
// vwtxh.push_back(wtx.GetHash());
continue;
}
if ( (wtx.nLockTime >= LOCKTIME_THRESHOLD && wtx.nLockTime < now-HUSH_MAXMEMPOOLTIME) )
{
if(fDebug) {
LogPrintf("%s: skip Relaying wtx %s nLockTime %u vs now.%u\n", __func__, wtx.GetHash().ToString(),(uint32_t)wtx.nLockTime,now);
}
skipped++;
// TODO: this does not seem to handle rescanning+finding old coinbase txs correctly
//vwtxh.push_back(wtx.GetHash());
continue;
}
mapSorted.insert(make_pair(wtx.nTimeReceived, &wtx));
}
BOOST_FOREACH(PAIRTYPE(const unsigned int, CWalletTx*)& item, mapSorted)
{
if ( item.second != 0 )
{
CWalletTx &wtx = *item.second;
if (wtx.RelayWalletTransaction())
result.push_back(wtx.GetHash());
}
}
// Unless we remove these unconfirmed and/or expired txs from the wallet, they will
// persist there forever. They are too old to be accepted by network
// consensus rules, so we erase them.
// Expired txs are always unconfirmed, but unconfirmed tx's could be expired or not,
// i.e. expired txs are a subset of unconfirmed tx's. Expired tx's can never be included
// in a block because they are against consensus rules. Unconfirmed tx's might still be
// included in a future block.
for (auto hash : vwtxh)
{
EraseFromWallet(hash);
erased++;
}
if(erased > 0 || skipped > 0) {
LogPrintf("%s: Prevented relaying %d and erased %d transactions which are too old\n", __func__, skipped, erased);
}
return result;
}
void CWallet::ResendWalletTransactions(int64_t nBestBlockTime)
{
// Do this infrequently and randomly to avoid giving away
// that these are our transactions.
if (GetTime() < nNextResend || !fBroadcastTransactions)
return;
bool fFirst = (nNextResend == 0);
// TODO: BTC Core changed this to be every 12 hours instead of every 30 mins
nNextResend = GetTime() + GetRand(30 * 60);
if (fFirst)
return;
// do not resend during IBD/rescan because some txs will be unconfirmed
// until completion
if(IsInitialBlockDownload()) {
return;
}
if (pwalletMain->fRescanning) {
return;
}
// do not resend during a reindex or initial loading of blocks
if (HUSH_LOADINGBLOCKS) {
return;
}
// Only do it if there's been a new block since last time
if (nBestBlockTime < nLastResend)
return;
nLastResend = GetTime();
LogPrintf("%s: nBestBlockTime=%ld nNextResend=%ld nLastResend=%ld time=%ld\n", __func__, nBestBlockTime, nNextResend, nLastResend, GetTime());
// Rebroadcast unconfirmed txes older than 5 minutes before the last
// block was found:
std::vector<uint256> relayed = ResendWalletTransactionsBefore(nBestBlockTime-5*60);
if (!relayed.empty())
LogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size());
}
/** @} */ // end of mapWallet
/** @defgroup Actions
*
* @{
*/
CAmount CWallet::GetBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (pcoin->IsTrusted())
nTotal += pcoin->GetAvailableCredit();
}
}
return nTotal;
}
CAmount CWallet::GetUnconfirmedBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (!CheckFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
nTotal += pcoin->GetAvailableCredit();
}
}
return nTotal;
}
CAmount CWallet::GetImmatureBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
nTotal += pcoin->GetImmatureCredit();
}
}
return nTotal;
}
CAmount CWallet::GetWatchOnlyBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (pcoin->IsTrusted())
nTotal += pcoin->GetAvailableWatchOnlyCredit();
}
}
return nTotal;
}
CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (!CheckFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
nTotal += pcoin->GetAvailableWatchOnlyCredit();
}
}
return nTotal;
}
CAmount CWallet::GetImmatureWatchOnlyBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
nTotal += pcoin->GetImmatureWatchOnlyCredit();
}
}
return nTotal;
}
/**
* populate vCoins with vector of available COutputs.
*/
void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue, bool fIncludeCoinBase) const
{
uint64_t *ptr;
vCoins.clear();
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const uint256& wtxid = it->first;
const CWalletTx* pcoin = &(*it).second;
if (!CheckFinalTx(*pcoin))
continue;
if (fOnlyConfirmed && !pcoin->IsTrusted())
continue;
if (pcoin->IsCoinBase() && !fIncludeCoinBase)
continue;
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
continue;
int nDepth = pcoin->GetDepthInMainChain();
if (nDepth < 0)
continue;
for (int i = 0; i < pcoin->vout.size(); i++)
{
isminetype mine = IsMine(pcoin->vout[i]);
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
!IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) &&
(!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i)))
{
ptr = (uint64_t *)&pcoin->vout[i].interest;
(*ptr) = 0;
vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO));
}
}
}
}
}
static void ApproximateBestSubset(vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > >vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
{
vector<char> vfIncluded;
vfBest.assign(vValue.size(), true);
nBest = nTotalLower;
seed_insecure_rand();
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
{
vfIncluded.assign(vValue.size(), false);
CAmount nTotal = 0;
bool fReachedTarget = false;
for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
{
for (unsigned int i = 0; i < vValue.size(); i++)
{
//The solver here uses a randomized algorithm,
//the randomness serves no real security purpose but is just
//needed to prevent degenerate behavior and it is important
//that the rng is fast. We do not use a constant random sequence,
//because there may be some privacy improvement by making
//the selection random.
if (nPass == 0 ? insecure_rand()&1 : !vfIncluded[i])
{
nTotal += vValue[i].first;
vfIncluded[i] = true;
if (nTotal >= nTargetValue)
{
fReachedTarget = true;
if (nTotal < nBest)
{
nBest = nTotal;
vfBest = vfIncluded;
}
nTotal -= vValue[i].first;
vfIncluded[i] = false;
}
}
}
}
}
}
bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, vector<COutput> vCoins,set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const
{
int32_t count = 0; //uint64_t lowest_interest = 0;
setCoinsRet.clear();
//memset(interests,0,sizeof(interests));
nValueRet = 0;
// List of values less than target
pair<CAmount, pair<const CWalletTx*,unsigned int> > coinLowestLarger;
coinLowestLarger.first = std::numeric_limits<CAmount>::max();
coinLowestLarger.second.first = NULL;
vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > > vValue;
CAmount nTotalLower = 0;
std::shuffle(vCoins.begin(), vCoins.end(), std::mt19937(GetRand(std::numeric_limits<uint32_t>::max())));
BOOST_FOREACH(const COutput &output, vCoins)
{
if (!output.fSpendable)
continue;
const CWalletTx *pcoin = output.tx;
if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs))
continue;
int i = output.i;
CAmount n = pcoin->vout[i].nValue;
pair<CAmount,pair<const CWalletTx*,unsigned int> > coin = make_pair(n,make_pair(pcoin, i));
if (n == nTargetValue)
{
setCoinsRet.insert(coin.second);
nValueRet += coin.first;
return true;
}
else if (n < nTargetValue + CENT)
{
vValue.push_back(coin);
nTotalLower += n;
if ( nTotalLower > 4*nTargetValue + CENT )
{
break;
}
} else if (n < coinLowestLarger.first)
{
coinLowestLarger = coin;
}
}
if (nTotalLower == nTargetValue)
{
for (unsigned int i = 0; i < vValue.size(); ++i)
{
setCoinsRet.insert(vValue[i].second);
nValueRet += vValue[i].first;
}
return true;
}
if (nTotalLower < nTargetValue)
{
if (coinLowestLarger.second.first == NULL)
return false;
setCoinsRet.insert(coinLowestLarger.second);
nValueRet += coinLowestLarger.first;
return true;
}
// Solve subset sum by stochastic approximation
sort(vValue.rbegin(), vValue.rend(), CompareValueOnly());
vector<char> vfBest;
CAmount nBest;
ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest, 1000);
if (nBest != nTargetValue && nTotalLower >= nTargetValue + CENT)
ApproximateBestSubset(vValue, nTotalLower, nTargetValue + CENT, vfBest, nBest, 1000);
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
// or the next bigger coin is closer), return the bigger coin
if (coinLowestLarger.second.first &&
((nBest != nTargetValue && nBest < nTargetValue + CENT) || coinLowestLarger.first <= nBest))
{
setCoinsRet.insert(coinLowestLarger.second);
nValueRet += coinLowestLarger.first;
} else {
for (unsigned int i = 0; i < vValue.size(); i++)
if (vfBest[i])
{
setCoinsRet.insert(vValue[i].second);
nValueRet += vValue[i].first;
}
LogPrint("selectcoins", "SelectCoins() best subset: ");
for (unsigned int i = 0; i < vValue.size(); i++)
if (vfBest[i])
LogPrint("selectcoins", "%s", FormatMoney(vValue[i].first));
LogPrint("selectcoins", "total %s\n", FormatMoney(nBest));
}
return true;
}
bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, bool& fOnlyCoinbaseCoinsRet, bool& fNeedCoinbaseCoinsRet, const CCoinControl* coinControl) const
{
// Output parameter fOnlyCoinbaseCoinsRet is set to true when the only available coins are coinbase utxos.
uint64_t tmp; int32_t retval;
vector<COutput> vCoinsNoCoinbase, vCoinsWithCoinbase;
AvailableCoins(vCoinsNoCoinbase, true, coinControl, false, false);
AvailableCoins(vCoinsWithCoinbase, true, coinControl, false, true);
fOnlyCoinbaseCoinsRet = vCoinsNoCoinbase.size() == 0 && vCoinsWithCoinbase.size() > 0;
// If coinbase utxos can only be sent to zaddrs, exclude any coinbase utxos from coin selection.
bool fProtectCoinbase = Params().GetConsensus().fCoinbaseMustBeProtected;
vector<COutput> vCoins = (fProtectCoinbase) ? vCoinsNoCoinbase : vCoinsWithCoinbase;
// Output parameter fNeedCoinbaseCoinsRet is set to true if coinbase utxos need to be spent to meet target amount
if (fProtectCoinbase && vCoinsWithCoinbase.size() > vCoinsNoCoinbase.size()) {
CAmount value = 0;
for (const COutput& out : vCoinsNoCoinbase) {
if (!out.fSpendable) {
continue;
}
value += out.tx->vout[out.i].nValue;
value += out.tx->vout[out.i].interest;
}
if (value <= nTargetValue) {
CAmount valueWithCoinbase = 0;
for (const COutput& out : vCoinsWithCoinbase) {
if (!out.fSpendable) {
continue;
}
valueWithCoinbase += out.tx->vout[out.i].nValue;
valueWithCoinbase += out.tx->vout[out.i].interest;
}
fNeedCoinbaseCoinsRet = (valueWithCoinbase >= nTargetValue);
}
}
// coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs)
{
BOOST_FOREACH(const COutput& out, vCoins)
{
if (!out.fSpendable)
continue;
nValueRet += out.tx->vout[out.i].nValue;
setCoinsRet.insert(make_pair(out.tx, out.i));
}
return (nValueRet >= nTargetValue);
}
// calculate value from preset inputs and store them
set<pair<const CWalletTx*, uint32_t> > setPresetCoins;
CAmount nValueFromPresetInputs = 0;
std::vector<COutPoint> vPresetInputs;
if (coinControl)
coinControl->ListSelected(vPresetInputs);
BOOST_FOREACH(const COutPoint& outpoint, vPresetInputs)
{
map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash);
if (it != mapWallet.end())
{
const CWalletTx* pcoin = &it->second;
// Clearly invalid input, fail
if (pcoin->vout.size() <= outpoint.n)
return false;
nValueFromPresetInputs += pcoin->vout[outpoint.n].nValue;
nValueFromPresetInputs += pcoin->vout[outpoint.n].interest;
setPresetCoins.insert(make_pair(pcoin, outpoint.n));
} else
return false; // TODO: Allow non-wallet inputs
}
// remove preset inputs from vCoins
for (vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();)
{
if (setPresetCoins.count(make_pair(it->tx, it->i)))
it = vCoins.erase(it);
else
++it;
}
retval = false;
if ( nTargetValue <= nValueFromPresetInputs )
retval = true;
else if ( SelectCoinsMinConf(nTargetValue, 1, 6, vCoins, setCoinsRet, nValueRet) != 0 )
retval = true;
else if ( SelectCoinsMinConf(nTargetValue, 1, 1, vCoins, setCoinsRet, nValueRet) != 0 )
retval = true;
else if ( bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet) != 0 )
retval = true;
// because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset
setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end());
// add preset inputs to the total value selected
nValueRet += nValueFromPresetInputs;
return retval;
}
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason)
{
vector<CRecipient> vecSend;
// Turn the txout set into a CRecipient vector
BOOST_FOREACH(const CTxOut& txOut, tx.vout)
{
CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, false};
vecSend.push_back(recipient);
}
CCoinControl coinControl;
coinControl.fAllowOtherInputs = true;
BOOST_FOREACH(const CTxIn& txin, tx.vin)
coinControl.Select(txin.prevout);
CReserveKey reservekey(this);
CWalletTx wtx;
if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosRet, strFailReason, &coinControl, false))
return false;
if (nChangePosRet != -1)
tx.vout.insert(tx.vout.begin() + nChangePosRet, wtx.vout[nChangePosRet]);
// Add new txins (keeping original txin scriptSig/order)
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
{
bool found = false;
BOOST_FOREACH(const CTxIn& origTxIn, tx.vin)
{
if (txin.prevout.hash == origTxIn.prevout.hash && txin.prevout.n == origTxIn.prevout.n)
{
found = true;
break;
}
}
if (!found)
tx.vin.push_back(txin);
}
return true;
}
bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign)
{
CAmount nValue = 0; unsigned int nSubtractFeeFromAmount = 0;
BOOST_FOREACH (const CRecipient& recipient, vecSend)
{
if (nValue < 0 || recipient.nAmount < 0)
{
strFailReason = _("Transaction amounts must be positive");
return false;
}
nValue += recipient.nAmount;
if (recipient.fSubtractFeeFromAmount)
nSubtractFeeFromAmount++;
}
if (vecSend.empty() || nValue < 0)
{
strFailReason = _("Transaction amounts must be positive");
return false;
}
wtxNew.fTimeReceivedIsTxTime = true;
wtxNew.BindWallet(this);
int nextBlockHeight = chainActive.Height() + 1;
CMutableTransaction txNew = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nextBlockHeight);
if ( !hush_hardfork_active((uint32_t)chainActive.LastTip()->nTime) )
txNew.nLockTime = (uint32_t)chainActive.LastTip()->nTime + 1; // set to a time close to now
else
txNew.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast();
// Activates after Overwinter network upgrade
const bool overwinter = nextBlockHeight >=1 ? true : false;
if (overwinter) { // NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) {
if (txNew.nExpiryHeight >= TX_EXPIRY_HEIGHT_THRESHOLD){
strFailReason = _("nExpiryHeight must be less than TX_EXPIRY_HEIGHT_THRESHOLD.");
return false;
}
}
unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING;
/* Sapling is always active since height=1
if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING;
}
*/
/*
// Discourage fee sniping.
//
// However because of a off-by-one-error in previous versions we need to
// neuter it by setting nLockTime to at least one less than nBestHeight.
// Secondly currently propagation of transactions created for block heights
// corresponding to blocks that were just mined may be iffy - transactions
// aren't re-accepted into the mempool - we additionally neuter the code by
// going ten blocks back. Doesn't yet do anything for sniping, but does act
// to shake out wallet bugs like not showing nLockTime'd transactions at
// all.
txNew.nLockTime = std::max(0, chainActive.Height() - 10);
// Secondly occasionally randomly pick a nLockTime even further back, so
// that transactions that are delayed after signing for whatever reason,
// e.g. high-latency mix networks and some CoinJoin implementations, have
// better privacy.
if (GetRandInt(10) == 0)
txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100));
assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);*/
{
LOCK2(cs_main, cs_wallet);
{
nFeeRet = 0;
while (true)
{
//interest = 0;
txNew.vin.clear();
txNew.vout.clear();
wtxNew.fFromMe = true;
nChangePosRet = -1;
bool fFirst = true;
CAmount nTotalValue = nValue;
if (nSubtractFeeFromAmount == 0)
nTotalValue += nFeeRet;
double dPriority = 0;
// vouts to the payees
BOOST_FOREACH (const CRecipient& recipient, vecSend)
{
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
if (recipient.fSubtractFeeFromAmount)
{
txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
if (fFirst) // first receiver pays the remainder not divisible by output count
{
fFirst = false;
txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
}
}
if (txout.IsDust(::minRelayTxFee))
{
if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
{
if (txout.nValue < 0)
strFailReason = _("The transaction amount is too small to pay the fee");
else
strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
}
else
strFailReason = _("Transaction amount too small");
return false;
}
txNew.vout.push_back(txout);
}
// Choose coins to use
set<pair<const CWalletTx*,unsigned int> > setCoins;
CAmount nValueIn = 0;
bool fOnlyCoinbaseCoins = false;
bool fNeedCoinbaseCoins = false;
if (!SelectCoins(nTotalValue, setCoins, nValueIn, fOnlyCoinbaseCoins, fNeedCoinbaseCoins, coinControl))
{
if (fOnlyCoinbaseCoins && Params().GetConsensus().fCoinbaseMustBeProtected) {
strFailReason = _("Coinbase funds can only be sent to a zaddr");
} else if (fNeedCoinbaseCoins) {
strFailReason = _("Insufficient funds, coinbase funds can only be spent after they have been sent to a zaddr");
} else {
strFailReason = _("Insufficient funds");
}
return false;
}
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
CAmount nCredit = pcoin.first->vout[pcoin.second].nValue;
//The coin age after the next block (depth+1) is used instead of the current,
//reflecting an assumption the user would accept a bit more delay for
//a chance at a free transaction.
//But mempool inputs might still be in the mempool, so their age stays 0
int age = pcoin.first->GetDepthInMainChain();
if (age != 0)
age += 1;
dPriority += (double)nCredit * age;
}
CAmount nChange = (nValueIn - nValue);
if (nSubtractFeeFromAmount == 0)
nChange -= nFeeRet;
if (nChange > 0)
{
// Fill a vout to ourself
// TODO: pass in scriptChange instead of reservekey so
// change transaction isn't always pay-to-bitcoin-address
CScript scriptChange;
// coin control: send change to custom address
if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
scriptChange = GetScriptForDestination(coinControl->destChange);
// no coin control: send change to newly generated address
else
{
// Note: We use a new key here to keep it from being obvious which side is the change.
// The drawback is that by not reusing a previous key, the change may be lost if a
// backup is restored, if the backup doesn't have the new private key for the change.
// If we reused the old key, it would be possible to add code to look for and
// rediscover unknown transactions that were written with keys of ours to recover
// post-backup change.
// Reserve a new key pair from key pool
CPubKey vchPubKey;
extern int32_t USE_EXTERNAL_PUBKEY; extern std::string NOTARY_PUBKEY;
if ( USE_EXTERNAL_PUBKEY == 0 )
{
bool ret;
ret = reservekey.GetReservedKey(vchPubKey);
assert(ret); // should never fail, as we just unlocked
scriptChange = GetScriptForDestination(vchPubKey.GetID());
}
else
{
scriptChange = CScript() << ParseHex(NOTARY_PUBKEY) << OP_CHECKSIG;
}
}
CTxOut newTxOut(nChange, scriptChange);
// We do not move dust-change to fees, because the sender would end up paying more than requested.
// This would be against the purpose of the all-inclusive feature.
// So instead we raise the change and deduct from the recipient.
if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(::minRelayTxFee))
{
CAmount nDust = newTxOut.GetDustThreshold(::minRelayTxFee) - newTxOut.nValue;
newTxOut.nValue += nDust; // raise change until no more dust
for (unsigned int i = 0; i < vecSend.size(); i++) // subtract from first recipient
{
if (vecSend[i].fSubtractFeeFromAmount)
{
txNew.vout[i].nValue -= nDust;
if (txNew.vout[i].IsDust(::minRelayTxFee))
{
strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
return false;
}
break;
}
}
}
// Never create dust outputs; if we would, just
// add the dust to the fee.
if (newTxOut.IsDust(::minRelayTxFee))
{
nFeeRet += nChange;
reservekey.ReturnKey();
}
else
{
nChangePosRet = txNew.vout.size() - 1; // dont change first or last
vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosRet;
txNew.vout.insert(position, newTxOut);
}
} else reservekey.ReturnKey();
// Fill vin
//
// Note how the sequence number is set to max()-1 so that the
// nLockTime set above actually works.
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second,CScript(),
std::numeric_limits<unsigned int>::max()-1));
// DragonX always has the Overwinter NU active and so this option was never used
// Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects
// const size_t limit = 0; // (size_t)GetArg("-mempooltxinputlimit", 0);
//{
// LOCK(cs_main);
//const bool overwinter = true;
//if (NetworkUpgradeActive(chainActive.Height() + 1, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) {
// limit = 0;
//}
//}
/*
if (limit > 0) {
size_t n = txNew.vin.size();
if (n > limit) {
strFailReason = _(strprintf("Too many transparent inputs %zu > limit %zu", n, limit).c_str());
return false;
}
}
*/
// Grab the current consensus branch ID
auto consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
// Sign
int nIn = 0;
CTransaction txNewConst(txNew);
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
{
bool signSuccess;
const CScript& scriptPubKey = coin.first->vout[coin.second].scriptPubKey;
SignatureData sigdata;
if (sign)
signSuccess = ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, coin.first->vout[coin.second].nValue, SIGHASH_ALL), scriptPubKey, sigdata, consensusBranchId);
else
signSuccess = ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata, consensusBranchId);
if (!signSuccess)
{
strFailReason = _("Signing transaction failed");
return false;
} else {
UpdateTransaction(txNew, nIn, sigdata);
}
nIn++;
}
unsigned int nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION);
// Remove scriptSigs if we used dummy signatures for fee calculation
if (!sign) {
BOOST_FOREACH (CTxIn& vin, txNew.vin)
vin.scriptSig = CScript();
}
// Embed the constructed transaction data in wtxNew.
*static_cast<CTransaction*>(&wtxNew) = CTransaction(txNew);
// Limit size
if (nBytes >= max_tx_size)
{
strFailReason = _("Transaction too large");
return false;
}
dPriority = wtxNew.ComputePriority(dPriority, nBytes);
// Can we complete this as a free transaction?
if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE)
{
// Not enough fee: enough priority?
double dPriorityNeeded = mempool.estimatePriority(nTxConfirmTarget);
// Not enough mempool history to estimate: use hard-coded AllowFree.
if (dPriorityNeeded <= 0 && AllowFree(dPriority))
break;
// Small enough, and priority high enough, to send for free
if (dPriorityNeeded > 0 && dPriority >= dPriorityNeeded)
break;
}
CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
if ( nFeeNeeded < 5000 )
nFeeNeeded = 5000;
// If we made it here and we aren't even able to meet the relay fee on the next pass, give up
// because we must be at the maximum allowed fee.
if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes))
{
strFailReason = _("Transaction too large for fee policy");
return false;
}
if (nFeeRet >= nFeeNeeded)
break; // Done, enough fee included.
// Include more fee and try again.
nFeeRet = nFeeNeeded;
continue;
}
}
}
return true;
}
// Call after CreateTransaction unless you want to abort
bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
{
{
LOCK2(cs_main, cs_wallet);
LogPrintf("CommitTransaction:\n%s", wtxNew.ToString());
{
// This is only to keep the database open to defeat the auto-flush for the
// duration of this scope. This is the only place where this optimization
// maybe makes sense; please don't do it anywhere else.
CWalletDB* pwalletdb = fFileBacked ? new CWalletDB(strWalletFile,"r+") : NULL;
// Take key pair from key pool so it won't be used again
reservekey.KeepKey();
// Add tx to wallet, because if it has change it's also ours,
// otherwise just for transaction history.
if (!AddToWallet(wtxNew, false, pwalletdb))
LogPrintf("CommitTransaction(): Error: failed to persist wallet tx %s to disk; wallet may be out of sync with the ledger\n", wtxNew.GetHash().ToString());
// Notify that old coins are spent
set<CWalletTx*> setCoins;
BOOST_FOREACH(const CTxIn& txin, wtxNew.vin)
{
CWalletTx &coin = mapWallet[txin.prevout.hash];
coin.BindWallet(this);
NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED);
}
if (fFileBacked)
delete pwalletdb;
}
std::string strCmd = GetArg("-txsend", "");
if (fBroadcastTransactions)
{
// Broadcast
if (!wtxNew.AcceptToMemoryPool(false))
{
// This must not fail. The transaction has already been signed and recorded.
LogPrintf("CommitTransaction(): Error: Transaction not valid\n");
return false;
}
wtxNew.RelayWalletTransaction();
}
// If we are configured to send transactions via an
// external service instead of broadcasting, do that
else if (!strCmd.empty()) {
boost::replace_all(strCmd, "%s", EncodeHexTx(wtxNew));
boost::thread t(runCommand, strCmd); // thread runs free
}
}
return true;
}
CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool)
{
// payTxFee is user-set "I want to pay this much"
CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes);
// user selected total at least (default=true)
if (fPayAtLeastCustomFee && nFeeNeeded > 0 && nFeeNeeded < payTxFee.GetFeePerK())
nFeeNeeded = payTxFee.GetFeePerK();
// User didn't set: use -txconfirmtarget to estimate...
if (nFeeNeeded == 0)
nFeeNeeded = pool.estimateFee(nConfirmTarget).GetFee(nTxBytes);
// ... unless we don't have enough mempool data, in which case fall
// back to a hard-coded fee
if (nFeeNeeded == 0)
nFeeNeeded = minTxFee.GetFee(nTxBytes);
// prevent user from paying a non-sense fee (like 1 satoshi): 0 < fee < minRelayFee
if (nFeeNeeded < ::minRelayTxFee.GetFee(nTxBytes))
nFeeNeeded = ::minRelayTxFee.GetFee(nTxBytes);
// But always obey the maximum
if (nFeeNeeded > maxTxFee)
nFeeNeeded = maxTxFee;
return nFeeNeeded;
}
void hush_prefetch(FILE *fp);
DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
if (!fFileBacked)
return DB_LOAD_OK;
fFirstRunRet = false;
if ( 0 ) // doesnt help
{
LogPrintf("loading wallet %s %u\n",strWalletFile.c_str(),(uint32_t)time(NULL));
FILE *fp;
if ( (fp= fopen(strWalletFile.c_str(),"rb")) != 0 )
{
hush_prefetch(fp);
fclose(fp);
}
}
DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this);
if (nLoadWalletRet == DB_NEED_REWRITE)
{
if (CDB::Rewrite(strWalletFile, "\x04pool"))
{
LOCK(cs_wallet);
setKeyPool.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
if (nLoadWalletRet != DB_LOAD_OK)
return nLoadWalletRet;
fFirstRunRet = !vchDefaultKey.IsValid();
uiInterface.LoadWallet(this);
return DB_LOAD_OK;
}
DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
{
if (!fFileBacked)
return DB_LOAD_OK;
DBErrors nZapWalletTxRet = CWalletDB(strWalletFile,"cr+").ZapWalletTx(this, vWtx);
if (nZapWalletTxRet == DB_NEED_REWRITE)
{
if (CDB::Rewrite(strWalletFile, "\x04pool"))
{
LOCK(cs_wallet);
setKeyPool.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
if (nZapWalletTxRet != DB_LOAD_OK)
return nZapWalletTxRet;
return DB_LOAD_OK;
}
bool CWallet::SetAddressBook(const CTxDestination& address, const string& strName, const string& strPurpose)
{
bool fUpdated = false;
{
LOCK(cs_wallet); // mapAddressBook
std::map<CTxDestination, CAddressBookData>::iterator mi = mapAddressBook.find(address);
fUpdated = mi != mapAddressBook.end();
mapAddressBook[address].name = strName;
if (!strPurpose.empty()) /* update purpose only if requested */
mapAddressBook[address].purpose = strPurpose;
}
NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO,
strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) );
if (!fFileBacked)
return false;
if (!strPurpose.empty() && !CWalletDB(strWalletFile).WritePurpose(EncodeDestination(address), strPurpose))
return false;
return CWalletDB(strWalletFile).WriteName(EncodeDestination(address), strName);
}
bool CWallet::DelAddressBook(const CTxDestination& address)
{
{
LOCK(cs_wallet); // mapAddressBook
if(fFileBacked)
{
// Delete destdata tuples associated with address
std::string strAddress = EncodeDestination(address);
BOOST_FOREACH(const PAIRTYPE(string, string) &item, mapAddressBook[address].destdata)
{
CWalletDB(strWalletFile).EraseDestData(strAddress, item.first);
}
}
mapAddressBook.erase(address);
}
NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address) != ISMINE_NO, "", CT_DELETED);
if (!fFileBacked)
return false;
CWalletDB(strWalletFile).ErasePurpose(EncodeDestination(address));
return CWalletDB(strWalletFile).EraseName(EncodeDestination(address));
}
bool CWallet::SetDefaultKey(const CPubKey &vchPubKey)
{
if (fFileBacked)
{
if (!CWalletDB(strWalletFile).WriteDefaultKey(vchPubKey))
return false;
}
vchDefaultKey = vchPubKey;
return true;
}
/**
* Mark old keypool keys as used,
* and generate all new keys
*/
bool CWallet::NewKeyPool()
{
{
LOCK(cs_wallet);
CWalletDB walletdb(strWalletFile);
BOOST_FOREACH(int64_t nIndex, setKeyPool)
walletdb.ErasePool(nIndex);
setKeyPool.clear();
if (IsLocked())
return false;
int64_t nKeys = max(GetArg("-keypool", 100), (int64_t)0);
for (int i = 0; i < nKeys; i++)
{
int64_t nIndex = i+1;
walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey()));
setKeyPool.insert(nIndex);
}
LogPrintf("CWallet::NewKeyPool wrote %d new keys\n", nKeys);
}
return true;
}
bool CWallet::TopUpKeyPool(unsigned int kpSize)
{
{
LOCK(cs_wallet);
if (IsLocked())
return false;
CWalletDB walletdb(strWalletFile);
// Top up key pool
unsigned int nTargetSize;
if (kpSize > 0)
nTargetSize = kpSize;
else
nTargetSize = max(GetArg("-keypool", 100), (int64_t) 0);
while (setKeyPool.size() < (nTargetSize + 1))
{
int64_t nEnd = 1;
if (!setKeyPool.empty())
nEnd = *(--setKeyPool.end()) + 1;
if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey())))
throw runtime_error("TopUpKeyPool(): writing generated key failed");
setKeyPool.insert(nEnd);
LogPrintf("keypool added key %d, size=%u\n", nEnd, setKeyPool.size());
}
}
return true;
}
void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool)
{
nIndex = -1;
keypool.vchPubKey = CPubKey();
{
LOCK(cs_wallet);
if (!IsLocked())
TopUpKeyPool();
// Get the oldest key
if(setKeyPool.empty())
return;
CWalletDB walletdb(strWalletFile);
nIndex = *(setKeyPool.begin());
setKeyPool.erase(setKeyPool.begin());
if (!walletdb.ReadPool(nIndex, keypool))
throw runtime_error("ReserveKeyFromKeyPool(): read failed");
if (!HaveKey(keypool.vchPubKey.GetID()))
throw runtime_error("ReserveKeyFromKeyPool(): unknown key in key pool");
assert(keypool.vchPubKey.IsValid());
}
}
void CWallet::KeepKey(int64_t nIndex)
{
// Remove from key pool
if (fFileBacked)
{
CWalletDB walletdb(strWalletFile);
walletdb.ErasePool(nIndex);
}
LogPrintf("keypool keep %d\n", nIndex);
}
void CWallet::ReturnKey(int64_t nIndex)
{
// Return to key pool
{
LOCK(cs_wallet);
setKeyPool.insert(nIndex);
}
}
bool CWallet::GetKeyFromPool(CPubKey& result)
{
int64_t nIndex = 0;
CKeyPool keypool;
{
LOCK(cs_wallet);
ReserveKeyFromKeyPool(nIndex, keypool);
if (nIndex == -1)
{
if (IsLocked()) return false;
result = GenerateNewKey();
return true;
}
KeepKey(nIndex);
result = keypool.vchPubKey;
}
return true;
}
int64_t CWallet::GetOldestKeyPoolTime()
{
int64_t nIndex = 0;
CKeyPool keypool;
ReserveKeyFromKeyPool(nIndex, keypool);
if (nIndex == -1)
return GetTime();
ReturnKey(nIndex);
return keypool.nTime;
}
std::map<CTxDestination, CAmount> CWallet::GetAddressBalances()
{
map<CTxDestination, CAmount> balances;
{
LOCK(cs_wallet);
BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet)
{
CWalletTx *pcoin = &walletEntry.second;
if (!CheckFinalTx(*pcoin) || !pcoin->IsTrusted())
continue;
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
continue;
int nDepth = pcoin->GetDepthInMainChain();
if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1))
continue;
for (unsigned int i = 0; i < pcoin->vout.size(); i++)
{
CTxDestination addr;
if (!IsMine(pcoin->vout[i]))
continue;
if(!ExtractDestination(pcoin->vout[i].scriptPubKey, addr))
continue;
CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->vout[i].nValue;
if (!balances.count(addr))
balances[addr] = 0;
balances[addr] += n;
}
}
}
return balances;
}
set< set<CTxDestination> > CWallet::GetAddressGroupings()
{
AssertLockHeld(cs_wallet); // mapWallet
set< set<CTxDestination> > groupings;
set<CTxDestination> grouping;
BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet)
{
CWalletTx *pcoin = &walletEntry.second;
if (pcoin->vin.size() > 0)
{
bool any_mine = false;
// group all input addresses with each other
BOOST_FOREACH(CTxIn txin, pcoin->vin)
{
CTxDestination address;
if(!IsMine(txin)) /* If this input isn't mine, ignore it */
continue;
if(!ExtractDestination(mapWallet[txin.prevout.hash].vout[txin.prevout.n].scriptPubKey, address))
continue;
grouping.insert(address);
any_mine = true;
}
// group change with input addresses
if (any_mine)
{
BOOST_FOREACH(CTxOut txout, pcoin->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 (unsigned int i = 0; i < pcoin->vout.size(); i++)
if (IsMine(pcoin->vout[i]))
{
CTxDestination address;
if(!ExtractDestination(pcoin->vout[i].scriptPubKey, address))
continue;
grouping.insert(address);
groupings.insert(grouping);
grouping.clear();
}
}
set< set<CTxDestination>* > uniqueGroupings; // a set of pointers to groups of addresses
map< CTxDestination, set<CTxDestination>* > setmap; // map addresses to the unique group containing it
BOOST_FOREACH(set<CTxDestination> grouping, groupings)
{
// make a set of all the groups hit by this new group
set< set<CTxDestination>* > hits;
map< CTxDestination, set<CTxDestination>* >::iterator it;
BOOST_FOREACH(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
set<CTxDestination>* merged = new set<CTxDestination>(grouping);
BOOST_FOREACH(set<CTxDestination>* hit, hits)
{
merged->insert(hit->begin(), hit->end());
uniqueGroupings.erase(hit);
delete hit;
}
uniqueGroupings.insert(merged);
// update setmap
BOOST_FOREACH(CTxDestination element, *merged)
setmap[element] = merged;
}
set< set<CTxDestination> > ret;
BOOST_FOREACH(set<CTxDestination>* uniqueGrouping, uniqueGroupings)
{
ret.insert(*uniqueGrouping);
delete uniqueGrouping;
}
return ret;
}
std::set<CTxDestination> CWallet::GetAccountAddresses(const std::string& strAccount) const
{
LOCK(cs_wallet);
set<CTxDestination> result;
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, mapAddressBook)
{
const CTxDestination& address = item.first;
const string& strName = item.second.name;
if (strName == strAccount)
result.insert(address);
}
return result;
}
bool CReserveKey::GetReservedKey(CPubKey& pubkey)
{
if (nIndex == -1)
{
CKeyPool keypool;
pwallet->ReserveKeyFromKeyPool(nIndex, keypool);
if (nIndex != -1)
vchPubKey = keypool.vchPubKey;
else {
return false;
}
}
assert(vchPubKey.IsValid());
pubkey = vchPubKey;
return true;
}
void CReserveKey::KeepKey()
{
if (nIndex != -1)
pwallet->KeepKey(nIndex);
nIndex = -1;
vchPubKey = CPubKey();
}
void CReserveKey::ReturnKey()
{
if (nIndex != -1)
pwallet->ReturnKey(nIndex);
nIndex = -1;
vchPubKey = CPubKey();
}
void CWallet::GetAllReserveKeys(set<CKeyID>& setAddress) const
{
setAddress.clear();
CWalletDB walletdb(strWalletFile);
LOCK2(cs_main, cs_wallet);
BOOST_FOREACH(const int64_t& id, setKeyPool)
{
CKeyPool keypool;
if (!walletdb.ReadPool(id, keypool))
throw runtime_error("GetAllReserveKeyHashes(): read failed");
assert(keypool.vchPubKey.IsValid());
CKeyID keyID = keypool.vchPubKey.GetID();
if (!HaveKey(keyID))
throw runtime_error("GetAllReserveKeyHashes(): unknown key in key pool");
setAddress.insert(keyID);
}
}
void CWallet::UpdatedTransaction(const uint256 &hashTx)
{
{
LOCK(cs_wallet);
// Only notify UI if this transaction is in this wallet
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(hashTx);
if (mi != mapWallet.end())
NotifyTransactionChanged(this, hashTx, CT_UPDATED);
}
}
void CWallet::LockCoin(COutPoint& output)
{
AssertLockHeld(cs_wallet); // setLockedCoins
setLockedCoins.insert(output);
}
void CWallet::UnlockCoin(COutPoint& output)
{
AssertLockHeld(cs_wallet); // setLockedCoins
setLockedCoins.erase(output);
}
void CWallet::UnlockAllCoins()
{
AssertLockHeld(cs_wallet); // setLockedCoins
setLockedCoins.clear();
}
bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const
{
AssertLockHeld(cs_wallet); // setLockedCoins
COutPoint outpt(hash, n);
return (setLockedCoins.count(outpt) > 0);
}
void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts)
{
AssertLockHeld(cs_wallet); // setLockedCoins
for (std::set<COutPoint>::iterator it = setLockedCoins.begin();
it != setLockedCoins.end(); it++) {
COutPoint outpt = (*it);
vOutpts.push_back(outpt);
}
}
// Note Locking Operations
void CWallet::LockNote(const SaplingOutPoint& output)
{
AssertLockHeld(cs_wallet);
setLockedSaplingNotes.insert(output);
LogPrintf("%s: locking note %s...\n", __func__, output.hash.ToString().substr(0,8).c_str() );
}
void CWallet::UnlockNote(const SaplingOutPoint& output)
{
AssertLockHeld(cs_wallet);
setLockedSaplingNotes.erase(output);
LogPrintf("%s: unlocking note %s...\n", __func__, output.hash.ToString().substr(0,8).c_str() );
}
void CWallet::UnlockAllSaplingNotes()
{
AssertLockHeld(cs_wallet);
setLockedSaplingNotes.clear();
}
bool CWallet::IsLockedNote(const SaplingOutPoint& output) const
{
AssertLockHeld(cs_wallet);
return (setLockedSaplingNotes.count(output) > 0);
}
std::vector<SaplingOutPoint> CWallet::ListLockedSaplingNotes()
{
AssertLockHeld(cs_wallet);
std::vector<SaplingOutPoint> vOutputs(setLockedSaplingNotes.begin(), setLockedSaplingNotes.end());
return vOutputs;
}
/** @} */ // end of Actions
class CAffectedKeysVisitor : public boost::static_visitor<void> {
private:
const CKeyStore &keystore;
std::vector<CKeyID> &vKeys;
public:
CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector<CKeyID> &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {}
void Process(const CScript &script) {
txnouttype type;
std::vector<CTxDestination> vDest;
int nRequired;
if (ExtractDestinations(script, type, vDest, nRequired)) {
BOOST_FOREACH(const CTxDestination &dest, vDest)
boost::apply_visitor(*this, dest);
}
}
void operator()(const CKeyID &keyId) {
if (keystore.HaveKey(keyId))
vKeys.push_back(keyId);
}
void operator()(const CPubKey &key) {
CKeyID keyId = key.GetID();
if (keystore.HaveKey(keyId))
vKeys.push_back(keyId);
}
void operator()(const CScriptID &scriptId) {
CScript script;
if (keystore.GetCScript(scriptId, script))
Process(script);
}
void operator()(const CNoDestination &none) {}
};
void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const {
AssertLockHeld(cs_wallet); // mapKeyMetadata
mapKeyBirth.clear();
// get birth times for keys with metadata
for (std::map<CKeyID, CKeyMetadata>::const_iterator it = mapKeyMetadata.begin(); it != mapKeyMetadata.end(); it++)
if (it->second.nCreateTime)
mapKeyBirth[it->first] = it->second.nCreateTime;
// map in which we'll infer heights of other keys
CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganised; use a 144-block safety margin
std::map<CKeyID, CBlockIndex*> mapKeyFirstBlock;
std::set<CKeyID> setKeys;
GetKeys(setKeys);
BOOST_FOREACH(const CKeyID &keyid, setKeys) {
if (mapKeyBirth.count(keyid) == 0)
mapKeyFirstBlock[keyid] = pindexMax;
}
setKeys.clear();
// if there are no such keys, we're done
if (mapKeyFirstBlock.empty())
return;
// find first block that affects those keys, if there are any left
std::vector<CKeyID> vAffected;
for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); it++) {
// iterate over all wallet transactions...
const CWalletTx &wtx = (*it).second;
BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock);
if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) {
// ... which are already in a block
int nHeight = blit->second->GetHeight();
BOOST_FOREACH(const CTxOut &txout, wtx.vout) {
// iterate over all their outputs
CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey);
BOOST_FOREACH(const CKeyID &keyid, vAffected) {
// ... and all their affected keys
std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->GetHeight())
rit->second = blit->second;
}
vAffected.clear();
}
}
}
// Extract block timestamps for those keys
for (std::map<CKeyID, CBlockIndex*>::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++)
mapKeyBirth[it->first] = it->second->GetBlockTime() - 7200; // block times can be 2h off
}
bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
{
if (boost::get<CNoDestination>(&dest))
return false;
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
if (!fFileBacked)
return true;
return CWalletDB(strWalletFile).WriteDestData(EncodeDestination(dest), key, value);
}
bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key)
{
if (!mapAddressBook[dest].destdata.erase(key))
return false;
if (!fFileBacked)
return true;
return CWalletDB(strWalletFile).EraseDestData(EncodeDestination(dest), key);
}
bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
{
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
return true;
}
bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const
{
std::map<CTxDestination, CAddressBookData>::const_iterator i = mapAddressBook.find(dest);
if(i != mapAddressBook.end())
{
CAddressBookData::StringMap::const_iterator j = i->second.destdata.find(key);
if(j != i->second.destdata.end())
{
if(value)
*value = j->second;
return true;
}
}
return false;
}
CKeyPool::CKeyPool()
{
nTime = GetTime();
}
CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn)
{
nTime = GetTime();
vchPubKey = vchPubKeyIn;
}
CWalletKey::CWalletKey(int64_t nExpires)
{
nTimeCreated = (nExpires ? GetTime() : 0);
nTimeExpires = nExpires;
}
void CMerkleTx::SetMerkleBranch(const CBlock& block)
{
CBlock blockTmp;
// Update the tx's hashBlock
hashBlock = block.GetHash();
// Locate the transaction
for (nIndex = 0; nIndex < (int)block.vtx.size(); nIndex++)
if (block.vtx[nIndex] == *(CTransaction*)this)
break;
if (nIndex == (int)block.vtx.size())
{
vMerkleBranch.clear();
nIndex = -1;
LogPrintf("ERROR: SetMerkleBranch(): couldn't find tx in block\n");
}
// Fill in merkle branch
vMerkleBranch = block.GetMerkleBranch(nIndex);
}
int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
{
if (hashBlock.IsNull() || nIndex == -1)
return 0;
AssertLockHeld(cs_main);
// Find the block it claims to be in
BlockMap::iterator mi = mapBlockIndex.find(hashBlock);
if (mi == mapBlockIndex.end())
return 0;
CBlockIndex* pindex = (*mi).second;
if (!pindex || !chainActive.Contains(pindex))
return 0;
// Make sure the merkle branch connects to this block
if (!fMerkleVerified)
{
if (CBlock::CheckMerkleBranch(GetHash(), vMerkleBranch, nIndex) != pindex->hashMerkleRoot)
return 0;
fMerkleVerified = true;
}
pindexRet = pindex;
return chainActive.Height() - pindex->GetHeight() + 1;
}
int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
{
AssertLockHeld(cs_main);
int nResult = GetDepthInMainChainINTERNAL(pindexRet);
if (nResult == 0 && !mempool.exists(GetHash()))
return -1; // Not in chain, not in mempool
return nResult;
}
int CMerkleTx::GetBlocksToMaturity() const
{
if ( SMART_CHAIN_SYMBOL[0] == 0 )
COINBASE_MATURITY = _COINBASE_MATURITY;
if (!IsCoinBase())
return 0;
int32_t depth = GetDepthInMainChain();
int32_t ut = UnlockTime(0);
int32_t toMaturity = (ut - chainActive.Height()) < 0 ? 0 : ut - chainActive.Height();
ut = (COINBASE_MATURITY - depth) < 0 ? 0 : COINBASE_MATURITY - depth;
return(ut < toMaturity ? toMaturity : ut);
}
bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee)
{
CValidationState state;
return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, fRejectAbsurdFee);
}
/**
* Find notes in the wallet filtered by payment address, min depth and ability to spend.
* These notes are decrypted and added to the output parameter vector, outEntries.
*/
void CWallet::GetFilteredNotes(
std::vector<SaplingNoteEntry>& saplingEntries,
std::string address,
int minDepth,
bool ignoreSpent,
bool requireSpendingKey)
{
std::set<PaymentAddress> filterAddresses;
if (address.length() > 0) {
filterAddresses.insert(DecodePaymentAddress(address));
}
GetFilteredNotes(saplingEntries, filterAddresses, minDepth, INT_MAX, ignoreSpent, requireSpendingKey);
}
/**
* Find notes in the wallet filtered by payment addresses, min depth, max depth,
* if the note is spent, if a spending key is required, and if the notes are locked.
* These notes are decrypted and added to the output parameter vector, outEntries.
*/
void CWallet::GetFilteredNotes(
std::vector<SaplingNoteEntry>& saplingEntries,
std::set<PaymentAddress>& filterAddresses,
int minDepth,
int maxDepth,
bool ignoreSpent,
bool requireSpendingKey,
bool ignoreLocked)
{
LOCK2(cs_main, cs_wallet);
LogPrintf("%s ignoreLocked=%d\n", __func__, ignoreLocked);
for (auto & p : mapWallet) {
CWalletTx wtx = p.second;
// Filter the transactions before checking for notes
if (!CheckFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0)
continue;
if (minDepth > 1) {
int nHeight = tx_height(wtx.GetHash());
if ( nHeight == 0 ) {
continue;
}
int nDepth = wtx.GetDepthInMainChain();
int dpowconfs = hush_dpowconfs(nHeight,nDepth);
if ( dpowconfs < minDepth || dpowconfs > maxDepth) {
continue;
}
} else {
if ( wtx.GetDepthInMainChain() < minDepth ||
wtx.GetDepthInMainChain() > maxDepth) {
continue;
}
}
for (auto & pair : wtx.mapSaplingNoteData) {
SaplingOutPoint op = pair.first;
SaplingNoteData nd = pair.second;
auto maybe_pt = SaplingNotePlaintext::decrypt(
wtx.vShieldedOutput[op.n].encCiphertext,
nd.ivk,
wtx.vShieldedOutput[op.n].ephemeralKey,
wtx.vShieldedOutput[op.n].cm);
assert(static_cast<bool>(maybe_pt));
auto notePt = maybe_pt.get();
auto maybe_pa = nd.ivk.address(notePt.d);
assert(static_cast<bool>(maybe_pa));
auto pa = maybe_pa.get();
// skip notes which belong to a different payment address in the wallet
if (!(filterAddresses.empty() || filterAddresses.count(pa))) {
continue;
}
if (ignoreSpent && nd.nullifier && IsSaplingSpent(*nd.nullifier)) {
continue;
}
// skip notes which cannot be spent
if (requireSpendingKey) {
libzcash::SaplingIncomingViewingKey ivk;
libzcash::SaplingFullViewingKey fvk;
if (!(GetSaplingIncomingViewingKey(pa, ivk) &&
GetSaplingFullViewingKey(ivk, fvk) &&
HaveSaplingSpendingKey(fvk))) {
continue;
}
}
// skip locked notes
if (ignoreLocked && IsLockedNote(op)) {
LogPrintf("%s: skipping locked note %s\n", __func__, op.hash.ToString().substr(0,10).c_str());
continue;
}
auto note = notePt.note(nd.ivk).get();
saplingEntries.push_back(SaplingNoteEntry {
op, pa, note, notePt.memo(), wtx.GetDepthInMainChain() });
}
}
}
//
// Shielded key and address generalizations
//
bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::SaplingPaymentAddress &zaddr) const
{
libzcash::SaplingIncomingViewingKey ivk;
return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk);
}
bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::InvalidEncoding& no) const
{
return false;
}
bool PaymentAddressBelongsToWallet::operator()(const libzcash::SaplingPaymentAddress &zaddr) const
{
libzcash::SaplingIncomingViewingKey ivk;
// If we have a SaplingExtendedSpendingKey in the wallet, then we will
// also have the corresponding SaplingFullViewingKey.
return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) &&
m_wallet->HaveSaplingFullViewingKey(ivk);
}
bool PaymentAddressBelongsToWallet::operator()(const libzcash::InvalidEncoding& no) const
{
return false;
}
bool HaveSpendingKeyForPaymentAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) const
{
libzcash::SaplingIncomingViewingKey ivk;
libzcash::SaplingFullViewingKey fvk;
return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) &&
m_wallet->GetSaplingFullViewingKey(ivk, fvk) &&
m_wallet->HaveSaplingSpendingKey(fvk);
}
bool HaveSpendingKeyForPaymentAddress::operator()(const libzcash::InvalidEncoding& no) const
{
return false;
}
boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator()(
const libzcash::SaplingPaymentAddress &zaddr) const
{
libzcash::SaplingExtendedSpendingKey extsk;
if (m_wallet->GetSaplingExtendedSpendingKey(zaddr, extsk)) {
return libzcash::SpendingKey(extsk);
} else {
return boost::none;
}
}
boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator()(
const libzcash::InvalidEncoding& no) const
{
// Defaults to InvalidEncoding
return libzcash::SpendingKey();
}
SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedSpendingKey &sk) const {
auto fvk = sk.expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key();
auto addr = sk.DefaultAddress();
{
if (log){
LogPrint("zrpc", "Importing zaddr %s...\n", EncodePaymentAddress(addr));
}
// Don't throw error in case a key is already there
if (m_wallet->HaveSaplingSpendingKey(fvk)) {
return KeyAlreadyExists;
} else {
if (!m_wallet-> AddSaplingZKey(sk, addr)) {
return KeyNotAdded;
}
// Sapling addresses can't have been used in transactions prior to activation.
if (params.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight == Consensus::NetworkUpgrade::ALWAYS_ACTIVE) {
m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = nTime;
} else {
// TODO: set a better time for DragonX
// 154051200 seconds from epoch is Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates
m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = std::max((int64_t) 154051200, nTime);
}
if (hdKeypath) {
m_wallet->mapSaplingZKeyMetadata[ivk].hdKeypath = hdKeypath.get();
}
if (seedFpStr) {
uint256 seedFp;
seedFp.SetHex(seedFpStr.get());
m_wallet->mapSaplingZKeyMetadata[ivk].seedFp = seedFp;
}
return KeyAdded;
}
}
}
SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::InvalidEncoding& no) const {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key");
}