Skip to content

Commit ad3a61c

Browse files
committed
Merge #18895: p2p: unbroadcast followups: rpcs, nLastResend, mempool sanity check
651f1d8 [test] wait for inital broadcast before comparing mempool entries (gzhao408) 9d3f7eb [mempool] sanity check that all unbroadcast txns are in mempool (gzhao408) a7ebe48 [rpc] add unbroadcast info to mempool entries and getmempoolinfo (gzhao408) d160069 [wallet] remove nLastResend logic (gzhao408) Pull request description: Followup to #18038 by amitiuttarwar which introduces the unbroadcast set: "a mechanism for the mempool to track locally submitted transactions" and decreases the frequency of rebroadcast from 10-15 minutes to 12-36 hours. This PR addresses some of the outstanding TODOs building on top of it: - remove `nLastResend` logic, which is used to ensure rebroadcast doesn't happen again if no new block has been mined (makes sense in 10-15 min period, but not necessary for 12-36 hour period). (#18038 [comment](bitcoin/bitcoin#18038 (comment))) - expose unbroadcast info via RPCs, for more informative queries and testing (#18038 [comment](bitcoin/bitcoin#18038 (comment))) - add sanity check to verify unbroadcast transactions still exist in mempool before rebroadcasting (#18038 [comment](bitcoin/bitcoin#18038 (comment))) ACKs for top commit: naumenkogs: Code review ACK 651f1d8 amitiuttarwar: ACK 651f1d8 🎉 MarcoFalke: Review ACK 651f1d8 Tree-SHA512: d5327e95ef39d44152b48df5c610502ae11c168f43dbbfb2885340c93d1ba9426eb3a5794573f5fc843502109cb3ffb63efa3f2db4f8f112efcde8f76d9a8845
2 parents 9abed46 + 651f1d8 commit ad3a61c

File tree

8 files changed

+52
-8
lines changed

8 files changed

+52
-8
lines changed

src/net_processing.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,12 @@ void PeerLogicValidation::ReattemptInitialBroadcast(CScheduler& scheduler) const
817817
std::set<uint256> unbroadcast_txids = m_mempool.GetUnbroadcastTxs();
818818

819819
for (const uint256& txid : unbroadcast_txids) {
820-
RelayTransaction(txid, *connman);
820+
// Sanity check: all unbroadcast txns should exist in the mempool
821+
if (m_mempool.exists(txid)) {
822+
RelayTransaction(txid, *connman);
823+
} else {
824+
m_mempool.RemoveUnbroadcastTx(txid, true);
825+
}
821826
}
822827

823828
// schedule next run for 10-15 minutes in the future

src/rpc/blockchain.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ static std::vector<RPCResult> MempoolEntryDescription() { return {
408408
RPCResult{RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction",
409409
{RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}},
410410
RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"},
411+
RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet confirmed)"},
411412
};}
412413

413414
static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
@@ -469,6 +470,7 @@ static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPool
469470
}
470471

471472
info.pushKV("bip125-replaceable", rbfStatus);
473+
info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetHash()));
472474
}
473475

474476
UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose)
@@ -1398,7 +1400,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool)
13981400
ret.pushKV("maxmempool", (int64_t) maxmempool);
13991401
ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK()));
14001402
ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
1401-
1403+
ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
14021404
return ret;
14031405
}
14041406

@@ -1417,6 +1419,7 @@ static UniValue getmempoolinfo(const JSONRPCRequest& request)
14171419
{RPCResult::Type::NUM, "maxmempool", "Maximum memory usage for the mempool"},
14181420
{RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + CURRENCY_UNIT + "/kB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee"},
14191421
{RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"},
1422+
{RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"}
14201423
}},
14211424
RPCExamples{
14221425
HelpExampleCli("getmempoolinfo", "")

src/txmempool.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,10 @@ class CTxMemPool
704704
/** Adds a transaction to the unbroadcast set */
705705
void AddUnbroadcastTx(const uint256& txid) {
706706
LOCK(cs);
707-
m_unbroadcast_txids.insert(txid);
707+
/** Sanity Check: the transaction should also be in the mempool */
708+
if (exists(txid)) {
709+
m_unbroadcast_txids.insert(txid);
710+
}
708711
}
709712

710713
/** Removes a transaction from the unbroadcast set */
@@ -716,6 +719,12 @@ class CTxMemPool
716719
return m_unbroadcast_txids;
717720
}
718721

