refactor: Make CWalletTx sync state type-safe

Current CWalletTx state representation makes it possible to set
inconsistent states that won't be handled correctly by wallet sync code
or serialized & deserialized back into the same form.

For example, it is possible to call setConflicted without setting a
conflicting block hash, or setConfirmed with no transaction index. And
it's possible update individual m_confirm and fInMempool data fields
without setting an overall consistent state that can be serialized and
handled correctly.

Fix this without changing behavior by using std::variant, instead of an
enum and collection of fields, to represent sync state, so state
tracking code is safer and more legible.

This is a first step to fixing state tracking bugs
https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking,
by adding an extra margin of safety that can prevent new bugs from being
introduced as existing bugs are fixed.
This commit is contained in:
Russell Yanofsky 2021-02-16 22:36:26 -05:00
parent 2efc8c0999
commit d8ee8f3cd3
16 changed files with 256 additions and 173 deletions

View file

@ -246,6 +246,7 @@ BITCOIN_CORE_H = \
util/macros.h \
util/message.h \
util/moneystr.h \
util/overloaded.h \
util/rbf.h \
util/readwritefile.h \
util/serfloat.h \

View file

@ -159,6 +159,7 @@ BITCOIN_TESTS += \
wallet/test/wallet_tests.cpp \
wallet/test/walletdb_tests.cpp \
wallet/test/wallet_crypto_tests.cpp \
wallet/test/wallet_transaction_tests.cpp \
wallet/test/coinselector_tests.cpp \
wallet/test/init_tests.cpp \
wallet/test/ismine_tests.cpp \

View file

@ -18,7 +18,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<st
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
tx.vout.resize(1);
tx.vout[0].nValue = nValue;
wtxs.push_back(std::make_unique<CWalletTx>(MakeTransactionRef(std::move(tx))));
wtxs.push_back(std::make_unique<CWalletTx>(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
}
// Simple benchmark for wallet coin selection. Note that it maybe be necessary

22
src/util/overloaded.h Normal file
View file

@ -0,0 +1,22 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_UTIL_OVERLOADED_H
#define BITCOIN_UTIL_OVERLOADED_H
namespace util {
//! Overloaded helper for std::visit. This helper and std::visit in general are
//! useful to write code that switches on a variant type. Unlike if/else-if and
//! switch/case statements, std::visit will trigger compile errors if there are
//! unhandled cases.
//!
//! Implementation comes from and example usage can be found at
//! https://en.cppreference.com/w/cpp/utility/variant/visit#Example
template<class... Ts> struct Overloaded : Ts... { using Ts::operator()...; };
//! Explicit deduction guide (not needed as of C++20)
template<class... Ts> Overloaded(Ts...) -> Overloaded<Ts...>;
} // namespace util
#endif // BITCOIN_UTIL_OVERLOADED_H

View file

@ -82,7 +82,10 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx)
{
WalletTxStatus result;
result.block_height = wtx.m_confirm.block_height > 0 ? wtx.m_confirm.block_height : std::numeric_limits<int>::max();
result.block_height =
wtx.state<TxStateConfirmed>() ? wtx.state<TxStateConfirmed>()->confirmed_block_height :
wtx.state<TxStateConflicted>() ? wtx.state<TxStateConflicted>()->conflicting_block_height :
std::numeric_limits<int>::max();
result.blocks_to_maturity = wallet.GetTxBlocksToMaturity(wtx);
result.depth_in_main_chain = wallet.GetTxDepthInMainChain(wtx);
result.time_received = wtx.nTimeReceived;

View file

@ -369,11 +369,9 @@ RPCHelpMan importprunedfunds()
unsigned int txnIndex = vIndex[it - vMatch.begin()];
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, merkleBlock.header.GetHash(), txnIndex);
CTransactionRef tx_ref = MakeTransactionRef(tx);
if (pwallet->IsMine(*tx_ref)) {
pwallet->AddToWallet(std::move(tx_ref), confirm);
pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)});
return NullUniValue;
}

View file

@ -166,13 +166,13 @@ static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue
entry.pushKV("confirmations", confirms);
if (wtx.IsCoinBase())
entry.pushKV("generated", true);
if (confirms > 0)
if (auto* conf = wtx.state<TxStateConfirmed>())
{
entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex());
entry.pushKV("blockheight", wtx.m_confirm.block_height);
entry.pushKV("blockindex", wtx.m_confirm.nIndex);
entry.pushKV("blockhash", conf->confirmed_block_hash.GetHex());
entry.pushKV("blockheight", conf->confirmed_block_height);
entry.pushKV("blockindex", conf->position_in_block);
int64_t block_time;
CHECK_NONFATAL(chain.findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(block_time)));
CHECK_NONFATAL(chain.findBlock(conf->confirmed_block_hash, FoundBlock().time(block_time)));
entry.pushKV("blocktime", block_time);
} else {
entry.pushKV("trusted", CachedTxIsTrusted(wallet, wtx));

View file

@ -74,7 +74,7 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
uint256 txid = tx.GetHash();
LOCK(wallet.cs_wallet);
auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx))));
auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
assert(ret.second);
CWalletTx& wtx = (*ret.first).second;
if (fIsFromMe)

