#pragma once /** * This is the state object that recursively checks all transactions in a * tree for double spent proofs that may cause the given txid to become * double spent and potentially rejected from mining. * * Usage is very simple: * @code * auto *d = new DSTxState(txid); * // connects to signals. * d->startTimer(); * @endcode */ class DSTxState : public QObject { Q_OBJECT public: // this is called from the user API to monitor a single txid // maybe a pointer to some fulcrum APIs are needed. IDK DSTxState(const uint256 &txid) : DSTxState(txid, -1, nullptr) {} DSTxState(const uint256 &txid, int outIndex, DSTxState *parent = nullptr) : QObject(parent), m_txid(txid) { bool needsMonitoring = true; if (parent) { // 1. fetch the mempool information for txid. // 1.1 if confirmed (not in mempool) needsMonitoring = false; // 1.2. set m_memPoolEntryTime to the epoch-based timestamp // 1.3 if (time(nullptr) - m_memPoolEntryTime) > 30sec needsMonitoring = false; } if (needsMonitoring) { // 2. use fulcrum APIs to fetch the tx-details. // 2.1. for each input: // 2.1.1. create a new DSTxState and add to the list. // 2.2. check the output with outIndex and if it is not a p2pkh, // call markFail(NotEligible); } else { parent->removeMe(this); } } void startTimer() { assert(!m_timer); m_timer = new QTimer(this); m_timer->start(200); connect (m_timer, SIGNAL(elapsed()), this, SLOT(updateState()); } signals: void finished(); void failed(); void proofFound(); private slots: void updateState(DSTxState *parent = nullptr) { // if its been in the mempool for > 30 sec, // waiting longer for a DSProof is irrelevant. // exit. if (time(nullptr) - m_memPoolEntryTime > 30) { auto *p = qobject_cast(parent()); if (p) { p->removeMe(this); return; } else { m_timer.stop(); emit finished(); return; } } // 1. Use Fulcrum to fetch dsproof info for this Tx. // 1.1. if double spent: // call parent()->markFail(DSPFound); // recurse auto copy = m_prevTxs; for (const auto &prevTx : copy) { prevTx.updateState(this); } } private: // a prevOut got confirmed or has been in the mempool long enough // to no longer need to be monitored. void removeMe(DSTxState *prevTx) { // removes arg from m_prevTxs auto c = m_prevTxs.removeAll(prevTx); assert(c); prevTx->deleteLater(); } enum FailReason { DSPFound, NotEligible }; void markFail(FailReason reason) { auto *p = qobject_cast(parent()); if (p)->markFail(reason); else { m_timer->stop(); if (reason == DSPFound) emit proofFound(); else emit failed(); // it is possible to do a deleteLater() here, // depends on how you want to tie this into the wider system. } } uint256 m_txid; quint64 m_memPoolEntryTime = 0; QList m_prevTxs; QTimer *m_timer = nullptr; // only the root state has an actual timer };