0diff --git a/src/index/txospenderindex.cpp b/src/index/txospenderindex.cpp
1index 10c104ab14..b120bb804c 100644
2--- a/src/index/txospenderindex.cpp
3+++ b/src/index/txospenderindex.cpp
4@@ -145,26 +145,26 @@ bool TxoSpenderIndex::CustomRemove(const interfaces::BlockInfo& block)
5 return true;
6 }
7
8-bool TxoSpenderIndex::ReadTransaction(const CDiskTxPos& tx_pos, CTransactionRef& tx, uint256& block_hash) const
9+std::optional<TxoSpenderIndex::Spender> TxoSpenderIndex::ReadTransaction(const CDiskTxPos& tx_pos) const
10 {
11 AutoFile file{m_chainstate->m_blockman.OpenBlockFile(tx_pos, true)};
12 if (file.IsNull()) {
13- return false;
14+ return std::nullopt;
15 }
16 CBlockHeader header;
17 try {
18 file >> header;
19 file.seek(tx_pos.nTxOffset, SEEK_CUR);
20+ CTransactionRef tx;
21 file >> TX_WITH_WITNESS(tx);
22- block_hash = header.GetHash();
23- return true;
24+ return Spender{tx, header.GetHash()};
25 } catch (const std::exception& e) {
26 LogError("Deserialize or I/O error - %s\n", e.what());
27- return false;
28+ return std::nullopt;
29 }
30 }
31
32-bool TxoSpenderIndex::FindSpender(const COutPoint& txo, CTransactionRef& tx, uint256& block_hash) const
33+std::optional<TxoSpenderIndex::Spender> TxoSpenderIndex::FindSpender(const COutPoint& txo) const
34 {
35 uint64_t prefix = CreateKeyPrefix(m_siphash_key, txo);
36 std::unique_ptr<CDBIterator> it(m_db->NewIterator());
37@@ -173,15 +173,15 @@ bool TxoSpenderIndex::FindSpender(const COutPoint& txo, CTransactionRef& tx, uin
38 // find all keys that start with the outpoint hash, load the transaction at the location specified in the key
39 // and return it if it does spend the provided outpoint
40 for (it->Seek(std::pair{DB_TXOSPENDERINDEX, prefix}); it->Valid() && it->GetKey(key) && key.hash == prefix; it->Next()) {
41- if (ReadTransaction(key.pos, tx, block_hash)) {
42- for (const auto& input : tx->vin) {
43+ if (auto res{ReadTransaction(key.pos)}) {
44+ for (const auto& input : res->tx->vin) {
45 if (input.prevout == txo) {
46- return true;
47+ return res;
48 }
49 }
50 }
51 }
52- return false;
53+ return std::nullopt;
54 }
55
56 BaseIndex::DB& TxoSpenderIndex::GetDB() const { return *m_db; }
57diff --git a/src/index/txospenderindex.h b/src/index/txospenderindex.h
58index 908774d9ef..9324d7cfc0 100644
59--- a/src/index/txospenderindex.h
60+++ b/src/index/txospenderindex.h
61@@ -28,13 +28,19 @@ static constexpr bool DEFAULT_TXOSPENDERINDEX{false};
62 */
63 class TxoSpenderIndex final : public BaseIndex
64 {
65+public:
66+ struct Spender {
67+ CTransactionRef tx;
68+ uint256 block_hash;
69+ };
70+
71 private:
72 std::unique_ptr<BaseIndex::DB> m_db;
73 std::pair<uint64_t, uint64_t> m_siphash_key;
74 bool AllowPrune() const override { return false; }
75 void WriteSpenderInfos(const std::vector<std::pair<COutPoint, CDiskTxPos>>& items);
76 void EraseSpenderInfos(const std::vector<std::pair<COutPoint, CDiskTxPos>>& items);
77- bool ReadTransaction(const CDiskTxPos& pos, CTransactionRef& tx, uint256& block_hash) const;
78+ std::optional<Spender> ReadTransaction(const CDiskTxPos& pos) const;
79
80 protected:
81 interfaces::Chain::NotifyOptions CustomOptions() override;
82@@ -51,7 +57,7 @@ public:
83 // Destroys unique_ptr to an incomplete type.
84 virtual ~TxoSpenderIndex() override;
85
86- bool FindSpender(const COutPoint& txo, CTransactionRef& tx, uint256& block_hash) const;
87+ std::optional<Spender> FindSpender(const COutPoint& txo) const;
88 };
89
90 /// The global txo spender index. May be null.
91diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
92index 04eac58ffa..a26ae2b791 100644
93--- a/src/rpc/mempool.cpp
94+++ b/src/rpc/mempool.cpp
95@@ -709,13 +709,11 @@ static RPCHelpMan gettxspendingprevout()
96 // do nothing, caller has selected to only query the mempool
97 } else if (g_txospenderindex) {
98 // no spending tx in mempool, query txospender index
99- CTransactionRef spending_tx;
100- uint256 block_hash;
101- if (g_txospenderindex->FindSpender(prevout, spending_tx, block_hash)) {
102- o.pushKV("spendingtxid", spending_tx->GetHash().GetHex());
103- o.pushKV("blockhash", block_hash.GetHex());
104+ if (auto spender{g_txospenderindex->FindSpender(prevout)}) {
105+ o.pushKV("spendingtxid", spender->tx->GetHash().GetHex());
106+ o.pushKV("blockhash", spender->block_hash.GetHex());
107 if (return_spending_tx.value_or(false)) {
108- o.pushKV("spendingtx", EncodeHexTx(*spending_tx));
109+ o.pushKV("spendingtx", EncodeHexTx(*spender->tx));
110 }
111 if (!txospenderindex_ready) {
112 // warn if index is not ready as the spending tx that we found may be stale (it may be reorged out)
113diff --git a/src/test/txospenderindex_tests.cpp b/src/test/txospenderindex_tests.cpp
114index 870262ac4d..5f930aceb8 100644
115--- a/src/test/txospenderindex_tests.cpp
116+++ b/src/test/txospenderindex_tests.cpp
117@@ -45,12 +45,9 @@ BOOST_FIXTURE_TEST_CASE(txospenderindex_initial_sync, TestChain100Setup)
118
119 CBlock block = CreateAndProcessBlock(spender, this->m_coinbase_txns[0]->vout[0].scriptPubKey);
120
121- CTransactionRef tx;
122- uint256 block_hash;
123-
124 // Transaction should not be found in the index before it is started.
125 for (const auto& outpoint : spent) {
126- BOOST_CHECK(!txospenderindex.FindSpender(outpoint, tx, block_hash));
127+ BOOST_CHECK(!txospenderindex.FindSpender(outpoint));
128 }
129
130 // BlockUntilSyncedToCurrentChain should return false before txospenderindex is started.
131@@ -58,9 +55,10 @@ BOOST_FIXTURE_TEST_CASE(txospenderindex_initial_sync, TestChain100Setup)
132
133 txospenderindex.Sync();
134 for (size_t i = 0; i < spent.size(); i++) {
135- BOOST_CHECK(txospenderindex.FindSpender(spent[i], tx, block_hash));
136- BOOST_CHECK_EQUAL(tx->GetHash(), spender[i].GetHash());
137- BOOST_CHECK_EQUAL(block_hash, block.GetHash());
138+ auto spend{txospenderindex.FindSpender(spent[i])};
139+ BOOST_CHECK(spend.has_value());
140+ BOOST_CHECK_EQUAL(spend->tx->GetHash(), spender[i].GetHash());
141+ BOOST_CHECK_EQUAL(spend->block_hash, block.GetHash());
142 }
143
144 // It is not safe to stop and destroy the index until it finishes handling