diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -5792,6 +5792,7 @@ int64_t expired = 0; int64_t failed = 0; int64_t already_there = 0; + int64_t unbroadcast = 0; int64_t nNow = GetTime(); try { @@ -5848,6 +5849,15 @@ for (const auto &i : mapDeltas) { pool.PrioritiseTransaction(i.first, i.second); } + + std::set unbroadcast_txids; + file >> unbroadcast_txids; + unbroadcast = unbroadcast_txids.size(); + + for (const auto &txid : unbroadcast_txids) { + pool.AddUnbroadcastTx(txid); + } + } catch (const std::exception &e) { LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing " "anyway.\n", @@ -5856,8 +5866,9 @@ } LogPrintf("Imported mempool transactions from disk: %i succeeded, %i " - "failed, %i expired, %i already there\n", - count, failed, expired, already_there); + "failed, %i expired, %i already there, %i waiting for initial " + "broadcast\n", + count, failed, expired, already_there, unbroadcast); return true; } @@ -5866,6 +5877,7 @@ std::map mapDeltas; std::vector vinfo; + std::set unbroadcast_txids; static Mutex dump_mutex; LOCK(dump_mutex); @@ -5877,6 +5889,7 @@ } vinfo = pool.infoAll(); + unbroadcast_txids = pool.GetUnbroadcastTxs(); } int64_t mid = GetTimeMicros(); @@ -5901,6 +5914,11 @@ } file << mapDeltas; + + LogPrintf("Writing %d unbroadcast transactions to disk.\n", + unbroadcast_txids.size()); + file << unbroadcast_txids; + if (!FileCommit(file.Get())) { throw std::runtime_error("FileCommit failed"); } diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -40,10 +40,14 @@ import time from test_framework.test_framework import BitcoinTestFramework +from test_framework.mininode import P2PTxInvStore from test_framework.util import ( assert_equal, assert_greater_than_or_equal, assert_raises_rpc_error, + connect_nodes, + disconnect_nodes, + wait_until, ) @@ -82,6 +86,12 @@ assert_greater_than_or_equal(tx_creation_time, tx_creation_time_lower) assert_greater_than_or_equal(tx_creation_time_higher, tx_creation_time) + # disconnect nodes & make a txn that remains in the unbroadcast set. + disconnect_nodes(self.nodes[0], self.nodes[2]) + self.nodes[0].sendtoaddress( + self.nodes[2].getnewaddress(), Decimal("12")) + connect_nodes(self.nodes[0], self.nodes[2]) + self.log.debug("Stop-start the nodes. Verify that node0 has the " "transactions in its mempool and node1 does not. " "Verify that node2 calculates its balance correctly " @@ -96,7 +106,7 @@ # start_node is blocking on the mempool being loaded assert self.nodes[0].getmempoolinfo()["loaded"] assert self.nodes[2].getmempoolinfo()["loaded"] - assert_equal(len(self.nodes[0].getrawmempool()), 5) + assert_equal(len(self.nodes[0].getrawmempool()), 6) assert_equal(len(self.nodes[2].getrawmempool()), 5) # The others have loaded their mempool. If node_1 loaded anything, we'd # probably notice by now: @@ -117,10 +127,12 @@ self.nodes[2].syncwithvalidationinterfacequeue() assert_equal(node2_balance, self.nodes[2].getbalance()) + # start node0 with wallet disabled so wallet transactions don't get + # resubmitted self.log.debug( "Stop-start node0 with -persistmempool=0. Verify that it doesn't load its mempool.dat file.") self.stop_nodes() - self.start_node(0, extra_args=["-persistmempool=0"]) + self.start_node(0, extra_args=["-persistmempool=0", "-disablewallet"]) assert self.nodes[0].getmempoolinfo()["loaded"] assert_equal(len(self.nodes[0].getrawmempool()), 0) @@ -129,7 +141,7 @@ self.stop_nodes() self.start_node(0) assert self.nodes[0].getmempoolinfo()["loaded"] - assert_equal(len(self.nodes[0].getrawmempool()), 5) + assert_equal(len(self.nodes[0].getrawmempool()), 6) mempooldat0 = os.path.join( self.nodes[0].datadir, self.chain, 'mempool.dat') @@ -142,12 +154,12 @@ assert os.path.isfile(mempooldat0) self.log.debug( - "Stop nodes, make node1 use mempool.dat from node0. Verify it has 5 transactions") + "Stop nodes, make node1 use mempool.dat from node0. Verify it has 6 transactions") os.rename(mempooldat0, mempooldat1) self.stop_nodes() self.start_node(1, extra_args=[]) assert self.nodes[1].getmempoolinfo()["loaded"] - assert_equal(len(self.nodes[1].getrawmempool()), 5) + assert_equal(len(self.nodes[1].getrawmempool()), 6) self.log.debug( "Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails") @@ -160,6 +172,29 @@ self.nodes[1].savemempool) os.rmdir(mempooldotnew1) + self.test_persist_unbroadcast() + + def test_persist_unbroadcast(self): + node0 = self.nodes[0] + self.start_node(0) + + # clear out mempool + node0.generate(1) + + # disconnect nodes to make a txn that remains in the unbroadcast set. + disconnect_nodes(node0, self.nodes[1]) + node0.sendtoaddress(self.nodes[1].getnewaddress(), Decimal("12")) + + # shutdown, then startup with wallet disabled + self.stop_nodes() + self.start_node(0, extra_args=["-disablewallet"]) + + # check that txn gets broadcast due to unbroadcast logic + conn = node0.add_p2p_connection(P2PTxInvStore()) + # 15 min + 1 for buffer + node0.mockscheduler(16 * 60) + wait_until(lambda: len(conn.get_invs()) == 1) + if __name__ == '__main__': MempoolPersistTest().main() diff --git a/test/functional/mempool_unbroadcast.py b/test/functional/mempool_unbroadcast.py --- a/test/functional/mempool_unbroadcast.py +++ b/test/functional/mempool_unbroadcast.py @@ -59,6 +59,9 @@ assert rpc_tx_hsh not in mempool assert wallet_tx_hsh not in mempool + # ensure that unbroadcast txs are persisted to mempool.dat + self.restart_node(0) + self.log.info("Reconnect nodes & check if they are sent to node 1") connect_nodes(node, self.nodes[1])