// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; //! Subset of states transaction sync logic is implemented to handle. using SyncTxState = std::variant; //! 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 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 m_avoid_reuse_value; std::optional 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 void Unserialize(Stream& s) { CTransactionRef tx; uint256 hashBlock; std::vector 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 m_from; std::optional m_message; // Comment strings provided by the user std::optional m_comment; std::optional m_comment_to; std::optional m_replaces_txid; std::optional m_replaced_by_txid; // BIP 21 URI Messages std::vector m_messages; // BIP 70 Payment Request (deprecated, field kept to preserve metadata from old wallets) std::vector 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 m_cached_from_me{std::nullopt}; int64_t nOrderPos; //!< position in ordered transaction list std::multimap::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) : tx(std::move(tx)), m_state(state) { Init(); } void Init() { nTimeReceived = 0; nTimeSmart = 0; fChangeCached = false; nChangeCached = 0; nOrderPos = -1; } CTransactionRef tx; 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 mempool_conflicts; // Track v3 mempool tx that spends from this tx // so that we don't try to create another unconfirmed child std::optional truc_child_in_mempool; template void Serialize(Stream& s) const { std::map 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> 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 dummy_vector1; // Used to be vMerkleBranch std::vector 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(tx) << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << string_values << msgs_reqs << dummy_int << nTimeReceived << dummy_bool << dummy_bool; } template void Unserialize(Stream& s) { Init(); std::vector dummy_vector1; // Used to be vMerkleBranch std::vector 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 string_values; std::vector> msgs_reqs; s >> TX_WITH_WITNESS(tx) >> serialized_block_hash >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> string_values >> msgs_reqs >> dummy_int >> nTimeReceived >> dummy_bool >> dummy_bool; 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(value); else if (key == "timesmart") nTimeSmart = LocaleIndependentAtoi(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"); } } } void SetTx(CTransactionRef arg) { tx = std::move(arg); } //! 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 const T* state() const { return std::get_if(&m_state); } template T* state() { return std::get_if(&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() && state()->abandoned; } bool isMempoolConflicted() const { return !mempool_conflicts.empty(); } bool isBlockConflicted() const { return state(); } bool isInactive() const { return state(); } bool isUnconfirmed() const { return !isAbandoned() && !isBlockConflicted() && !isMempoolConflicted() && !isConfirmed(); } bool isConfirmed() const { return state(); } const Txid& GetHash() const LIFETIMEBOUND { return tx->GetHash(); } const Wtxid& GetWitnessHash() const LIFETIMEBOUND { return tx->GetWitnessHash(); } bool IsCoinBase() const { return tx->IsCoinBase(); } private: // 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&) = default; CWalletTx& operator=(const CWalletTx&) = default; public: // Instead have an explicit copy function void CopyFrom(const CWalletTx&); }; 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.tx->vout, output) != wtx.tx->vout.end()); } const CWalletTx& GetWalletTx() const { return m_wtx; } const CTxOut& GetTxOut() const { return m_output; } }; } // namespace wallet #endif // BITCOIN_WALLET_TRANSACTION_H