View file

@ -34,12 +34,12 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION);
CTransactionRef prev_tx1;
s_prev_tx1 >> prev_tx1;
m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx1->GetHash()), std::forward_as_tuple(prev_tx1));
m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx1->GetHash()), std::forward_as_tuple(prev_tx1, TxStateInactive{}));
CDataStream s_prev_tx2(ParseHex("0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000"), SER_NETWORK, PROTOCOL_VERSION);
CTransactionRef prev_tx2;
s_prev_tx2 >> prev_tx2;
m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx2->GetHash()), std::forward_as_tuple(prev_tx2));
m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx2->GetHash()), std::forward_as_tuple(prev_tx2, TxStateInactive{}));
// Import descriptors for keys and scripts
import_descriptor(m_wallet, "sh(multi(2,xprv9s21ZrQH143K2LE7W4Xf3jATf9jECxSb7wj91ZnmY4qEJrS66Qru9RFqq8xbkgT32ya6HqYJweFdJUEDf5Q6JFV7jMiUws7kQfe6Tv4RbfN/0h/0h/0h,xprv9s21ZrQH143K2LE7W4Xf3jATf9jECxSb7wj91ZnmY4qEJrS66Qru9RFqq8xbkgT32ya6HqYJweFdJUEDf5Q6JFV7jMiUws7kQfe6Tv4RbfN/0h/0h/1h))");

View file

@ -330,7 +330,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
{
CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase());
CWalletTx wtx(m_coinbase_txns.back());
CWalletTx wtx{m_coinbase_txns.back(), TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*position_in_block=*/0}};
LOCK(wallet.cs_wallet);
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
@ -338,9 +338,6 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash(), 0);
wtx.m_confirm = confirm;
// Call GetImmatureCredit() once before adding the key to the wallet to
// cache the current immature credit amount, which is 0.
BOOST_CHECK_EQUAL(CachedTxGetImmatureCredit(wallet, wtx), 0);
@ -355,7 +352,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime)
{
CMutableTransaction tx;
CWalletTx::Confirmation confirm;
TxState state = TxStateInactive{};
tx.nLockTime = lockTime;
SetMockTime(mockTime);
CBlockIndex* block = nullptr;
@ -367,13 +364,13 @@ static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lock
block = inserted.first->second;
block->nTime = blockTime;
block->phashBlock = &hash;
confirm = {CWalletTx::Status::CONFIRMED, block->nHeight, hash, 0};
state = TxStateConfirmed{hash, block->nHeight, /*position_in_block=*/0};
}
// If transaction is already in map, to avoid inconsistencies, unconfirmation
// is needed before confirm again with different block.
return wallet.AddToWallet(MakeTransactionRef(tx), confirm, [&](CWalletTx& wtx, bool /* new_tx */) {
wtx.setUnconfirmed();
return wallet.AddToWallet(MakeTransactionRef(tx), state, [&](CWalletTx& wtx, bool /* new_tx */) {
// Assign wtx.m_state to simplify test and avoid the need to simulate
// reorg events. Without this, AddToWallet asserts false when the same
// transaction is confirmed in different blocks.
wtx.m_state = state;
return true;
})->nTimeSmart;
}
@ -534,8 +531,7 @@ public:
wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
auto it = wallet->mapWallet.find(tx->GetHash());
BOOST_CHECK(it != wallet->mapWallet.end());
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash(), 1);
it->second.m_confirm = confirm;
it->second.m_state = TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*position_in_block=*/1};
return it->second;
}

View file

