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