mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-11 12:22:39 -03:00
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:
parent
2efc8c0999
commit
d8ee8f3cd3
16 changed files with 256 additions and 173 deletions
|
@ -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 \
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
22
src/util/overloaded.h
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
24
src/wallet/test/wallet_transaction_tests.cpp
Normal file
24
src/wallet/test/wallet_transaction_tests.cpp
Normal 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()
|
|
@ -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
|
||||
|
|
|
@ -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(); }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue