Skip to content

Commit ac88e2e

Browse files
committed
Add support for tx-relay via wtxid
This adds a field to CNodeState that tracks whether to relay transactions with that peer via wtxid, instead of txid. As of this commit the field will always be false, but in a later commit we will add a way to negotiate turning this on via p2p messages exchanged with the peer.
1 parent 8e68fc2 commit ac88e2e

File tree

6 files changed

+106
-44
lines changed

6 files changed

+106
-44
lines changed

src/net_processing.cpp

Lines changed: 98 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,9 @@ struct CNodeState {
410410
//! A rolling bloom filter of all announced tx CInvs to this peer.
411411
CRollingBloomFilter m_recently_announced_invs = CRollingBloomFilter{INVENTORY_MAX_RECENT_RELAY, 0.000001};
412412

413+
//! Whether this peer relays txs via wtxid
414+
bool m_wtxid_relay{false};
415+
413416
CNodeState(CAddress addrIn, std::string addrNameIn, bool is_inbound, bool is_manual) :
414417
address(addrIn), name(std::move(addrNameIn)), m_is_inbound(is_inbound),
415418
m_is_manual_connection (is_manual)
@@ -836,7 +839,8 @@ void PeerLogicValidation::ReattemptInitialBroadcast(CScheduler& scheduler) const
836839
for (const auto& elem : unbroadcast_txids) {
837840
// Sanity check: all unbroadcast txns should exist in the mempool
838841
if (m_mempool.exists(elem.first)) {
839-
RelayTransaction(elem.first, *connman);
842+
LOCK(cs_main);
843+
RelayTransaction(elem.first, elem.second, *connman);
840844
} else {
841845
m_mempool.RemoveUnbroadcastTx(elem.first, true);
842846
}
@@ -1405,6 +1409,7 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
14051409
{
14061410
case MSG_TX:
14071411
case MSG_WITNESS_TX:
1412+
case MSG_WTX:
14081413
{
14091414
assert(recentRejects);
14101415
if (::ChainActive().Tip()->GetBlockHash() != hashRecentRejectsChainTip)
@@ -1419,16 +1424,20 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
14191424

14201425
{
14211426
LOCK(g_cs_orphans);
1422-
if (mapOrphanTransactions.count(inv.hash)) return true;
1427+
if (inv.type != MSG_WTX && mapOrphanTransactions.count(inv.hash)) {
1428+
return true;
1429+
} else if (inv.type == MSG_WTX && g_orphans_by_wtxid.count(inv.hash)) {
1430+
return true;
1431+
}
14231432
}
14241433

14251434
{
14261435
LOCK(g_cs_recent_confirmed_transactions);
14271436
if (g_recent_confirmed_transactions->contains(inv.hash)) return true;
14281437
}
14291438

1430-
return recentRejects->contains(inv.hash) ||
1431-
mempool.exists(inv.hash);
1439+
const bool by_wtxid = (inv.type == MSG_WTX);
1440+
return recentRejects->contains(inv.hash) || mempool.exists(inv.hash, by_wtxid);
14321441
}
14331442
case MSG_BLOCK:
14341443
case MSG_WITNESS_BLOCK:
@@ -1438,11 +1447,17 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
14381447
return true;
14391448
}
14401449

1441-
void RelayTransaction(const uint256& txid, const CConnman& connman)
1450+
void RelayTransaction(const uint256& txid, const uint256& wtxid, const CConnman& connman)
14421451
{
1443-
connman.ForEachNode([&txid](CNode* pnode)
1452+
connman.ForEachNode([&txid, &wtxid](CNode* pnode)
14441453
{
1445-
pnode->PushTxInventory(txid);
1454+
AssertLockHeld(cs_main);
1455+
CNodeState &state = *State(pnode->GetId());
1456+
if (state.m_wtxid_relay) {
1457+
pnode->PushTxInventory(wtxid);
1458+
} else {
1459+
pnode->PushTxInventory(txid);
1460+
}
14461461
});
14471462
}
14481463

@@ -1640,9 +1655,9 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
16401655
}
16411656

16421657
//! Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed).
1643-
CTransactionRef static FindTxForGetData(const CNode& peer, const uint256& txid, const std::chrono::seconds mempool_req, const std::chrono::seconds now) LOCKS_EXCLUDED(cs_main)
1658+
CTransactionRef static FindTxForGetData(const CNode& peer, const uint256& txid_or_wtxid, bool use_wtxid, const std::chrono::seconds mempool_req, const std::chrono::seconds now) LOCKS_EXCLUDED(cs_main)
16441659
{
1645-
auto txinfo = mempool.info(txid);
1660+
auto txinfo = mempool.info(txid_or_wtxid, use_wtxid);
16461661
if (txinfo.tx) {
16471662
// If a TX could have been INVed in reply to a MEMPOOL request,
16481663
// or is older than UNCONDITIONAL_RELAY_DELAY, permit the request
@@ -1654,13 +1669,12 @@ CTransactionRef static FindTxForGetData(const CNode& peer, const uint256& txid,
16541669

16551670
{
16561671
LOCK(cs_main);
1657-
16581672
// Otherwise, the transaction must have been announced recently.
1659-
if (State(peer.GetId())->m_recently_announced_invs.contains(txid)) {
1673+
if (State(peer.GetId())->m_recently_announced_invs.contains(txid_or_wtxid)) {
16601674
// If it was, it can be relayed from either the mempool...
16611675
if (txinfo.tx) return std::move(txinfo.tx);
16621676
// ... or the relay pool.
1663-
auto mi = mapRelay.find(txid);
1677+
auto mi = mapRelay.find(txid_or_wtxid);
16641678
if (mi != mapRelay.end()) return mi->second;
16651679
}
16661680
}
@@ -1684,7 +1698,7 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm
16841698
// Process as many TX items from the front of the getdata queue as
16851699
// possible, since they're common and it's efficient to batch process
16861700
// them.
1687-
while (it != pfrom.vRecvGetData.end() && (it->type == MSG_TX || it->type == MSG_WITNESS_TX)) {
1701+
while (it != pfrom.vRecvGetData.end() && (it->type == MSG_TX || it->type == MSG_WITNESS_TX || it->type == MSG_WTX)) {
16881702
if (interruptMsgProc) return;
16891703
// The send buffer provides backpressure. If there's no space in
16901704
// the buffer, pause processing until the next call.
@@ -1697,11 +1711,12 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm
16971711
continue;
16981712
}
16991713

1700-
CTransactionRef tx = FindTxForGetData(pfrom, inv.hash, mempool_req, now);
1714+
CTransactionRef tx = FindTxForGetData(pfrom, inv.hash, inv.type == MSG_WTX, mempool_req, now);
17011715
if (tx) {
1716+
// WTX and WITNESS_TX imply we serialize with witness
17021717
int nSendFlags = (inv.type == MSG_TX ? SERIALIZE_TRANSACTION_NO_WITNESS : 0);
17031718
connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *tx));
1704-
mempool.RemoveUnbroadcastTx(inv.hash);
1719+
mempool.RemoveUnbroadcastTx(tx->GetHash());
17051720
// As we're going to send tx, make sure its unconfirmed parents are made requestable.
17061721
for (const auto& txin : tx->vin) {
17071722
auto txinfo = mempool.info(txin.prevout.hash);
@@ -1980,7 +1995,7 @@ void static ProcessOrphanTx(CConnman& connman, CTxMemPool& mempool, std::set<uin
19801995
if (setMisbehaving.count(fromPeer)) continue;
19811996
if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
19821997
LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString());
1983-
RelayTransaction(orphanHash, connman);
1998+
RelayTransaction(orphanHash, porphanTx->GetWitnessHash(), connman);
19841999
for (unsigned int i = 0; i < orphanTx.vout.size(); i++) {
19852000
auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(orphanHash, i));
19862001
if (it_by_prev != mapOrphanTransactionsByPrev.end()) {
@@ -2841,23 +2856,47 @@ void ProcessMessage(
28412856
const CTransaction& tx = *ptx;
28422857

28432858
const uint256& txid = ptx->GetHash();
2844-
pfrom.AddInventoryKnown(txid);
2859+
const uint256& wtxid = ptx->GetWitnessHash();
28452860

28462861
LOCK2(cs_main, g_cs_orphans);
28472862

2863+
CNodeState* nodestate = State(pfrom.GetId());
2864+
2865+
const uint256& hash = nodestate->m_wtxid_relay ? wtxid : txid;
2866+
pfrom.AddInventoryKnown(hash);
2867+
if (nodestate->m_wtxid_relay && txid != wtxid) {
2868+
// Insert txid into filterInventoryKnown, even for
2869+
// wtxidrelay peers. This prevents re-adding of
2870+
// unconfirmed parents to the recently_announced
2871+
// filter, when a child tx is requested. See
2872+
// ProcessGetData().
2873+
pfrom.AddInventoryKnown(txid);
2874+
}
2875+
28482876
TxValidationState state;
28492877

2850-
CNodeState* nodestate = State(pfrom.GetId());
2851-
nodestate->m_tx_download.m_tx_announced.erase(txid);
2852-
nodestate->m_tx_download.m_tx_in_flight.erase(txid);
2853-
EraseTxRequest(txid);
2878+
nodestate->m_tx_download.m_tx_announced.erase(hash);
2879+
nodestate->m_tx_download.m_tx_in_flight.erase(hash);
2880+
EraseTxRequest(hash);
28542881

28552882
std::list<CTransactionRef> lRemovedTxn;
28562883

2857-
if (!AlreadyHave(CInv(MSG_TX, txid), mempool) &&
2884+
// We do the AlreadyHave() check using wtxid, rather than txid - in the
2885+
// absence of witness malleation, this is strictly better, because the
2886+
// recent rejects filter may contain the wtxid but will never contain
2887+
// the txid of a segwit transaction that has been rejected.
2888+
// In the presence of witness malleation, it's possible that by only
2889+
// doing the check with wtxid, we could overlook a transaction which
2890+
// was confirmed with a different witness, or exists in our mempool
2891+
// with a different witness, but this has limited downside:
2892+
// mempool validation does its own lookup of whether we have the txid
2893+
// already; and an adversary can already relay us old transactions
2894+
// (older than our recency filter) if trying to DoS us, without any need
2895+
// for witness malleation.
2896+
if (!AlreadyHave(CInv(MSG_WTX, wtxid), mempool) &&
28582897
AcceptToMemoryPool(mempool, state, ptx, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
28592898
mempool.check(&::ChainstateActive().CoinsTip());
2860-
RelayTransaction(tx.GetHash(), connman);
2899+
RelayTransaction(tx.GetHash(), tx.GetWitnessHash(), connman);
28612900
for (unsigned int i = 0; i < tx.vout.size(); i++) {
28622901
auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(txid, i));
28632902
if (it_by_prev != mapOrphanTransactionsByPrev.end()) {
@@ -2890,10 +2929,17 @@ void ProcessMessage(
28902929
uint32_t nFetchFlags = GetFetchFlags(pfrom);
28912930
const auto current_time = GetTime<std::chrono::microseconds>();
28922931

2893-
for (const CTxIn& txin : tx.vin) {
2894-
CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash);
2895-
pfrom.AddInventoryKnown(txin.prevout.hash);
2896-
if (!AlreadyHave(_inv, mempool)) RequestTx(State(pfrom.GetId()), _inv.hash, current_time);
2932+
if (!State(pfrom.GetId())->m_wtxid_relay) {
2933+
for (const CTxIn& txin : tx.vin) {
2934+
// Here, we only have the txid (and not wtxid) of the
2935+
// inputs, so we only request parents from
2936+
// non-wtxid-relay peers.
2937+
// Eventually we should replace this with an improved
2938+
// protocol for getting all unconfirmed parents.
2939+
CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash);
2940+
pfrom.AddInventoryKnown(txin.prevout.hash);
2941+
if (!AlreadyHave(_inv, mempool)) RequestTx(State(pfrom.GetId()), _inv.hash, current_time);
2942+
}
28972943
}
28982944
AddOrphanTx(ptx, pfrom.GetId());
28992945

@@ -2933,7 +2979,7 @@ void ProcessMessage(
29332979
LogPrintf("Not relaying non-mempool transaction %s from forcerelay peer=%d\n", tx.GetHash().ToString(), pfrom.GetId());
29342980
} else {
29352981
LogPrintf("Force relaying tx %s from peer=%d\n", tx.GetHash().ToString(), pfrom.GetId());
2936-
RelayTransaction(tx.GetHash(), connman);
2982+
RelayTransaction(tx.GetHash(), tx.GetWitnessHash(), connman);
29372983
}
29382984
}
29392985
}
@@ -3573,7 +3619,7 @@ void ProcessMessage(
35733619
vRecv >> vInv;
35743620
if (vInv.size() <= MAX_PEER_TX_IN_FLIGHT + MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
35753621
for (CInv &inv : vInv) {
3576-
if (inv.type == MSG_TX || inv.type == MSG_WITNESS_TX) {
3622+
if (inv.type == MSG_TX || inv.type == MSG_WITNESS_TX || inv.type == MSG_WTX) {
35773623
// If we receive a NOTFOUND message for a txid we requested, erase
35783624
// it from our data structures for this peer.
35793625
auto in_flight_it = state->m_tx_download.m_tx_in_flight.find(inv.hash);
@@ -3858,17 +3904,19 @@ namespace {
38583904
class CompareInvMempoolOrder
38593905
{
38603906
CTxMemPool *mp;
3907+
bool m_wtxid_relay;
38613908
public:
3862-
explicit CompareInvMempoolOrder(CTxMemPool *_mempool)
3909+
explicit CompareInvMempoolOrder(CTxMemPool *_mempool, bool use_wtxid)
38633910
{
38643911
mp = _mempool;
3912+
m_wtxid_relay = use_wtxid;
38653913
}
38663914

38673915
bool operator()(std::set<uint256>::iterator a, std::set<uint256>::iterator b)
38683916
{
38693917
/* As std::make_heap produces a max-heap, we want the entries with the
38703918
* fewest ancestors/highest fee to sort later. */
3871-
return mp->CompareDepthAndScore(*b, *a);
3919+
return mp->CompareDepthAndScore(*b, *a, m_wtxid_relay);
38723920
}
38733921
};
38743922
}
@@ -4175,8 +4223,8 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
41754223
LOCK(pto->m_tx_relay->cs_filter);
41764224

41774225
for (const auto& txinfo : vtxinfo) {
4178-
const uint256& hash = txinfo.tx->GetHash();
4179-
CInv inv(MSG_TX, hash);
4226+
const uint256& hash = state.m_wtxid_relay ? txinfo.tx->GetWitnessHash() : txinfo.tx->GetHash();
4227+
CInv inv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash);
41804228
pto->m_tx_relay->setInventoryTxToSend.erase(hash);
41814229
// Don't send transactions that peers will not put into their mempool
41824230
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
@@ -4211,7 +4259,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
42114259
}
42124260
// Topologically and fee-rate sort the inventory we send for privacy and priority reasons.
42134261
// A heap is used so that not all items need sorting if only a few are being sent.
4214-
CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool);
4262+
CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool, state.m_wtxid_relay);
42154263
std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
42164264
// No reason to drain out at many times the network's capacity,
42174265
// especially since we have many peers and some will draw much shorter delays.
@@ -4230,18 +4278,20 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
42304278
continue;
42314279
}
42324280
// Not in the mempool anymore? don't bother sending it.
4233-
auto txinfo = m_mempool.info(hash);
4281+
auto txinfo = m_mempool.info(hash, state.m_wtxid_relay);
42344282
if (!txinfo.tx) {
42354283
continue;
42364284
}
4285+
auto txid = txinfo.tx->GetHash();
4286+
auto wtxid = txinfo.tx->GetWitnessHash();
42374287
// Peer told you to not send transactions at that feerate? Don't bother sending it.
42384288
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
42394289
continue;
42404290
}
42414291
if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
42424292
// Send
42434293
State(pto->GetId())->m_recently_announced_invs.insert(hash);
4244-
vInv.push_back(CInv(MSG_TX, hash));
4294+
vInv.push_back(CInv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash));
42454295
nRelayedTransactions++;
42464296
{
42474297
// Expire old relay messages
@@ -4251,12 +4301,12 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
42514301
vRelayExpiration.pop_front();
42524302
}
42534303

4254-
auto ret = mapRelay.insert(std::make_pair(hash, std::move(txinfo.tx)));
4304+
auto ret = mapRelay.emplace(txid, std::move(txinfo.tx));
42554305
if (ret.second) {
4256-
vRelayExpiration.push_back(std::make_pair(nNow + std::chrono::microseconds{RELAY_TX_CACHE_TIME}.count(), ret.first));
4306+
vRelayExpiration.emplace_back(nNow + std::chrono::microseconds{RELAY_TX_CACHE_TIME}.count(), ret.first);
42574307
}
42584308
// Add wtxid-based lookup into mapRelay as well, so that peers can request by wtxid
4259-
auto ret2 = mapRelay.emplace(ret.first->second->GetWitnessHash(), ret.first->second);
4309+
auto ret2 = mapRelay.emplace(wtxid, ret.first->second);
42604310
if (ret2.second) {
42614311
vRelayExpiration.emplace_back(nNow + std::chrono::microseconds{RELAY_TX_CACHE_TIME}.count(), ret2.first);
42624312
}
@@ -4266,6 +4316,14 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
42664316
vInv.clear();
42674317
}
42684318
pto->m_tx_relay->filterInventoryKnown.insert(hash);
4319+
if (hash != txid) {
4320+
// Insert txid into filterInventoryKnown, even for
4321+
// wtxidrelay peers. This prevents re-adding of
4322+
// unconfirmed parents to the recently_announced
4323+
// filter, when a child tx is requested. See
4324+
// ProcessGetData().
4325+
pto->m_tx_relay->filterInventoryKnown.insert(txid);
4326+
}
42694327
}
42704328
}
42714329
}
@@ -4390,7 +4448,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
43904448
// Erase this entry from tx_process_time (it may be added back for
43914449
// processing at a later time, see below)
43924450
tx_process_time.erase(tx_process_time.begin());
4393-
CInv inv(MSG_TX | GetFetchFlags(*pto), txid);
4451+
CInv inv(state.m_wtxid_relay ? MSG_WTX : (MSG_TX | GetFetchFlags(*pto)), txid);
43944452
if (!AlreadyHave(inv, m_mempool)) {
43954453
// If this transaction was last requested more than 1 minute ago,
43964454
// then request.

src/net_processing.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,6 @@ struct CNodeStateStats {
100100
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats);
101101

102102
/** Relay transaction to every node */
103-
void RelayTransaction(const uint256&, const CConnman& connman);
103+
void RelayTransaction(const uint256& txid, const uint256& wtxid, const CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
104104

105105
#endif // BITCOIN_NET_PROCESSING_H

src/node/transaction.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
8282
// best-effort of initial broadcast
8383
node.mempool->AddUnbroadcastTx(hashTx, tx->GetWitnessHash());
8484

85-
RelayTransaction(hashTx, *node.connman);
85+
LOCK(cs_main);
86+
RelayTransaction(hashTx, tx->GetWitnessHash(), *node.connman);
8687
}
8788

8889
return TransactionError::OK;

src/protocol.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ std::string CInv::GetCommand() const
177177
switch (masked)
178178
{
179179
case MSG_TX: return cmd.append(NetMsgType::TX);
180+
// WTX is not a message type, just an inv type
181+
case MSG_WTX: return cmd.append("wtx");
180182
case MSG_BLOCK: return cmd.append(NetMsgType::BLOCK);
181183
case MSG_FILTERED_BLOCK: return cmd.append(NetMsgType::MERKLEBLOCK);
182184
case MSG_CMPCT_BLOCK: return cmd.append(NetMsgType::CMPCTBLOCK);

src/protocol.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,10 +397,11 @@ const uint32_t MSG_TYPE_MASK = 0xffffffff >> 2;
397397
* These numbers are defined by the protocol. When adding a new value, be sure
398398
* to mention it in the respective BIP.
399399
*/
400-
enum GetDataMsg {
400+
enum GetDataMsg : uint32_t {
401401
UNDEFINED = 0,
402402
MSG_TX = 1,
403403
MSG_BLOCK = 2,
404+
MSG_WTX = 5, //!< Defined in BIP 339
404405
// The following can only occur in getdata. Invs always use TX or BLOCK.
405406
MSG_FILTERED_BLOCK = 3, //!< Defined in BIP37
406407
MSG_CMPCT_BLOCK = 4, //!< Defined in BIP152

0 commit comments

Comments
 (0)