@ -0,0 +1,24 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <wallet/transaction.h>
#include <wallet/test/wallet_test_fixture.h>
#include <boost/test/unit_test.hpp>
BOOST_FIXTURE_TEST_SUITE(wallet_transaction_tests, WalletTestingSetup)
BOOST_AUTO_TEST_CASE(roundtrip)
{
for (uint8_t hash = 0; hash < 5; ++hash) {
for (int index = -2; index < 3; ++index) {
TxState state = TxStateInterpretSerialized(TxStateUnrecognized{uint256{hash}, index});
BOOST_CHECK_EQUAL(TxStateSerializedBlockHash(state), uint256{hash});
BOOST_CHECK_EQUAL(TxStateSerializedIndex(state), index);
}
}
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -15,7 +15,7 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const
bool CWalletTx::InMempool() const
{
return fInMempool;
return state<TxStateInMempool>();
}
int64_t CWalletTx::GetTxTime() const

View file

@ -11,12 +11,102 @@
#include <wallet/ismine.h>
#include <threadsafety.h>
#include <tinyformat.h>
#include <util/overloaded.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <list>
#include <variant>
#include <vector>
//! State of transaction confirmed in a block.
struct TxStateConfirmed {
uint256 confirmed_block_hash;
int confirmed_block_height;
int position_in_block;
explicit TxStateConfirmed(const uint256& block_hash, int height, int index) : confirmed_block_hash(block_hash), confirmed_block_height(height), position_in_block(index) {}
};
//! State of transaction added to mempool.
struct TxStateInMempool {
};
//! State of rejected transaction that conflicts with a confirmed block.
struct TxStateConflicted {
uint256 conflicting_block_hash;
int conflicting_block_height;
explicit TxStateConflicted(const uint256& block_hash, int height) : conflicting_block_hash(block_hash), conflicting_block_height(height) {}
};
//! State of transaction not confirmed or conflicting with a known block and
//! not in the mempool. May conflict with the mempool, or with an unknown block,
//! or be abandoned, never broadcast, or rejected from the mempool for another
//! reason.
struct TxStateInactive {
bool abandoned;
explicit TxStateInactive(bool abandoned = false) : abandoned(abandoned) {}
};
//! State of transaction loaded in an unrecognized state with unexpected hash or
//! index values. Treated as inactive (with serialized hash and index values
//! preserved) by default, but may enter another state if transaction is added
//! to the mempool, or confirmed, or abandoned, or found conflicting.
struct TxStateUnrecognized {
uint256 block_hash;
int index;
TxStateUnrecognized(const uint256& block_hash, int index) : block_hash(block_hash), index(index) {}
};
//! All possible CWalletTx states
using TxState = std::variant<TxStateConfirmed, TxStateInMempool, TxStateConflicted, TxStateInactive, TxStateUnrecognized>;
//! Subset of states transaction sync logic is implemented to handle.
using SyncTxState = std::variant<TxStateConfirmed, TxStateInMempool, TxStateInactive>;
//! Try to interpret deserialized TxStateUnrecognized data as a recognized state.
static inline TxState TxStateInterpretSerialized(TxStateUnrecognized data)
{
if (data.block_hash == uint256::ZERO) {
if (data.index == 0) return TxStateInactive{};
} else if (data.block_hash == uint256::ONE) {
if (data.index == -1) return TxStateInactive{/*abandoned=*/true};
} else if (data.index >= 0) {
return TxStateConfirmed{data.block_hash, /*height=*/-1, data.index};
} else if (data.index == -1) {
return TxStateConflicted{data.block_hash, /*height=*/-1};
}
return data;
}
//! Get TxState serialized block hash. Inverse of TxStateInterpretSerialized.
static inline uint256 TxStateSerializedBlockHash(const TxState& state)
{
return std::visit(util::Overloaded{
[](const TxStateInactive& inactive) { return inactive.abandoned ? uint256::ONE : uint256::ZERO; },
[](const TxStateInMempool& in_mempool) { return uint256::ZERO; },
[](const TxStateConfirmed& confirmed) { return confirmed.confirmed_block_hash; },
[](const TxStateConflicted& conflicted) { return conflicted.conflicting_block_hash; },
[](const TxStateUnrecognized& unrecognized) { return unrecognized.block_hash; }
}, state);
}
//! Get TxState serialized block index. Inverse of TxStateInterpretSerialized.
static inline int TxStateSerializedIndex(const TxState& state)
{
return std::visit(util::Overloaded{
[](const TxStateInactive& inactive) { return inactive.abandoned ? -1 : 0; },
[](const TxStateInMempool& in_mempool) { return 0; },
[](const TxStateConfirmed& confirmed) { return confirmed.position_in_block; },
[](const TxStateConflicted& conflicted) { return -1; },
[](const TxStateUnrecognized& unrecognized) { return unrecognized.index; }
}, state);
}
typedef std::map<std::string, std::string> mapValue_t;
/** Legacy class used for deserializing vtxPrev for backwards compatibility.
@ -45,12 +135,6 @@ public:
*/
class CWalletTx
{
private:
/** Constant used in hashBlock to indicate tx has been abandoned, only used at
* serialization/deserialization to avoid ambiguity with conflicted.
*/
static constexpr const uint256& ABANDON_HASH = uint256::ONE;
public:
/**
* Key/value map with information about the transaction.
@ -111,11 +195,9 @@ public:
*/
mutable bool m_is_cache_empty{true};
mutable bool fChangeCached;
mutable bool fInMempool;
mutable CAmount nChangeCached;
CWalletTx(CTransactionRef arg)
: tx(std::move(arg))
CWalletTx(CTransactionRef tx, const TxState& state) : tx(std::move(tx)), m_state(state)
{
Init();
}
@ -129,44 +211,12 @@ public:
nTimeSmart = 0;
fFromMe = false;
fChangeCached = false;
fInMempool = false;
nChangeCached = 0;
nOrderPos = -1;
m_confirm = Confirmation{};
}
CTransactionRef tx;
/** New transactions start as UNCONFIRMED. At BlockConnected,
* they will transition to CONFIRMED. In case of reorg, at BlockDisconnected,
* they roll back to UNCONFIRMED. If we detect a conflicting transaction at
* block connection, we update conflicted tx and its dependencies as CONFLICTED.
* If tx isn't confirmed and outside of mempool, the user may switch it to ABANDONED
* by using the abandontransaction call. This last status may be override by a CONFLICTED
* or CONFIRMED transition.
*/
enum Status {
UNCONFIRMED,
CONFIRMED,
CONFLICTED,
ABANDONED
};
/** Confirmation includes tx status and a triplet of {block height/block hash/tx index in block}
* at which tx has been confirmed. All three are set to 0 if tx is unconfirmed or abandoned.
* Meaning of these fields changes with CONFLICTED state where they instead point to block hash
* and block height of the deepest conflicting tx.
*/
struct Confirmation {
Status status;
int block_height;
uint256 hashBlock;
int nIndex;
Confirmation(Status status = UNCONFIRMED, int block_height = 0, uint256 block_hash = uint256(), int block_index = 0)
: status{status}, block_height{block_height}, hashBlock{block_hash}, nIndex{block_index} {}
};
Confirmation m_confirm;
TxState m_state;
template<typename Stream>
void Serialize(Stream& s) const
@ -184,8 +234,8 @@ public:
std::vector<uint8_t> dummy_vector1; //!< Used to be vMerkleBranch
std::vector<uint8_t> dummy_vector2; //!< Used to be vtxPrev
bool dummy_bool = false; //!< Used to be fSpent
uint256 serializedHash = isAbandoned() ? ABANDON_HASH : m_confirm.hashBlock;
int serializedIndex = isAbandoned() || isConflicted() ? -1 : m_confirm.nIndex;
uint256 serializedHash = TxStateSerializedBlockHash(m_state);
int serializedIndex = TxStateSerializedIndex(m_state);
s << tx << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool;
}
@ -197,24 +247,11 @@ public:
std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch
std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev
bool dummy_bool; //! Used to be fSpent
uint256 serialized_block_hash;
int serializedIndex;
s >> tx >> m_confirm.hashBlock >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool;
s >> tx >> serialized_block_hash >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool;
/* At serialization/deserialization, an nIndex == -1 means that hashBlock refers to
* the earliest block in the chain we know this or any in-wallet ancestor conflicts
* with. If nIndex == -1 and hashBlock is ABANDON_HASH, it means transaction is abandoned.
* In same context, an nIndex >= 0 refers to a confirmed transaction (if hashBlock set) or
* unconfirmed one. Older clients interpret nIndex == -1 as unconfirmed for backward
* compatibility (pre-commit 9ac63d6).
*/
if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) {
setAbandoned();
} else if (serializedIndex == -1) {
setConflicted();
} else if (!m_confirm.hashBlock.IsNull()) {
m_confirm.nIndex = serializedIndex;
setConfirmed();
}
m_state = TxStateInterpretSerialized({serialized_block_hash, serializedIndex});
const auto it_op = mapValue.find("n");
nOrderPos = (it_op != mapValue.end()) ? LocaleIndependentAtoi<int64_t>(it_op->second) : -1;
@ -250,20 +287,13 @@ public:
int64_t GetTxTime() const;
bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; }
void setAbandoned()
{
m_confirm.status = CWalletTx::ABANDONED;
m_confirm.hashBlock = uint256();
m_confirm.block_height = 0;
m_confirm.nIndex = 0;
}
bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; }
void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; }
bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; }
void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; }
bool isConfirmed() const { return m_confirm.status == CWalletTx::CONFIRMED; }
void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; }
template<typename T> const T* state() const { return std::get_if<T>(&m_state); }
template<typename T> T* state() { return std::get_if<T>(&m_state); }
bool isAbandoned() const { return state<TxStateInactive>() && state<TxStateInactive>()->abandoned; }
bool isConflicted() const { return state<TxStateConflicted>(); }
bool isUnconfirmed() const { return !isAbandoned() && !isConflicted() && !isConfirmed(); }
bool isConfirmed() const { return state<TxStateConfirmed>(); }
const uint256& GetHash() const { return tx->GetHash(); }
bool IsCoinBase() const { return tx->IsCoinBase(); }

View file

@ -99,7 +99,11 @@ static void UpdateWalletSetting(interfaces::Chain& chain,
*/
static void RefreshMempoolStatus(CWalletTx& tx, interfaces::Chain& chain)
{
tx.fInMempool = chain.isInMempool(tx.GetHash());
if (chain.isInMempool(tx.GetHash())) {
tx.m_state = TxStateInMempool();
} else if (tx.state<TxStateInMempool>()) {
tx.m_state = TxStateInactive();
}
}
bool AddWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet)
@ -885,7 +889,7 @@ bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const
return false;
}
CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmation& confirm, const UpdateWalletTxFn& update_wtx, bool fFlushOnClose, bool rescanning_old_block)
CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const UpdateWalletTxFn& update_wtx, bool fFlushOnClose, bool rescanning_old_block)
{
LOCK(cs_wallet);
@ -906,12 +910,11 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
}
// Inserts only if not already there, returns tx inserted or tx found
auto ret = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(tx));
auto ret = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(tx, state));
CWalletTx& wtx = (*ret.first).second;
bool fInsertedNew = ret.second;
bool fUpdated = update_wtx && update_wtx(wtx, fInsertedNew);
if (fInsertedNew) {
wtx.m_confirm = confirm;
wtx.nTimeReceived = chain().getAdjustedTime();
wtx.nOrderPos = IncOrderPosNext(&batch);
wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
@ -921,16 +924,12 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
if (!fInsertedNew)
{
if (confirm.status != wtx.m_confirm.status) {
wtx.m_confirm.status = confirm.status;
wtx.m_confirm.nIndex = confirm.nIndex;
wtx.m_confirm.hashBlock = confirm.hashBlock;
wtx.m_confirm.block_height = confirm.block_height;
if (state.index() != wtx.m_state.index()) {
wtx.m_state = state;
fUpdated = true;
} else {
assert(wtx.m_confirm.nIndex == confirm.nIndex);
assert(wtx.m_confirm.hashBlock == confirm.hashBlock);
assert(wtx.m_confirm.block_height == confirm.block_height);
assert(TxStateSerializedIndex(wtx.m_state) == TxStateSerializedIndex(state));
assert(TxStateSerializedBlockHash(wtx.m_state) == TxStateSerializedBlockHash(state));
}
// If we have a witness-stripped version of this transaction, and we
// see a new version with a witness, then we must be upgrading a pre-segwit
@ -964,10 +963,10 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
if (!strCmd.empty())
{
boost::replace_all(strCmd, "%s", hash.GetHex());
if (confirm.status == CWalletTx::Status::CONFIRMED)
if (auto* conf = wtx.state<TxStateConfirmed>())
{
boost::replace_all(strCmd, "%b", confirm.hashBlock.GetHex());
boost::replace_all(strCmd, "%h", ToString(confirm.block_height));
boost::replace_all(strCmd, "%b", conf->confirmed_block_hash.GetHex());
boost::replace_all(strCmd, "%h", ToString(conf->confirmed_block_height));
} else {
boost::replace_all(strCmd, "%b", "unconfirmed");
boost::replace_all(strCmd, "%h", "-1");
@ -990,7 +989,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx)
{
const auto& ins = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(nullptr));
const auto& ins = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(nullptr, TxStateInactive{}));
CWalletTx& wtx = ins.first->second;
if (!fill_wtx(wtx, ins.second)) {
return false;
@ -998,22 +997,21 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx
// If wallet doesn't have a chain (e.g wallet-tool), don't bother to update txn.
if (HaveChain()) {
bool active;
int height;
if (chain().findBlock(wtx.m_confirm.hashBlock, FoundBlock().inActiveChain(active).height(height)) && active) {
// Update cached block height variable since it not stored in the
// serialized transaction.
wtx.m_confirm.block_height = height;
} else if (wtx.isConflicted() || wtx.isConfirmed()) {
auto lookup_block = [&](const uint256& hash, int& height, TxState& state) {
// If tx block (or conflicting block) was reorged out of chain
// while the wallet was shutdown, change tx status to UNCONFIRMED
// and reset block height, hash, and index. ABANDONED tx don't have
// associated blocks and don't need to be updated. The case where a
// transaction was reorged out while online and then reconfirmed
// while offline is covered by the rescan logic.
wtx.setUnconfirmed();
wtx.m_confirm.hashBlock = uint256();
wtx.m_confirm.block_height = 0;
wtx.m_confirm.nIndex = 0;
if (!chain().findBlock(hash, FoundBlock().inActiveChain(active).height(height)) || !active) {
state = TxStateInactive{};
}
};
if (auto* conf = wtx.state<TxStateConfirmed>()) {
lookup_block(conf->confirmed_block_hash, conf->confirmed_block_height, wtx.m_state);
} else if (auto* conf = wtx.state<TxStateConflicted>()) {
lookup_block(conf->conflicting_block_hash, conf->conflicting_block_height, wtx.m_state);
}
}
if (/* insertion took place */ ins.second) {
@ -1024,27 +1022,27 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx
auto it = mapWallet.find(txin.prevout.hash);
if (it != mapWallet.end()) {
CWalletTx& prevtx = it->second;
if (prevtx.isConflicted()) {
MarkConflicted(prevtx.m_confirm.hashBlock, prevtx.m_confirm.block_height, wtx.GetHash());
if (auto* prev = prevtx.state<TxStateConflicted>()) {
MarkConflicted(prev->conflicting_block_hash, prev->conflicting_block_height, wtx.GetHash());
}
}
}
return true;
}
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool fUpdate, bool rescanning_old_block)
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const SyncTxState& state, bool fUpdate, bool rescanning_old_block)
{
const CTransaction& tx = *ptx;
{
AssertLockHeld(cs_wallet);
if (!confirm.hashBlock.IsNull()) {
if (auto* conf = std::get_if<TxStateConfirmed>(&state)) {
for (const CTxIn& txin : tx.vin) {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout);
while (range.first != range.second) {
if (range.first->second != tx.GetHash()) {
WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), confirm.hashBlock.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n);
MarkConflicted(confirm.hashBlock, confirm.block_height, range.first->second);
WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), conf->confirmed_block_hash.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n);
MarkConflicted(conf->confirmed_block_hash, conf->confirmed_block_height, range.first->second);
}
range.first++;
}
@ -1070,7 +1068,8 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Co
// Block disconnection override an abandoned tx as unconfirmed
// which means user may have to call abandontransaction again
return AddToWallet(MakeTransactionRef(tx), confirm, /* update_wtx= */ nullptr, /* fFlushOnClose= */ false, rescanning_old_block);
TxState tx_state = std::visit([](auto&& s) -> TxState { return s; }, state);
return AddToWallet(MakeTransactionRef(tx), tx_state, /*update_wtx=*/nullptr, /*fFlushOnClose=*/false, rescanning_old_block);
}
}
return false;
@ -1126,7 +1125,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
if (currentconfirm == 0 && !wtx.isAbandoned()) {
// If the orig tx was not in block/mempool, none of its spends can be in mempool
assert(!wtx.InMempool());
wtx.setAbandoned();
wtx.m_state = TxStateInactive{/*abandoned=*/true};
wtx.MarkDirty();
batch.WriteTx(wtx);
NotifyTransactionChanged(wtx.GetHash(), CT_UPDATED);
@ -1178,10 +1177,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
if (conflictconfirms < currentconfirm) {
// Block is 'more conflicted' than current confirm; update.
// Mark transaction as conflicted with this block.
wtx.m_confirm.nIndex = 0;
wtx.m_confirm.hashBlock = hashBlock;
wtx.m_confirm.block_height = conflicting_height;
wtx.setConflicted();
wtx.m_state = TxStateConflicted{hashBlock, conflicting_height};
wtx.MarkDirty();
batch.WriteTx(wtx);
// Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
@ -1199,9 +1195,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
}
}
void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool update_tx, bool rescanning_old_block)
void CWallet::SyncTransaction(const CTransactionRef& ptx, const SyncTxState& state, bool update_tx, bool rescanning_old_block)
{
if (!AddToWalletIfInvolvingMe(ptx, confirm, update_tx, rescanning_old_block))
if (!AddToWalletIfInvolvingMe(ptx, state, update_tx, rescanning_old_block))
return; // Not one of ours
// If a transaction changes 'conflicted' state, that changes the balance
@ -1212,7 +1208,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmatio
void CWallet::transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) {
LOCK(cs_wallet);
SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /*block_height=*/0, /*block_hash=*/{}, /*block_index=*/0});
SyncTransaction(tx, TxStateInMempool{});
auto it = mapWallet.find(tx->GetHash());
if (it != mapWallet.end()) {
@ -1253,7 +1249,7 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe
// distinguishing between conflicted and unconfirmed transactions are
// imperfect, and could be improved in general, see
// https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /*block_height=*/0, /*block_hash=*/{}, /*block_index=*/0});
SyncTransaction(tx, TxStateInactive{});
}
}
@ -1265,7 +1261,7 @@ void CWallet::blockConnected(const CBlock& block, int height)
m_last_block_processed_height = height;
m_last_block_processed = block_hash;
for (size_t index = 0; index < block.vtx.size(); index++) {
SyncTransaction(block.vtx[index], {CWalletTx::Status::CONFIRMED, height, block_hash, (int)index});
SyncTransaction(block.vtx[index], TxStateConfirmed{block_hash, height, static_cast<int>(index)});
transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::BLOCK, 0 /* mempool_sequence */);
}
}
@ -1281,7 +1277,7 @@ void CWallet::blockDisconnected(const CBlock& block, int height)
m_last_block_processed_height = height - 1;
m_last_block_processed = block.hashPrevBlock;
for (const CTransactionRef& ptx : block.vtx) {
SyncTransaction(ptx, {CWalletTx::Status::UNCONFIRMED, /*block_height=*/0, /*block_hash=*/{}, /*block_index=*/0});
SyncTransaction(ptx, TxStateInactive{});
}
}
@ -1645,7 +1641,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
break;
}
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
SyncTransaction(block.vtx[posInBlock], {CWalletTx::Status::CONFIRMED, block_height, block_hash, (int)posInBlock}, fUpdate, /* rescanning_old_block */ true);
SyncTransaction(block.vtx[posInBlock], TxStateConfirmed{block_hash, block_height, static_cast<int>(posInBlock)}, fUpdate, /*rescanning_old_block=*/true);
}
// scan succeeded, record block as most recent successfully scanned
result.last_scanned_block = block_hash;
@ -1720,7 +1716,7 @@ void CWallet::ReacceptWalletTransactions()
}
}
bool CWallet::SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_string, bool relay) const
bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const
{
// Can't relay if wallet is not broadcasting
if (!GetBroadcastTransactions()) return false;
@ -1734,17 +1730,17 @@ bool CWallet::SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_
// Submit transaction to mempool for relay
WalletLogPrintf("Submitting wtx %s to mempool for relay\n", wtx.GetHash().ToString());
// We must set fInMempool here - while it will be re-set to true by the
// We must set TxStateInMempool here. Even though it will also be set later by the
// entered-mempool callback, if we did not there would be a race where a
// user could call sendmoney in a loop and hit spurious out of funds errors
// because we think that this newly generated transaction's change is
// unavailable as we're not yet aware that it is in the mempool.
//
// Irrespective of the failure reason, un-marking fInMempool
// out-of-order is incorrect - it should be unmarked when
// If broadcast fails for any reason, trying to set wtx.m_state here would be incorrect.
// If transaction was previously in the mempool, it should be updated when
// TransactionRemovedFromMempool fires.
bool ret = chain().broadcastTransaction(wtx.tx, m_default_max_tx_fee, relay, err_string);
wtx.fInMempool |= ret;
if (ret) wtx.m_state = TxStateInMempool{};
return ret;
}
@ -1831,7 +1827,8 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const
return false;
}
const CWalletTx& wtx = mi->second;
coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], wtx.m_confirm.block_height, wtx.IsCoinBase());
int prev_height = wtx.state<TxStateConfirmed>() ? wtx.state<TxStateConfirmed>()->confirmed_block_height : 0;
coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], prev_height, wtx.IsCoinBase());
}
std::map<int, bilingual_str> input_errors;
return SignTransaction(tx, coins, SIGHASH_DEFAULT, input_errors);
@ -1956,7 +1953,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
// Add tx to wallet, because if it has change it's also ours,
// otherwise just for transaction history.
AddToWallet(tx, {}, [&](CWalletTx& wtx, bool new_tx) {
AddToWallet(tx, TxStateInactive{}, [&](CWalletTx& wtx, bool new_tx) {
CHECK_NONFATAL(wtx.mapValue.empty());
CHECK_NONFATAL(wtx.vOrderForm.empty());
wtx.mapValue = std::move(mapValue);
@ -1974,7 +1971,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
}
// Get the inserted-CWalletTx from mapWallet so that the
// fInMempool flag is cached properly
// wtx cached mempool state is updated correctly
CWalletTx& wtx = mapWallet.at(tx->GetHash());
if (!fBroadcastTransactions) {
@ -2321,10 +2318,10 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t>& mapKeyBirth) const {
mapKeyBirth.clear();
// map in which we'll infer heights of other keys
std::map<CKeyID, const CWalletTx::Confirmation*> mapKeyFirstBlock;
CWalletTx::Confirmation max_confirm;
max_confirm.block_height = GetLastBlockHeight() > 144 ? GetLastBlockHeight() - 144 : 0; // the tip can be reorganized; use a 144-block safety margin
CHECK_NONFATAL(chain().findAncestorByHeight(GetLastBlockHash(), max_confirm.block_height, FoundBlock().hash(max_confirm.hashBlock)));
std::map<CKeyID, const TxStateConfirmed*> mapKeyFirstBlock;
TxStateConfirmed max_confirm{uint256{}, /*height=*/-1, /*index=*/-1};
max_confirm.confirmed_block_height = GetLastBlockHeight() > 144 ? GetLastBlockHeight() - 144 : 0; // the tip can be reorganized; use a 144-block safety margin
CHECK_NONFATAL(chain().findAncestorByHeight(GetLastBlockHash(), max_confirm.confirmed_block_height, FoundBlock().hash(max_confirm.confirmed_block_hash)));
{
LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
@ -2352,15 +2349,15 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t>& mapKeyBirth) const {
for (const auto& entry : mapWallet) {
// iterate over all wallet transactions...
const CWalletTx &wtx = entry.second;
if (wtx.m_confirm.status == CWalletTx::CONFIRMED) {
if (auto* conf = wtx.state<TxStateConfirmed>()) {
// ... which are already in a block
for (const CTxOut &txout : wtx.tx->vout) {
// iterate over all their outputs
for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *spk_man)) {
// ... and all their affected keys
auto rit = mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() && wtx.m_confirm.block_height < rit->second->block_height) {
rit->second = &wtx.m_confirm;
if (rit != mapKeyFirstBlock.end() && conf->confirmed_block_height < rit->second->confirmed_block_height) {
rit->second = conf;
}
}
}
@ -2371,7 +2368,7 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t>& mapKeyBirth) const {
// Extract block timestamps for those keys
for (const auto& entry : mapKeyFirstBlock) {
int64_t block_time;
CHECK_NONFATAL(chain().findBlock(entry.second->hashBlock, FoundBlock().time(block_time)));
CHECK_NONFATAL(chain().findBlock(entry.second->confirmed_block_hash, FoundBlock().time(block_time)));
mapKeyBirth[entry.first] = block_time - TIMESTAMP_WINDOW; // block times can be 2h off
}
}
@ -2401,11 +2398,18 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t>& mapKeyBirth) const {
*/
unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx, bool rescanning_old_block) const
{
std::optional<uint256> block_hash;
if (auto* conf = wtx.state<TxStateConfirmed>()) {
block_hash = conf->confirmed_block_hash;
} else if (auto* conf = wtx.state<TxStateConflicted>()) {
block_hash = conf->conflicting_block_hash;
}
unsigned int nTimeSmart = wtx.nTimeReceived;
if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) {
if (block_hash) {
int64_t blocktime;
int64_t block_max_time;
if (chain().findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(blocktime).maxTime(block_max_time))) {
if (chain().findBlock(*block_hash, FoundBlock().time(blocktime).maxTime(block_max_time))) {
if (rescanning_old_block) {
nTimeSmart = block_max_time;
} else {
@ -2437,7 +2441,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx, bool rescanning_old
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
}
} else {
WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.m_confirm.hashBlock.ToString());
WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), block_hash->ToString());
}
}
return nTimeSmart;
@ -2944,9 +2948,13 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const
{
AssertLockHeld(cs_wallet);
if (wtx.isUnconfirmed() || wtx.isAbandoned()) return 0;
return (GetLastBlockHeight() - wtx.m_confirm.block_height + 1) * (wtx.isConflicted() ? -1 : 1);
if (auto* conf = wtx.state<TxStateConfirmed>()) {
return GetLastBlockHeight() - conf->confirmed_block_height + 1;
} else if (auto* conf = wtx.state<TxStateConflicted>()) {
return -1 * (GetLastBlockHeight() - conf->conflicting_block_height + 1);
} else {
return 0;
}
}
int CWallet::GetTxBlocksToMaturity(const CWalletTx& wtx) const

View file

@ -275,7 +275,7 @@ private:
* Should be called with rescanning_old_block set to true, if the transaction is
* not discovered in real time, but during a rescan of old blocks.
*/
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Confirmation confirm, bool fUpdate, bool rescanning_old_block) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const SyncTxState& state, bool fUpdate, bool rescanning_old_block) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
void MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx);
@ -285,7 +285,7 @@ private:
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SyncTransaction(const CTransactionRef& tx, CWalletTx::Confirmation confirm, bool update_tx = true, bool rescanning_old_block = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SyncTransaction(const CTransactionRef& tx, const SyncTxState& state, bool update_tx = true, bool rescanning_old_block = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** WalletFlags set on this wallet. */
std::atomic<uint64_t> m_wallet_flags{0};
@ -508,7 +508,7 @@ public:
//! @return true if wtx is changed and needs to be saved to disk, otherwise false
using UpdateWalletTxFn = std::function<bool(CWalletTx& wtx, bool new_tx)>;
CWalletTx* AddToWallet(CTransactionRef tx, const CWalletTx::Confirmation& confirm, const UpdateWalletTxFn& update_wtx=nullptr, bool fFlushOnClose=true, bool rescanning_old_block = false);
CWalletTx* AddToWallet(CTransactionRef tx, const TxState& state, const UpdateWalletTxFn& update_wtx=nullptr, bool fFlushOnClose=true, bool rescanning_old_block = false);
bool LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) override;
void blockConnected(const CBlock& block, int height) override;
@ -576,7 +576,7 @@ public:
void CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm);
/** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */
bool SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_string, bool relay) const;
bool SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const;
bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const
{

View file

@ -987,7 +987,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWal
uint256 hash;
ssKey >> hash;
vTxHash.push_back(hash);
vWtx.emplace_back(nullptr /* tx */);
vWtx.emplace_back(/*tx=*/nullptr, TxStateInactive{});
ssValue >> vWtx.back();
}
}