722+
// Returns if a txid is in the unbroadcast set
723+
bool IsUnbroadcastTx(const uint256& txid) const {
724+
LOCK(cs);
725+
return (m_unbroadcast_txids.count(txid) != 0);
726+
}
727+
719728
private:
720729
/** UpdateForDescendants is used by UpdateTransactionsFromBlock to update
721730
* the descendants for a single transaction that has been added to the

src/wallet/wallet.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,10 +1982,6 @@ void CWallet::ResendWalletTransactions()
19821982
nNextResend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60);
19831983
if (fFirst) return;
19841984

1985-
// Only do it if there's been a new block since last time
1986-
if (m_best_block_time < nLastResend) return;
1987-
nLastResend = GetTime();
1988-
19891985
int submitted_tx_count = 0;
19901986

19911987
{ // cs_wallet scope

src/wallet/wallet.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,6 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
641641
int nWalletMaxVersion GUARDED_BY(cs_wallet) = FEATURE_BASE;
642642

643643
int64_t nNextResend = 0;
644-
int64_t nLastResend = 0;
645644
bool fBroadcastTransactions = false;
646645
// Local time that the tip block was received. Used to schedule wallet rebroadcasts.
647646
std::atomic<int64_t> m_best_block_time {0};

test/functional/mempool_packages.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from decimal import Decimal
88

99
from test_framework.messages import COIN
10+
from test_framework.mininode import P2PTxInvStore
1011
from test_framework.test_framework import BitcoinTestFramework
1112
from test_framework.util import (
1213
assert_equal,
@@ -58,6 +59,7 @@ def chain_transaction(self, node, parent_txid, vout, value, fee, num_outputs):
5859

5960
def run_test(self):
6061
# Mine some blocks and have them mature.
62+
self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs
6163
self.nodes[0].generate(101)
6264
utxo = self.nodes[0].listunspent(10)
6365
txid = utxo[0]['txid']
@@ -72,6 +74,10 @@ def run_test(self):
7274
value = sent_value
7375
chain.append(txid)
7476

77+
# Wait until mempool transactions have passed initial broadcast (sent inv and received getdata)
78+
# Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between
79+
self.nodes[0].p2p.wait_for_broadcast(chain)
80+
7581
# Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor
7682
# count and fees should look correct
7783
mempool = self.nodes[0].getrawmempool(True)
@@ -212,6 +218,10 @@ def run_test(self):
212218
for tx in chain[:MAX_ANCESTORS_CUSTOM]:
213219
assert tx in mempool1
214220
# TODO: more detailed check of node1's mempool (fees etc.)
221+
# check transaction unbroadcast info (should be false if in both mempools)
222+
mempool = self.nodes[0].getrawmempool(True)
223+
for tx in mempool:
224+
assert_equal(mempool[tx]['unbroadcast'], False)
215225

216226
# TODO: test ancestor size limits
217227

test/functional/mempool_unbroadcast.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ def test_broadcast(self):
5353
txFS = node.signrawtransactionwithwallet(txF["hex"])
5454
rpc_tx_hsh = node.sendrawtransaction(txFS["hex"])
5555

56+
# check transactions are in unbroadcast using rpc
57+
mempoolinfo = self.nodes[0].getmempoolinfo()
58+
assert_equal(mempoolinfo['unbroadcastcount'], 2)
59+
mempool = self.nodes[0].getrawmempool(True)
60+
for tx in mempool:
61+
assert_equal(mempool[tx]['unbroadcast'], True)
62+
5663
# check that second node doesn't have these two txns
5764
mempool = self.nodes[1].getrawmempool()
5865
assert rpc_tx_hsh not in mempool
@@ -71,6 +78,11 @@ def test_broadcast(self):
7178
assert rpc_tx_hsh in mempool
7279
assert wallet_tx_hsh in mempool
7380

81+
# check that transactions are no longer in first node's unbroadcast set
82+
mempool = self.nodes[0].getrawmempool(True)
83+
for tx in mempool:
84+
assert_equal(mempool[tx]['unbroadcast'], False)
85+
7486
self.log.info("Add another connection & ensure transactions aren't broadcast again")
7587

7688
conn = node.add_p2p_connection(P2PTxInvStore())

test/functional/test_framework/mininode.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ def __init__(self):
645645
self.tx_invs_received = defaultdict(int)
646646

647647
def on_inv(self, message):
648+
super().on_inv(message) # Send getdata in response.
648649
# Store how many times invs have been received for each tx.
649650
for i in message.inv:
650651
if i.type == MSG_TX:
@@ -654,3 +655,12 @@ def on_inv(self, message):
654655
def get_invs(self):
655656
with mininode_lock:
656657
return list(self.tx_invs_received.keys())
658+
659+
def wait_for_broadcast(self, txns, timeout=60):
660+
"""Waits for the txns (list of txids) to complete initial broadcast.
661+
The mempool should mark unbroadcast=False for these transactions.
662+
"""
663+
# Wait until invs have been received (and getdatas sent) for each txid.
664+
self.wait_until(lambda: set(self.get_invs()) == set([int(tx, 16) for tx in txns]), timeout)
665+
# Flush messages and wait for the getdatas to be processed
666+
self.sync_with_ping()

0 commit comments

Comments
 (0)