transaction.h raw

   1  // Copyright (c) 2021-2022 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #ifndef LIMENKA_WALLET_TRANSACTION_H
   6  #define LIMENKA_WALLET_TRANSACTION_H
   7  
   8  #include <attributes.h>
   9  #include <consensus/amount.h>
  10  #include <primitives/transaction.h>
  11  #include <tinyformat.h>
  12  #include <uint256.h>
  13  #include <util/overloaded.h>
  14  #include <util/strencodings.h>
  15  #include <util/string.h>
  16  #include <wallet/types.h>
  17  
  18  #include <bitset>
  19  #include <cstdint>
  20  #include <map>
  21  #include <utility>
  22  #include <variant>
  23  #include <vector>
  24  
  25  namespace interfaces {
  26  class Chain;
  27  } // namespace interfaces
  28  
  29  namespace wallet {
  30  //! State of transaction confirmed in a block.
  31  struct TxStateConfirmed {
  32      uint256 confirmed_block_hash;
  33      int confirmed_block_height;
  34      int position_in_block;
  35  
  36      explicit TxStateConfirmed(const uint256& block_hash, int height, int index) : confirmed_block_hash(block_hash), confirmed_block_height(height), position_in_block(index) {}
  37      std::string toString() const { return strprintf("Confirmed (block=%s, height=%i, index=%i)", confirmed_block_hash.ToString(), confirmed_block_height, position_in_block); }
  38  };
  39  
  40  //! State of transaction added to mempool.
  41  struct TxStateInMempool {
  42      std::string toString() const { return strprintf("InMempool"); }
  43  };
  44  
  45  //! State of rejected transaction that conflicts with a confirmed block.
  46  struct TxStateBlockConflicted {
  47      uint256 conflicting_block_hash;
  48      int conflicting_block_height;
  49  
  50      explicit TxStateBlockConflicted(const uint256& block_hash, int height) : conflicting_block_hash(block_hash), conflicting_block_height(height) {}
  51      std::string toString() const { return strprintf("BlockConflicted (block=%s, height=%i)", conflicting_block_hash.ToString(), conflicting_block_height); }
  52  };
  53  
  54  //! State of transaction not confirmed or conflicting with a known block and
  55  //! not in the mempool. May conflict with the mempool, or with an unknown block,
  56  //! or be abandoned, never broadcast, or rejected from the mempool for another
  57  //! reason.
  58  struct TxStateInactive {
  59      bool abandoned;
  60  
  61      explicit TxStateInactive(bool abandoned = false) : abandoned(abandoned) {}
  62      std::string toString() const { return strprintf("Inactive (abandoned=%i)", abandoned); }
  63  };
  64  
  65  //! State of transaction loaded in an unrecognized state with unexpected hash or
  66  //! index values. Treated as inactive (with serialized hash and index values
  67  //! preserved) by default, but may enter another state if transaction is added
  68  //! to the mempool, or confirmed, or abandoned, or found conflicting.
  69  struct TxStateUnrecognized {
  70      uint256 block_hash;
  71      int index;
  72  
  73      TxStateUnrecognized(const uint256& block_hash, int index) : block_hash(block_hash), index(index) {}
  74      std::string toString() const { return strprintf("Unrecognized (block=%s, index=%i)", block_hash.ToString(), index); }
  75  };
  76  
  77  //! All possible CWalletTx states
  78  using TxState = std::variant<TxStateConfirmed, TxStateInMempool, TxStateBlockConflicted, TxStateInactive, TxStateUnrecognized>;
  79  
  80  //! Subset of states transaction sync logic is implemented to handle.
  81  using SyncTxState = std::variant<TxStateConfirmed, TxStateInMempool, TxStateInactive>;
  82  
  83  //! Try to interpret deserialized TxStateUnrecognized data as a recognized state.
  84  static inline TxState TxStateInterpretSerialized(TxStateUnrecognized data)
  85  {
  86      if (data.block_hash == uint256::ZERO) {
  87          if (data.index == 0) return TxStateInactive{};
  88      } else if (data.block_hash == uint256::ONE) {
  89          if (data.index == -1) return TxStateInactive{/*abandoned=*/true};
  90      } else if (data.index >= 0) {
  91          return TxStateConfirmed{data.block_hash, /*height=*/-1, data.index};
  92      } else if (data.index == -1) {
  93          return TxStateBlockConflicted{data.block_hash, /*height=*/-1};
  94      }
  95      return data;
  96  }
  97  
  98  //! Get TxState serialized block hash. Inverse of TxStateInterpretSerialized.
  99  static inline uint256 TxStateSerializedBlockHash(const TxState& state)
 100  {
 101      return std::visit(util::Overloaded{
 102          [](const TxStateInactive& inactive) { return inactive.abandoned ? uint256::ONE : uint256::ZERO; },
 103          [](const TxStateInMempool& in_mempool) { return uint256::ZERO; },
 104          [](const TxStateConfirmed& confirmed) { return confirmed.confirmed_block_hash; },
 105          [](const TxStateBlockConflicted& conflicted) { return conflicted.conflicting_block_hash; },
 106          [](const TxStateUnrecognized& unrecognized) { return unrecognized.block_hash; }
 107      }, state);
 108  }
 109  
 110  //! Get TxState serialized block index. Inverse of TxStateInterpretSerialized.
 111  static inline int TxStateSerializedIndex(const TxState& state)
 112  {
 113      return std::visit(util::Overloaded{
 114          [](const TxStateInactive& inactive) { return inactive.abandoned ? -1 : 0; },
 115          [](const TxStateInMempool& in_mempool) { return 0; },
 116          [](const TxStateConfirmed& confirmed) { return confirmed.position_in_block; },
 117          [](const TxStateBlockConflicted& conflicted) { return -1; },
 118          [](const TxStateUnrecognized& unrecognized) { return unrecognized.index; }
 119      }, state);
 120  }
 121  
 122  //! Return TxState or SyncTxState as a string for logging or debugging.
 123  template<typename T>
 124  std::string TxStateString(const T& state)
 125  {
 126      return std::visit([](const auto& s) { return s.toString(); }, state);
 127  }
 128  
 129  /**
 130   * Cachable amount subdivided into watchonly and spendable parts.
 131   */
 132  struct CachableAmount
 133  {
 134      // NO and ALL are never (supposed to be) cached
 135      std::bitset<ISMINE_ENUM_ELEMENTS> m_cached;
 136      CAmount m_value[ISMINE_ENUM_ELEMENTS];
 137      inline void Reset()
 138      {
 139          m_cached.reset();
 140      }
 141      void Set(isminefilter filter, CAmount value)
 142      {
 143          m_cached.set(filter);
 144          m_value[filter] = value;
 145      }
 146  };
 147  
 148  
 149  typedef std::map<std::string, std::string> mapValue_t;
 150  
 151  
 152  /** Legacy class used for deserializing vtxPrev for backwards compatibility.
 153   * vtxPrev was removed in commit 93a18a3650292afbb441a47d1fa1b94aeb0164e3,
 154   * but old wallet.dat files may still contain vtxPrev vectors of CMerkleTxs.
 155   * These need to get deserialized for field alignment when deserializing
 156   * a CWalletTx, but the deserialized values are discarded.**/
 157  class CMerkleTx
 158  {
 159  public:
 160      template<typename Stream>
 161      void Unserialize(Stream& s)
 162      {
 163          CTransactionRef tx;
 164          uint256 hashBlock;
 165          std::vector<uint256> vMerkleBranch;
 166          int nIndex;
 167  
 168          s >> TX_WITH_WITNESS(tx) >> hashBlock >> vMerkleBranch >> nIndex;
 169      }
 170  };
 171  
 172  /**
 173   * A transaction with a bunch of additional info that only the owner cares about.
 174   * It includes any unrecorded transactions needed to link it back to the block chain.
 175   */
 176  class CWalletTx
 177  {
 178  public:
 179      /**
 180       * Key/value map with information about the transaction.
 181       *
 182       * The following keys can be read and written through the map and are
 183       * serialized in the wallet database:
 184       *
 185       *     "comment", "to"   - comment strings provided to sendtoaddress,
 186       *                         and sendmany wallet RPCs
 187       *     "replaces_txid"   - txid (as HexStr) of transaction replaced by
 188       *                         bumpfee on transaction created by bumpfee
 189       *     "replaced_by_txid" - txid (as HexStr) of transaction created by
 190       *                         bumpfee on transaction replaced by bumpfee
 191       *     "from", "message" - obsolete fields that could be set in UI prior to
 192       *                         2011 (removed in commit 4d9b223)
 193       *
 194       * The following keys are serialized in the wallet database, but shouldn't
 195       * be read or written through the map (they will be temporarily added and
 196       * removed from the map during serialization):
 197       *
 198       *     "fromaccount"     - serialized strFromAccount value
 199       *     "n"               - serialized nOrderPos value
 200       *     "timesmart"       - serialized nTimeSmart value
 201       *     "spent"           - serialized vfSpent value that existed prior to
 202       *                         2014 (removed in commit 93a18a3)
 203       */
 204      mapValue_t mapValue;
 205      std::vector<std::pair<std::string, std::string> > vOrderForm;
 206      unsigned int fTimeReceivedIsTxTime;
 207      unsigned int nTimeReceived; //!< time received by this node
 208      /**
 209       * Stable timestamp that never changes, and reflects the order a transaction
 210       * was added to the wallet. Timestamp is based on the block time for a
 211       * transaction added as part of a block, or else the time when the
 212       * transaction was received if it wasn't part of a block, with the timestamp
 213       * adjusted in both cases so timestamp order matches the order transactions
 214       * were added to the wallet. More details can be found in
 215       * CWallet::ComputeTimeSmart().
 216       */
 217      unsigned int nTimeSmart;
 218      /**
 219       * From me flag is set to 1 for transactions that were created by the wallet
 220       * on this limenka node, and set to 0 for transactions that were created
 221       * externally and came in through the network or sendrawtransaction RPC.
 222       */
 223      bool fFromMe;
 224      int64_t nOrderPos; //!< position in ordered transaction list
 225      std::multimap<int64_t, CWalletTx*>::const_iterator m_it_wtxOrdered;
 226  
 227      // memory only
 228      enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS };
 229      mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS];
 230      /**
 231       * This flag is true if all m_amounts caches are empty. This is particularly
 232       * useful in places where MarkDirty is conditionally called and the
 233       * condition can be expensive and thus can be skipped if the flag is true.
 234       * See MarkDestinationsDirty.
 235       */
 236      mutable bool m_is_cache_empty{true};
 237      mutable bool fChangeCached;
 238      mutable CAmount nChangeCached;
 239  
 240      CWalletTx(CTransactionRef tx, const TxState& state) : tx(std::move(tx)), m_state(state)
 241      {
 242          Init();
 243      }
 244  
 245      void Init()
 246      {
 247          mapValue.clear();
 248          vOrderForm.clear();
 249          fTimeReceivedIsTxTime = false;
 250          nTimeReceived = 0;
 251          nTimeSmart = 0;
 252          fFromMe = false;
 253          fChangeCached = false;
 254          nChangeCached = 0;
 255          nOrderPos = -1;
 256      }
 257  
 258      CTransactionRef tx;
 259      TxState m_state;
 260  
 261      // Set of mempool transactions that conflict
 262      // directly with the transaction, or that conflict
 263      // with an ancestor transaction. This set will be
 264      // empty if state is InMempool or Confirmed, but
 265      // can be nonempty if state is Inactive or
 266      // BlockConflicted.
 267      std::set<Txid> mempool_conflicts;
 268  
 269      template<typename Stream>
 270      void Serialize(Stream& s) const
 271      {
 272          mapValue_t mapValueCopy = mapValue;
 273  
 274          mapValueCopy["fromaccount"] = "";
 275          if (nOrderPos != -1) {
 276              mapValueCopy["n"] = util::ToString(nOrderPos);
 277          }
 278          if (nTimeSmart) {
 279              mapValueCopy["timesmart"] = strprintf("%u", nTimeSmart);
 280          }
 281  
 282          std::vector<uint8_t> dummy_vector1; //!< Used to be vMerkleBranch
 283          std::vector<uint8_t> dummy_vector2; //!< Used to be vtxPrev
 284          bool dummy_bool = false; //!< Used to be fSpent
 285          uint256 serializedHash = TxStateSerializedBlockHash(m_state);
 286          int serializedIndex = TxStateSerializedIndex(m_state);
 287          s << TX_WITH_WITNESS(tx) << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool;
 288      }
 289  
 290      template<typename Stream>
 291      void Unserialize(Stream& s)
 292      {
 293          Init();
 294  
 295          std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch
 296          std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev
 297          bool dummy_bool; //! Used to be fSpent
 298          uint256 serialized_block_hash;
 299          int serializedIndex;
 300          s >> TX_WITH_WITNESS(tx) >> serialized_block_hash >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool;
 301  
 302          m_state = TxStateInterpretSerialized({serialized_block_hash, serializedIndex});
 303  
 304          const auto it_op = mapValue.find("n");
 305          nOrderPos = (it_op != mapValue.end()) ? LocaleIndependentAtoi<int64_t>(it_op->second) : -1;
 306          const auto it_ts = mapValue.find("timesmart");
 307          nTimeSmart = (it_ts != mapValue.end()) ? static_cast<unsigned int>(LocaleIndependentAtoi<int64_t>(it_ts->second)) : 0;
 308  
 309          mapValue.erase("fromaccount");
 310          mapValue.erase("spent");
 311          mapValue.erase("n");
 312          mapValue.erase("timesmart");
 313      }
 314  
 315      void SetTx(CTransactionRef arg)
 316      {
 317          tx = std::move(arg);
 318      }
 319  
 320      //! make sure balances are recalculated
 321      void MarkDirty()
 322      {
 323          m_amounts[DEBIT].Reset();
 324          m_amounts[CREDIT].Reset();
 325          m_amounts[IMMATURE_CREDIT].Reset();
 326          m_amounts[AVAILABLE_CREDIT].Reset();
 327          fChangeCached = false;
 328          m_is_cache_empty = true;
 329      }
 330  
 331      /** True if only scriptSigs are different */
 332      bool IsEquivalentTo(const CWalletTx& tx) const;
 333  
 334      bool InMempool() const;
 335  
 336      int64_t GetTxTime() const;
 337  
 338      template<typename T> const T* state() const { return std::get_if<T>(&m_state); }
 339      template<typename T> T* state() { return std::get_if<T>(&m_state); }
 340  
 341      //! Update transaction state when attaching to a chain, filling in heights
 342      //! of conflicted and confirmed blocks
 343      void updateState(interfaces::Chain& chain);
 344  
 345      bool isAbandoned() const { return state<TxStateInactive>() && state<TxStateInactive>()->abandoned; }
 346      bool isMempoolConflicted() const { return !mempool_conflicts.empty(); }
 347      bool isBlockConflicted() const { return state<TxStateBlockConflicted>(); }
 348      bool isInactive() const { return state<TxStateInactive>(); }
 349      bool isUnconfirmed() const { return !isAbandoned() && !isBlockConflicted() && !isMempoolConflicted() && !isConfirmed(); }
 350      bool isConfirmed() const { return state<TxStateConfirmed>(); }
 351      const Txid& GetHash() const LIFETIMEBOUND { return tx->GetHash(); }
 352      const Wtxid& GetWitnessHash() const LIFETIMEBOUND { return tx->GetWitnessHash(); }
 353      bool IsCoinBase() const { return tx->IsCoinBase(); }
 354  
 355  private:
 356      // Disable copying of CWalletTx objects to prevent bugs where instances get
 357      // copied in and out of the mapWallet map, and fields are updated in the
 358      // wrong copy.
 359      CWalletTx(const CWalletTx&) = default;
 360      CWalletTx& operator=(const CWalletTx&) = default;
 361  public:
 362      // Instead have an explicit copy function
 363      void CopyFrom(const CWalletTx&);
 364  };
 365  
 366  struct WalletTxOrderComparator {
 367      bool operator()(const CWalletTx* a, const CWalletTx* b) const
 368      {
 369          return a->nOrderPos < b->nOrderPos;
 370      }
 371  };
 372  } // namespace wallet
 373  
 374  #endif // LIMENKA_WALLET_TRANSACTION_H
 375