/
githubmirror
/
bitcoin
Обзор
Документация
Войти
/
githubmirror
/
bitcoin
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/wallet/transaction.h
443 строки
17 KB
Ava Chow
wallet: Replace CWalletTx::SetTx with Update
16 июл 2026, 20:56
16 июл 2026, 20:56
0b1af01
Код
Авторство
О чём код?
// Copyright (c) 2021-present The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLET_TRANSACTION_H #define BITCOIN_WALLET_TRANSACTION_H #include <attributes.h> #include <consensus/amount.h> #include <primitives/transaction.h> #include <tinyformat.h> #include <uint256.h> #include <util/check.h> #include <util/overloaded.h> #include <util/strencodings.h> #include <util/string.h> #include <wallet/types.h> #include <bitset> #include <cstdint> #include <map> #include <utility> #include <variant> #include <vector> namespace interfaces { class Chain; } // namespace interfaces namespace wallet { //! 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) {} std::string toString() const { return strprintf("Confirmed (block=%s, height=%i, index=%i)", confirmed_block_hash.ToString(), confirmed_block_height, position_in_block); } }; //! State of transaction added to mempool. struct TxStateInMempool { std::string toString() const { return strprintf("InMempool"); } }; //! State of rejected transaction that conflicts with a confirmed block. struct TxStateBlockConflicted { uint256 conflicting_block_hash; int conflicting_block_height; explicit TxStateBlockConflicted(const uint256& block_hash, int height) : conflicting_block_hash(block_hash), conflicting_block_height(height) {} std::string toString() const { return strprintf("BlockConflicted (block=%s, height=%i)", conflicting_block_hash.ToString(), conflicting_block_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) {} std::string toString() const { return strprintf("Inactive (abandoned=%i)", 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) {} std::string toString() const { return strprintf("Unrecognized (block=%s, index=%i)", block_hash.ToString(), index); } }; //! All possible CWalletTx states using TxState = std::variant<TxStateConfirmed, TxStateInMempool, TxStateBlockConflicted, 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 TxStateBlockConflicted{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 TxStateBlockConflicted& 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 TxStateBlockConflicted& conflicted) { return -1; }, [](const TxStateUnrecognized& unrecognized) { return unrecognized.index; } }, state); } //! Return TxState or SyncTxState as a string for logging or debugging. template<typename T> std::string TxStateString(const T& state) { return std::visit([](const auto& s) { return s.toString(); }, state); } /** * Cachable amount subdivided into avoid reuse and all balances */ struct CachableAmount { std::optional<CAmount> m_avoid_reuse_value; std::optional<CAmount> m_all_value; inline void Reset() { m_avoid_reuse_value.reset(); m_all_value.reset(); } void Set(bool avoid_reuse, CAmount value) { if (avoid_reuse) { m_avoid_reuse_value = value; } else { m_all_value = value; } } CAmount Get(bool avoid_reuse) { if (avoid_reuse) { Assert(m_avoid_reuse_value.has_value()); return m_avoid_reuse_value.value(); } Assert(m_all_value.has_value()); return m_all_value.value(); } bool IsCached(bool avoid_reuse) { if (avoid_reuse) return m_avoid_reuse_value.has_value(); return m_all_value.has_value(); } }; /** Legacy class used for deserializing vtxPrev for backwards compatibility. * vtxPrev was removed in commit 93a18a3650292afbb441a47d1fa1b94aeb0164e3, * but old wallet.dat files may still contain vtxPrev vectors of CMerkleTxs. * These need to get deserialized for field alignment when deserializing * a CWalletTx, but the deserialized values are discarded.**/ class CMerkleTx { public: template<typename Stream> void Unserialize(Stream& s) { CTransactionRef tx; uint256 hashBlock; std::vector<uint256> vMerkleBranch; int nIndex; s >> TX_WITH_WITNESS(tx) >> hashBlock >> vMerkleBranch >> nIndex; } }; /** * A transaction with a bunch of additional info that only the owner cares about. * It includes any unrecorded transactions needed to link it back to the block chain. */ class CWalletTx { public: // "from" and "message" are obsolete fields that could be set in // the UI prior to 2011 (removed in commit 4d9b223) // These fields are kept to avoid losing metadata. std::optional<std::string> m_from; std::optional<std::string> m_message; // Comment strings provided by the user std::optional<std::string> m_comment; std::optional<std::string> m_comment_to; std::optional<Txid> m_replaces_txid; std::optional<Txid> m_replaced_by_txid; // BIP 21 URI Messages std::vector<std::string> m_messages; // BIP 70 Payment Request (deprecated, field kept to preserve metadata from old wallets) std::vector<std::string> m_payment_requests; unsigned int nTimeReceived; //!< time received by this node /** * Stable timestamp that never changes, and reflects the order a transaction * was added to the wallet. Timestamp is based on the block time for a * transaction added as part of a block, or else the time when the * transaction was received if it wasn't part of a block, with the timestamp * adjusted in both cases so timestamp order matches the order transactions * were added to the wallet. More details can be found in * CWallet::ComputeTimeSmart(). */ unsigned int nTimeSmart; // Cached value for whether the transaction spends any inputs known to the wallet mutable std::optional<bool> m_cached_from_me{std::nullopt}; int64_t nOrderPos; //!< position in ordered transaction list std::multimap<int64_t, CWalletTx*>::const_iterator m_it_wtxOrdered; // memory only enum AmountType { DEBIT, CREDIT, AMOUNTTYPE_ENUM_ELEMENTS }; mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS]; /** * This flag is true if all m_amounts caches are empty. This is particularly * useful in places where MarkDirty is conditionally called and the * condition can be expensive and thus can be skipped if the flag is true. * See MarkDestinationsDirty. */ mutable bool m_is_cache_empty{true}; mutable bool fChangeCached; mutable CAmount nChangeCached; CWalletTx(CTransactionRef tx, const TxState& state) : m_state(state) { Assert(tx); m_canonical_wtxid = tx->GetWitnessHash(); m_txs.emplace(tx->GetWitnessHash(), std::move(tx)); Init(); } template <typename Stream> CWalletTx(deserialize_type, Stream& s, const std::map<Wtxid, CTransactionRef>& variants) : m_state(TxStateInactive{}) { Unserialize(s); // Merge witness variants m_txs.insert(variants.begin(), variants.end()); Assert(m_txs.contains(GetWitnessHash())); } void Init() { nTimeReceived = 0; nTimeSmart = 0; fChangeCached = false; nChangeCached = 0; nOrderPos = -1; } TxState m_state; // Set of mempool transactions that conflict // directly with the transaction, or that conflict // with an ancestor transaction. This set will be // empty if state is InMempool or Confirmed, but // can be nonempty if state is Inactive or // BlockConflicted. std::set<Txid> mempool_conflicts; // Track v3 mempool tx that spends from this tx // so that we don't try to create another unconfirmed child std::optional<Txid> truc_child_in_mempool; template<typename Stream> void Serialize(Stream& s) const { std::map<std::string, std::string> string_values; if (m_from) string_values["from"] = *m_from; if (m_message) string_values["message"] = *m_message; if (m_comment) string_values["comment"] = *m_comment; if (m_comment_to) string_values["to"] = *m_comment_to; if (m_replaces_txid) string_values["replaces_txid"] = m_replaces_txid->ToString(); if (m_replaced_by_txid) string_values["replaced_by_txid"] = m_replaced_by_txid->ToString(); string_values["fromaccount"] = ""; if (nOrderPos != -1) string_values["n"] = util::ToString(nOrderPos); if (nTimeSmart) string_values["timesmart"] = strprintf("%u", nTimeSmart); std::vector<std::pair<std::string, std::string>> msgs_reqs; msgs_reqs.reserve(m_messages.size() + m_payment_requests.size()); for (const std::string& msg : m_messages) { msgs_reqs.emplace_back("Message", msg); } for (const std::string& req : m_payment_requests) { msgs_reqs.emplace_back("PaymentRequest", req); } 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 fFromMe, and fSpent uint32_t dummy_int = 0; // Used to be fTimeReceivedIsTxTime uint256 serializedHash = TxStateSerializedBlockHash(m_state); int serializedIndex = TxStateSerializedIndex(m_state); s << TX_WITH_WITNESS(GetTx()) << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << string_values << msgs_reqs << dummy_int << nTimeReceived << dummy_bool << dummy_bool; } template<typename Stream> void Unserialize(Stream& s) { Init(); std::vector<uint256> dummy_vector1; // Used to be vMerkleBranch std::vector<CMerkleTx> dummy_vector2; // Used to be vtxPrev bool dummy_bool; // Used to be fFromMe, and fSpent uint32_t dummy_int; // Used to be fTimeReceivedIsTxTime uint256 serialized_block_hash; int serializedIndex; std::map<std::string, std::string> string_values; std::vector<std::pair<std::string, std::string>> msgs_reqs; CTransactionRef canonical_tx; s >> TX_WITH_WITNESS(canonical_tx) >> serialized_block_hash >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> string_values >> msgs_reqs >> dummy_int >> nTimeReceived >> dummy_bool >> dummy_bool; m_canonical_wtxid = canonical_tx->GetWitnessHash(); m_txs.emplace(m_canonical_wtxid, std::move(canonical_tx)); m_state = TxStateInterpretSerialized({serialized_block_hash, serializedIndex}); string_values.erase("fromaccount"); string_values.erase("spent"); for (const auto& [key, value] : string_values) { if (key == "n") nOrderPos = LocaleIndependentAtoi<int64_t>(value); else if (key == "timesmart") nTimeSmart = LocaleIndependentAtoi<int64_t>(value); else if (key == "from") m_from = value; else if (key == "message") m_message = value; else if (key == "comment") m_comment = value; else if (key == "to") m_comment_to = value; else if (key == "replaces_txid") m_replaces_txid = Txid::FromHex(value); else if (key == "replaced_by_txid") m_replaced_by_txid = Txid::FromHex(value); else { throw std::runtime_error("Unexpected value in CWalletTx strings value map"); } } for (const auto& [type, data] : msgs_reqs) { if (type == "Message") m_messages.emplace_back(data); else if (type == "PaymentRequest") m_payment_requests.emplace_back(data); else { throw std::runtime_error("Unknown type in CWalletTx messages and requests vector"); } } } CTransactionRef GetTx() const { return m_txs.at(m_canonical_wtxid); } // Update the state of this wallet transaction along with a transaction that may have a different wtxid. // If the given transaction has a different wtxid, the transaction is stored if it has not been seen before. // The canonical wtxid is also updated. The tx that is confirmed becomes canonical. For unconfirmed txs, // those with witnesses are preferred, followed by least weight. bool Update(CTransactionRef tx, const TxState& arg_state); //! make sure balances are recalculated void MarkDirty() { m_amounts[DEBIT].Reset(); m_amounts[CREDIT].Reset(); fChangeCached = false; m_is_cache_empty = true; m_cached_from_me = std::nullopt; } /** True if only scriptSigs are different */ bool IsEquivalentTo(const CWalletTx& tx) const; bool InMempool() const; int64_t GetTxTime() const; 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); } //! Update transaction state when attaching to a chain, filling in heights //! of conflicted and confirmed blocks void updateState(interfaces::Chain& chain); bool isAbandoned() const { return state<TxStateInactive>() && state<TxStateInactive>()->abandoned; } bool isMempoolConflicted() const { return !mempool_conflicts.empty(); } bool isBlockConflicted() const { return state<TxStateBlockConflicted>(); } bool isInactive() const { return state<TxStateInactive>(); } bool isUnconfirmed() const { return !isAbandoned() && !isBlockConflicted() && !isMempoolConflicted() && !isConfirmed(); } bool isConfirmed() const { return state<TxStateConfirmed>(); } const Txid& GetHash() const LIFETIMEBOUND { return GetTx()->GetHash(); } const Wtxid& GetWitnessHash() const LIFETIMEBOUND { return GetTx()->GetWitnessHash(); } bool IsCoinBase() const { return GetTx()->IsCoinBase(); } const std::map<Wtxid, CTransactionRef>& GetTxs() const { return m_txs; } // Disable copying of CWalletTx objects to prevent bugs where instances get // copied in and out of the mapWallet map, and fields are updated in the // wrong copy. CWalletTx(const CWalletTx&) = delete; CWalletTx& operator=(const CWalletTx&) = delete; // Enable the default move constructor CWalletTx(CWalletTx&&) = default; private: Wtxid m_canonical_wtxid; std::map<Wtxid, CTransactionRef> m_txs; //! Set m_canonical_wtxid to the best variant under the unconfirmed rule //! (witnessed preferred, then least weight). Ignores state. void RecomputeCanonical(); }; struct WalletTxOrderComparator { bool operator()(const CWalletTx* a, const CWalletTx* b) const { return a->nOrderPos < b->nOrderPos; } }; class WalletTXO { private: const CWalletTx& m_wtx; const CTxOut& m_output; public: WalletTXO(const CWalletTx& wtx, const CTxOut& output) : m_wtx(wtx), m_output(output) { Assume(std::ranges::find(wtx.GetTx()->vout, output) != wtx.GetTx()->vout.end()); } const CWalletTx& GetWalletTx() const { return m_wtx; } const CTxOut& GetTxOut() const { return m_output; } }; } // namespace wallet #endif // BITCOIN_WALLET_TRANSACTION_H