0diff --git a/src/test/txrebroadcast_tests.cpp b/src/test/txrebroadcast_tests.cpp
  1index 6fdd5deb8f..d10a962ab3 100644
  2--- a/src/test/txrebroadcast_tests.cpp
  3+++ b/src/test/txrebroadcast_tests.cpp
  4@@ -27,34 +27,13 @@ public:
  5 
  6     bool CheckRecordedAttempt(uint256 txhsh, int expected_count, std::chrono::microseconds expected_timestamp)
  7     {
  8-        const auto it = m_attempt_tracker.find(txhsh);
  9-        if (it == m_attempt_tracker.end()) return false;
 10-        if (it->m_count != expected_count) return false;
 11-
 12-        // Check the recorded timestamp is within 2 seconds of the param passed in
 13-        std::chrono::microseconds delta = expected_timestamp - it->m_last_attempt;
 14-        if (delta.count() > 2) return false;
 15-
 16-        return true;
 17-    };
 18+        return TxRebroadcastHandler::CheckRecordedAttempt(txhsh, expected_count, expected_timestamp);
 19+    }
 20 
 21     void UpdateAttempt(uint256 txhsh, int count)
 22     {
 23-        auto it = m_attempt_tracker.find(txhsh);
 24-        for (int i = 0; i < count; ++i) {
 25-            RecordAttempt(it);
 26-        }
 27-    };
 28-
 29-    void RecordAttempt(indexed_rebroadcast_set::index<index_by_wtxid>::type::iterator& entry_it)
 30-    {
 31-        auto UpdateRebroadcastEntry = [](RebroadcastEntry& rebroadcast_entry) {
 32-            rebroadcast_entry.m_last_attempt = GetTime<std::chrono::microseconds>() - 4h;
 33-            ++rebroadcast_entry.m_count;
 34-        };
 35-
 36-        m_attempt_tracker.modify(entry_it, UpdateRebroadcastEntry);
 37-    };
 38+        TxRebroadcastHandler::UpdateAttempt(txhsh, count);
 39+    }
 40 
 41     void UpdateCachedFeeRate(CFeeRate new_fee_rate)
 42     {
 43diff --git a/src/txrebroadcast.cpp b/src/txrebroadcast.cpp
 44index 3cb9abaf69..49050c0d39 100644
 45--- a/src/txrebroadcast.cpp
 46+++ b/src/txrebroadcast.cpp
 47@@ -10,6 +10,11 @@
 48 #include <txrebroadcast.h>
 49 #include <validation.h>
 50 
 51+#include <boost/multi_index/hashed_index.hpp>
 52+#include <boost/multi_index/member.hpp>
 53+#include <boost/multi_index/ordered_index.hpp>
 54+#include <boost/multi_index_container.hpp>
 55+
 56 /** We rebroadcast 3/4 of max block weight to reduce noise due to circumstances
 57  *  such as miners mining priority transactions. */
 58 static constexpr unsigned int MAX_REBROADCAST_WEIGHT = 3 * MAX_BLOCK_WEIGHT / 4;
 59@@ -30,6 +35,46 @@ static constexpr int MAX_ENTRIES = 500;
 60 /** The maximum age of an entry ~3 months */
 61 static constexpr std::chrono::hours MAX_ENTRY_AGE = std::chrono::hours(3 * 30 * 24);
 62 
 63+/** Used for multi_index tag  */
 64+struct index_by_last_attempt {};
 65+
 66+struct RebroadcastEntry {
 67+    RebroadcastEntry(std::chrono::microseconds now_time, uint256 wtxid)
 68+        : m_last_attempt(now_time),
 69+          m_wtxid(wtxid),
 70+          m_count(1) {}
 71+
 72+    std::chrono::microseconds m_last_attempt;
 73+    const uint256 m_wtxid;
 74+    int m_count;
 75+};
 76+
 77+class indexed_rebroadcast_set : public
 78+boost::multi_index_container<
 79+    RebroadcastEntry,
 80+    boost::multi_index::indexed_by<
 81+        // sorted by wtxid
 82+        boost::multi_index::hashed_unique<
 83+            boost::multi_index::tag<index_by_wtxid>,
 84+            boost::multi_index::member<RebroadcastEntry, const uint256, &RebroadcastEntry::m_wtxid>,
 85+            SaltedTxidHasher
 86+        >,
 87+        // sorted by last rebroadcast time
 88+        boost::multi_index::ordered_non_unique<
 89+            boost::multi_index::tag<index_by_last_attempt>,
 90+            boost::multi_index::member<RebroadcastEntry, std::chrono::microseconds, &RebroadcastEntry::m_last_attempt>
 91+        >
 92+    >
 93+> {};
 94+
 95+TxRebroadcastHandler::TxRebroadcastHandler(CTxMemPool& mempool, ChainstateManager& chainman)
 96+    : m_attempt_tracker{std::make_unique<indexed_rebroadcast_set>()}
 97+    , m_mempool{mempool}
 98+    , m_chainman{chainman}
 99+{}
100+
101+TxRebroadcastHandler::~TxRebroadcastHandler() = default;
102+
103 std::vector<TxIds> TxRebroadcastHandler::GetRebroadcastTransactions()
104 {
105     std::vector<TxIds> rebroadcast_txs;
106@@ -58,12 +103,12 @@ std::vector<TxIds> TxRebroadcastHandler::GetRebroadcastTransactions()
107 
108         // Check if we have previously rebroadcasted, decide if we will this
109         // round, and if so, record the attempt.
110-        auto entry_it = m_attempt_tracker.find(wtxid);
111+        auto entry_it = m_attempt_tracker->find(wtxid);
112 
113-        if (entry_it == m_attempt_tracker.end()) {
114+        if (entry_it == m_attempt_tracker->end()) {
115             // No existing entry, we will rebroadcast, so create a new one
116             RebroadcastEntry entry(start_time, wtxid);
117-            m_attempt_tracker.insert(entry);
118+            m_attempt_tracker->insert(entry);
119         } else if (entry_it->m_count >= MAX_REBROADCAST_COUNT) {
120             // We have already rebroadcast this transaction the maximum number
121             // of times permitted, so skip rebroadcasting.
122@@ -76,7 +121,12 @@ std::vector<TxIds> TxRebroadcastHandler::GetRebroadcastTransactions()
123         } else {
124             // We have rebroadcasted this transaction before, but will try
125             // again now.
126-            RecordAttempt(entry_it);
127+            auto UpdateRebroadcastEntry = [](RebroadcastEntry& rebroadcast_entry) {
128+                rebroadcast_entry.m_last_attempt = GetTime<std::chrono::microseconds>();
129+                ++rebroadcast_entry.m_count;
130+            };
131+
132+            m_attempt_tracker->modify(entry_it, UpdateRebroadcastEntry);
133         }
134 
135         // Add to set of rebroadcast candidates
136@@ -97,6 +147,32 @@ std::vector<TxIds> TxRebroadcastHandler::GetRebroadcastTransactions()
137     return rebroadcast_txs;
138 };
139 
140+void TxRebroadcastHandler::UpdateAttempt(uint256 txhsh, int count)
141+{
142+    auto it = m_attempt_tracker->find(txhsh);
143+    for (int i = 0; i < count; ++i) {
144+        auto UpdateRebroadcastEntry = [](RebroadcastEntry& rebroadcast_entry) {
145+            rebroadcast_entry.m_last_attempt = GetTime<std::chrono::microseconds>() - 4h;
146+            ++rebroadcast_entry.m_count;
147+        };
148+
149+        m_attempt_tracker->modify(it, UpdateRebroadcastEntry);
150+    }
151+};
152+
153+bool TxRebroadcastHandler::CheckRecordedAttempt(uint256 txhsh, int expected_count, std::chrono::microseconds expected_timestamp)
154+{
155+    const auto it = m_attempt_tracker->find(txhsh);
156+    if (it == m_attempt_tracker->end()) return false;
157+    if (it->m_count != expected_count) return false;
158+
159+    // Check the recorded timestamp is within 2 seconds of the param passed in
160+    std::chrono::microseconds delta = expected_timestamp - it->m_last_attempt;
161+    if (delta.count() > 2) return false;
162+
163+    return true;
164+};
165+
166 void TxRebroadcastHandler::CacheMinRebroadcastFee()
167 {
168     // Update stamp of chain tip on cache run
169@@ -109,33 +185,23 @@ void TxRebroadcastHandler::CacheMinRebroadcastFee()
170     LogPrint(BCLog::BENCH, "Caching minimum fee for rebroadcast to %s, took %d µs to calculate.\n", m_cached_fee_rate.ToString(FeeEstimateMode::SAT_VB), delta_time.count());
171 };
172 
173-void TxRebroadcastHandler::RecordAttempt(indexed_rebroadcast_set::index<index_by_wtxid>::type::iterator& entry_it)
174-{
175-    auto UpdateRebroadcastEntry = [](RebroadcastEntry& rebroadcast_entry) {
176-        rebroadcast_entry.m_last_attempt = GetTime<std::chrono::microseconds>();
177-        ++rebroadcast_entry.m_count;
178-    };
179-
180-    m_attempt_tracker.modify(entry_it, UpdateRebroadcastEntry);
181-};
182-
183 void TxRebroadcastHandler::TrimMaxRebroadcast()
184 {
185     // Delete any entries that are older than MAX_ENTRY_AGE
186     std::chrono::microseconds min_age = GetTime<std::chrono::microseconds>() - MAX_ENTRY_AGE;
187 
188-    while (!m_attempt_tracker.empty()) {
189-        auto it = m_attempt_tracker.get<index_by_last_attempt>().begin();
190+    while (!m_attempt_tracker->empty()) {
191+        auto it = m_attempt_tracker->get<index_by_last_attempt>().begin();
192         if (it->m_last_attempt < min_age) {
193-            m_attempt_tracker.get<index_by_last_attempt>().erase(it);
194+            m_attempt_tracker->get<index_by_last_attempt>().erase(it);
195         } else {
196             break;
197         }
198     }
199 
200     // If there are still too many entries, delete the oldest ones
201-    while (m_attempt_tracker.size() > MAX_ENTRIES) {
202-        auto it = m_attempt_tracker.get<index_by_last_attempt>().begin();
203-        m_attempt_tracker.get<index_by_last_attempt>().erase(it);
204+    while (m_attempt_tracker->size() > MAX_ENTRIES) {
205+        auto it = m_attempt_tracker->get<index_by_last_attempt>().begin();
206+        m_attempt_tracker->get<index_by_last_attempt>().erase(it);
207     }
208 };
209diff --git a/src/txrebroadcast.h b/src/txrebroadcast.h
210index 8a176a08bc..ec3ea34ed7 100644
211--- a/src/txrebroadcast.h
212+++ b/src/txrebroadcast.h
213@@ -12,11 +12,6 @@
214 #include <util/time.h>
215 #include <validation.h>
216 
217-#include <boost/multi_index/hashed_index.hpp>
218-#include <boost/multi_index/member.hpp>
219-#include <boost/multi_index/ordered_index.hpp>
220-#include <boost/multi_index_container.hpp>
221-
222 struct TxIds {
223     TxIds(uint256 txid, uint256 wtxid) : m_txid(txid), m_wtxid(wtxid) {}
224 
225@@ -24,43 +19,15 @@ struct TxIds {
226     const uint256 m_wtxid;
227 };
228 
229-struct RebroadcastEntry {
230-    RebroadcastEntry(std::chrono::microseconds now_time, uint256 wtxid)
231-        : m_last_attempt(now_time),
232-          m_wtxid(wtxid),
233-          m_count(1) {}
234-
235-    std::chrono::microseconds m_last_attempt;
236-    const uint256 m_wtxid;
237-    int m_count;
238-};
239-
240-/** Used for multi_index tag  */
241-struct index_by_last_attempt {};
242-
243-using indexed_rebroadcast_set = boost::multi_index_container<
244-    RebroadcastEntry,
245-    boost::multi_index::indexed_by<
246-        // sorted by wtxid
247-        boost::multi_index::hashed_unique<
248-            boost::multi_index::tag<index_by_wtxid>,
249-            boost::multi_index::member<RebroadcastEntry, const uint256, &RebroadcastEntry::m_wtxid>,
250-            SaltedTxidHasher
251-        >,
252-        // sorted by last rebroadcast time
253-        boost::multi_index::ordered_non_unique<
254-            boost::multi_index::tag<index_by_last_attempt>,
255-            boost::multi_index::member<RebroadcastEntry, std::chrono::microseconds, &RebroadcastEntry::m_last_attempt>
256-        >
257-    >
258->;
259+class indexed_rebroadcast_set;
260 
261 class TxRebroadcastHandler
262 {
263 public:
264-    TxRebroadcastHandler(CTxMemPool& mempool, ChainstateManager& chainman)
265-        : m_mempool(mempool),
266-          m_chainman(chainman){};
267+    TxRebroadcastHandler(CTxMemPool& mempool, ChainstateManager& chainman);
268+    ~TxRebroadcastHandler();
269+
270+    TxRebroadcastHandler(const TxRebroadcastHandler& other) = delete;
271 
272     std::vector<TxIds> GetRebroadcastTransactions();
273 
274@@ -75,10 +42,13 @@ protected:
275     CFeeRate m_cached_fee_rate;
276 
277     /** Keep track of previous rebroadcast attempts */
278-    indexed_rebroadcast_set m_attempt_tracker;
279+    std::unique_ptr<indexed_rebroadcast_set> m_attempt_tracker;
280+
281+    /** Test only */
282+    void UpdateAttempt(uint256 txhsh, int count);
283 
284-    /** Update an existing RebroadcastEntry - increment count and update timestamp */
285-    void RecordAttempt(indexed_rebroadcast_set::index<index_by_wtxid>::type::iterator& entry_it);
286+    /** Test only */
287+    bool CheckRecordedAttempt(uint256 txhsh, int expected_count, std::chrono::microseconds expected_timestamp);
288 
289 private:
290     const CTxMemPool& m_mempool;