Skip to content

Commit 2599277

Browse files
sdaftuarjnewbery
authored andcommitted
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 be1b7a8 commit 2599277

File tree

6 files changed

+98
-39
lines changed

6 files changed

+98
-39
lines changed

src/net_processing.cpp

Lines changed: 90 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,9 @@ struct CNodeState {
362362
//! Whether this peer is a manual connection
363363
bool m_is_manual_connection;
364364

365+
//! Whether this peer relays txs via wtxid
366+
bool m_wtxid_relay{false};
367+
365368
CNodeState(CAddress addrIn, std::string addrNameIn, bool is_inbound, bool is_manual) :
366369
address(addrIn), name(std::move(addrNameIn)), m_is_inbound(is_inbound),
367370
m_is_manual_connection (is_manual)
@@ -1332,6 +1335,7 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
13321335
{
13331336
case MSG_TX:
13341337
case MSG_WITNESS_TX:
1338+
case MSG_WTX:
13351339
{
13361340
assert(recentRejects);
13371341
if (::ChainActive().Tip()->GetBlockHash() != hashRecentRejectsChainTip)
@@ -1346,16 +1350,20 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
13461350

13471351
{
13481352
LOCK(g_cs_orphans);
1349-
if (mapOrphanTransactions.count(inv.hash)) return true;
1353+
if (inv.type != MSG_WTX && mapOrphanTransactions.count(inv.hash)) {
1354+
return true;
1355+
} else if (inv.type == MSG_WTX && g_orphans_by_wtxid.count(inv.hash)) {
1356+
return true;
1357+
}
13501358
}
13511359

13521360
{
13531361
LOCK(g_cs_recent_confirmed_transactions);
13541362
if (g_recent_confirmed_transactions->contains(inv.hash)) return true;
13551363
}
13561364

1357-
return recentRejects->contains(inv.hash) ||
1358-
mempool.exists(inv.hash);
1365+
const bool by_wtxid = (inv.type == MSG_WTX);
1366+
return recentRejects->contains(inv.hash) || mempool.exists(inv.hash, by_wtxid);
13591367
}
13601368
case MSG_BLOCK:
13611369
case MSG_WITNESS_BLOCK:
@@ -1365,12 +1373,17 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
13651373
return true;
13661374
}
13671375

1368-
void RelayTransaction(const uint256& txid, const CConnman& connman)
1376+
void RelayTransaction(const uint256& txid, const uint256& wtxid, const CConnman& connman)
13691377
{
1370-
CInv inv(MSG_TX, txid);
1371-
connman.ForEachNode([&inv](CNode* pnode)
1378+
connman.ForEachNode([&txid, &wtxid](CNode* pnode)
13721379
{
1373-
pnode->PushInventory(inv);
1380+
AssertLockHeld(cs_main);
1381+
CNodeState &state = *State(pnode->GetId());
1382+
if (state.m_wtxid_relay) {
1383+
pnode->PushInventory({MSG_TX, wtxid}); // inv type is MSG_TX even for wtxid relay
1384+
} else {
1385+
pnode->PushInventory({MSG_TX, txid});
1386+
}
13741387
});
13751388
}
13761389

@@ -1585,7 +1598,7 @@ void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnm
15851598
// Process as many TX items from the front of the getdata queue as
15861599
// possible, since they're common and it's efficient to batch process
15871600
// them.
1588-
while (it != pfrom->vRecvGetData.end() && (it->type == MSG_TX || it->type == MSG_WITNESS_TX)) {
1601+
while (it != pfrom->vRecvGetData.end() && (it->type == MSG_TX || it->type == MSG_WITNESS_TX || it->type == MSG_WTX)) {
15891602
if (interruptMsgProc)
15901603
return;
15911604
// The send buffer provides backpressure. If there's no space in
@@ -1608,7 +1621,7 @@ void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnm
16081621
connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *mi->second));
16091622
push = true;
16101623
} else {
1611-
auto txinfo = mempool.info(inv.hash);
1624+
auto txinfo = mempool.info(inv.hash, inv.type == MSG_WTX);
16121625
// To protect privacy, do not answer getdata using the mempool when
16131626
// that TX couldn't have been INVed in reply to a MEMPOOL request,
16141627
// or when it's too recent to have expired from mapRelay.
@@ -1888,7 +1901,7 @@ void static ProcessOrphanTx(CConnman* connman, CTxMemPool& mempool, std::set<uin
18881901
if (setMisbehaving.count(fromPeer)) continue;
18891902
if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
18901903
LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString());
1891-
RelayTransaction(orphanHash, *connman);
1904+
RelayTransaction(orphanHash, porphanTx->GetWitnessHash(), *connman);
18921905
for (unsigned int i = 0; i < orphanTx.vout.size(); i++) {
18931906
auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(orphanHash, i));
18941907
if (it_by_prev != mapOrphanTransactionsByPrev.end()) {
@@ -2559,23 +2572,47 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
25592572
const CTransaction& tx = *ptx;
25602573

25612574
const uint256& txid = ptx->GetHash();
2562-
pfrom->AddInventoryKnown(txid);
2575+
const uint256& wtxid = ptx->GetWitnessHash();
25632576

25642577
LOCK2(cs_main, g_cs_orphans);
25652578

2579+
CNodeState* nodestate = State(pfrom->GetId());
2580+
2581+
const uint256& hash = nodestate->m_wtxid_relay ? wtxid : txid;
2582+
pfrom->AddInventoryKnown(hash);
2583+
if (nodestate->m_wtxid_relay && txid != wtxid) {
2584+
// Insert txid into filterInventoryKnown, even for
2585+
// wtxidrelay peers. This prevents re-adding of
2586+
// unconfirmed parents to the recently_announced
2587+
// filter, when a child tx is requested. See
2588+
// ProcessGetData().
2589+
pfrom->AddInventoryKnown(txid);
2590+
}
2591+
25662592
TxValidationState state;
25672593

2568-
CNodeState* nodestate = State(pfrom->GetId());
2569-
nodestate->m_tx_download.m_tx_announced.erase(txid);
2570-
nodestate->m_tx_download.m_tx_in_flight.erase(txid);
2571-
EraseTxRequest(txid);
2594+
nodestate->m_tx_download.m_tx_announced.erase(hash);
2595+
nodestate->m_tx_download.m_tx_in_flight.erase(hash);
2596+
EraseTxRequest(hash);
25722597

25732598
std::list<CTransactionRef> lRemovedTxn;
25742599

2575-
if (!AlreadyHave(CInv(MSG_TX, txid), mempool) &&
2600+
// We do the AlreadyHave() check using wtxid, rather than txid - in the
2601+
// absence of witness malleation, this is strictly better, because the
2602+
// recent rejects filter may contain the wtxid but will never contain
2603+
// the txid of a segwit transaction that has been rejected.
2604+
// In the presence of witness malleation, it's possible that by only
2605+
// doing the check with wtxid, we could overlook a transaction which
2606+
// was confirmed with a different witness, or exists in our mempool
2607+
// with a different witness, but this has limited downside:
2608+
// mempool validation does its own lookup of whether we have the txid
2609+
// already; and an adversary can already relay us old transactions
2610+
// (older than our recency filter) if trying to DoS us, without any need
2611+
// for witness malleation.
2612+
if (!AlreadyHave(CInv(MSG_WTX, wtxid), mempool) &&
25762613
AcceptToMemoryPool(mempool, state, ptx, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
25772614
mempool.check(&::ChainstateActive().CoinsTip());
2578-
RelayTransaction(tx.GetHash(), *connman);
2615+
RelayTransaction(tx.GetHash(), tx.GetWitnessHash(), *connman);
25792616
for (unsigned int i = 0; i < tx.vout.size(); i++) {
25802617
auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(txid, i));
25812618
if (it_by_prev != mapOrphanTransactionsByPrev.end()) {
@@ -2608,10 +2645,17 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
26082645
uint32_t nFetchFlags = GetFetchFlags(pfrom);
26092646
const auto current_time = GetTime<std::chrono::microseconds>();
26102647

2611-
for (const CTxIn& txin : tx.vin) {
2612-
CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash);
2613-
pfrom->AddInventoryKnown(txin.prevout.hash);
2614-
if (!AlreadyHave(_inv, mempool)) RequestTx(State(pfrom->GetId()), _inv.hash, current_time);
2648+
if (!State(pfrom->GetId())->m_wtxid_relay) {
2649+
for (const CTxIn& txin : tx.vin) {
2650+
// Here, we only have the txid (and not wtxid) of the
2651+
// inputs, so we only request parents from
2652+
// non-wtxid-relay peers.
2653+
// Eventually we should replace this with an improved
2654+
// protocol for getting all unconfirmed parents.
2655+
CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash);
2656+
pfrom->AddInventoryKnown(txin.prevout.hash);
2657+
if (!AlreadyHave(_inv, mempool)) RequestTx(State(pfrom->GetId()), _inv.hash, current_time);
2658+
}
26152659
}
26162660
AddOrphanTx(ptx, pfrom->GetId());
26172661

@@ -2672,7 +2716,7 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
26722716
LogPrintf("Not relaying non-mempool transaction %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->GetId());
26732717
} else {
26742718
LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->GetId());
2675-
RelayTransaction(tx.GetHash(), *connman);
2719+
RelayTransaction(tx.GetHash(), tx.GetWitnessHash(), *connman);
26762720
}
26772721
}
26782722
}
@@ -3288,7 +3332,7 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
32883332
vRecv >> vInv;
32893333
if (vInv.size() <= MAX_PEER_TX_IN_FLIGHT + MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
32903334
for (CInv &inv : vInv) {
3291-
if (inv.type == MSG_TX || inv.type == MSG_WITNESS_TX) {
3335+
if (inv.type == MSG_TX || inv.type == MSG_WITNESS_TX || inv.type == MSG_WTX) {
32923336
// If we receive a NOTFOUND message for a txid we requested, erase
32933337
// it from our data structures for this peer.
32943338
auto in_flight_it = state->m_tx_download.m_tx_in_flight.find(inv.hash);
@@ -3580,17 +3624,19 @@ namespace {
35803624
class CompareInvMempoolOrder
35813625
{
35823626
CTxMemPool *mp;
3627+
bool m_wtxid_relay;
35833628
public:
3584-
explicit CompareInvMempoolOrder(CTxMemPool *_mempool)
3629+
explicit CompareInvMempoolOrder(CTxMemPool *_mempool, bool use_wtxid)
35853630
{
35863631
mp = _mempool;
3632+
m_wtxid_relay = use_wtxid;
35873633
}
35883634

35893635
bool operator()(std::set<uint256>::iterator a, std::set<uint256>::iterator b)
35903636
{
35913637
/* As std::make_heap produces a max-heap, we want the entries with the
35923638
* fewest ancestors/highest fee to sort later. */
3593-
return mp->CompareDepthAndScore(*b, *a);
3639+
return mp->CompareDepthAndScore(*b, *a, m_wtxid_relay);
35943640
}
35953641
};
35963642
}
@@ -3897,8 +3943,8 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
38973943
LOCK(pto->m_tx_relay->cs_filter);
38983944

38993945
for (const auto& txinfo : vtxinfo) {
3900-
const uint256& hash = txinfo.tx->GetHash();
3901-
CInv inv(MSG_TX, hash);
3946+
const uint256& hash = state.m_wtxid_relay ? txinfo.tx->GetWitnessHash() : txinfo.tx->GetHash();
3947+
CInv inv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash);
39023948
pto->m_tx_relay->setInventoryTxToSend.erase(hash);
39033949
// Don't send transactions that peers will not put into their mempool
39043950
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
@@ -3932,7 +3978,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
39323978
}
39333979
// Topologically and fee-rate sort the inventory we send for privacy and priority reasons.
39343980
// A heap is used so that not all items need sorting if only a few are being sent.
3935-
CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool);
3981+
CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool, state.m_wtxid_relay);
39363982
std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
39373983
// No reason to drain out at many times the network's capacity,
39383984
// especially since we have many peers and some will draw much shorter delays.
@@ -3951,17 +3997,19 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
39513997
continue;
39523998
}
39533999
// Not in the mempool anymore? don't bother sending it.
3954-
auto txinfo = m_mempool.info(hash);
4000+
auto txinfo = m_mempool.info(hash, state.m_wtxid_relay);
39554001
if (!txinfo.tx) {
39564002
continue;
39574003
}
4004+
auto txid = txinfo.tx->GetHash();
4005+
auto wtxid = txinfo.tx->GetWitnessHash();
39584006
// Peer told you to not send transactions at that feerate? Don't bother sending it.
39594007
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
39604008
continue;
39614009
}
39624010
if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
39634011
// Send
3964-
vInv.push_back(CInv(MSG_TX, hash));
4012+
vInv.push_back(CInv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash));
39654013
nRelayedTransactions++;
39664014
{
39674015
// Expire old relay messages
@@ -3971,12 +4019,12 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
39714019
vRelayExpiration.pop_front();
39724020
}
39734021

3974-
auto ret = mapRelay.insert(std::make_pair(hash, std::move(txinfo.tx)));
4022+
auto ret = mapRelay.emplace(txid, std::move(txinfo.tx));
39754023
if (ret.second) {
3976-
vRelayExpiration.push_back(std::make_pair(nNow + std::chrono::microseconds{RELAY_TX_CACHE_TIME}.count(), ret.first));
4024+
vRelayExpiration.emplace_back(nNow + std::chrono::microseconds{RELAY_TX_CACHE_TIME}.count(), ret.first);
39774025
}
39784026
// Add wtxid-based lookup into mapRelay as well, so that peers can request by wtxid
3979-
auto ret2 = mapRelay.emplace(ret.first->second->GetWitnessHash(), ret.first->second);
4027+
auto ret2 = mapRelay.emplace(wtxid, ret.first->second);
39804028
if (ret2.second) {
39814029
vRelayExpiration.emplace_back(nNow + std::chrono::microseconds{RELAY_TX_CACHE_TIME}.count(), ret2.first);
39824030
}
@@ -3986,6 +4034,14 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
39864034
vInv.clear();
39874035
}
39884036
pto->m_tx_relay->filterInventoryKnown.insert(hash);
4037+
if (hash != txid) {
4038+
// Insert txid into filterInventoryKnown, even for
4039+
// wtxidrelay peers. This prevents re-adding of
4040+
// unconfirmed parents to the recently_announced
4041+
// filter, when a child tx is requested. See
4042+
// ProcessGetData().
4043+
pto->m_tx_relay->filterInventoryKnown.insert(txid);
4044+
}
39894045
}
39904046
}
39914047
}
@@ -4110,7 +4166,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
41104166
// Erase this entry from tx_process_time (it may be added back for
41114167
// processing at a later time, see below)
41124168
tx_process_time.erase(tx_process_time.begin());
4113-
CInv inv(MSG_TX | GetFetchFlags(pto), txid);
4169+
CInv inv(state.m_wtxid_relay ? MSG_WTX : (MSG_TX | GetFetchFlags(pto)), txid);
41144170
if (!AlreadyHave(inv, m_mempool)) {
41154171
// If this transaction was last requested more than 1 minute ago,
41164172
// then request.

src/net_processing.h

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

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

9797
#endif // BITCOIN_NET_PROCESSING_H

src/node/transaction.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
7878
}
7979

8080
if (relay) {
81-
RelayTransaction(hashTx, *node.connman);
81+
LOCK(cs_main);
82+
RelayTransaction(hashTx, tx->GetWitnessHash(), *node.connman);
8283
}
8384

8485
return TransactionError::OK;

src/protocol.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ std::string CInv::GetCommand() const
183183
switch (masked)
184184
{
185185
case MSG_TX: return cmd.append(NetMsgType::TX);
186+
// WTX is not a message type, just an inv type
187+
case MSG_WTX: return cmd.append("wtx");
186188
case MSG_BLOCK: return cmd.append(NetMsgType::BLOCK);
187189
case MSG_FILTERED_BLOCK: return cmd.append(NetMsgType::MERKLEBLOCK);
188190
case MSG_CMPCT_BLOCK: return cmd.append(NetMsgType::CMPCTBLOCK);

src/protocol.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,11 @@ const uint32_t MSG_TYPE_MASK = 0xffffffff >> 2;
362362
* These numbers are defined by the protocol. When adding a new value, be sure
363363
* to mention it in the respective BIP.
364364
*/
365-
enum GetDataMsg
366-
{
365+
enum GetDataMsg : uint32_t {
367366
UNDEFINED = 0,
368367
MSG_TX = 1,
369368
MSG_BLOCK = 2,
369+
MSG_WTX = 5, //!< Defined in BIP 339
370370
// The following can only occur in getdata. Invs always use TX or BLOCK.
371371
MSG_FILTERED_BLOCK = 3, //!< Defined in BIP37
372372
MSG_CMPCT_BLOCK = 4, //!< Defined in BIP152

test/functional/p2p_segwit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1296,7 +1296,7 @@ def test_tx_relay_after_segwit_activation(self):
12961296
self.std_node.announce_tx_and_wait_for_getdata(tx3)
12971297
test_transaction_acceptance(self.nodes[1], self.std_node, tx3, True, False, 'tx-size')
12981298
self.std_node.announce_tx_and_wait_for_getdata(tx3)
1299-
test_transaction_acceptance(self.nodes[1], self.std_node, tx3, True, False, 'tx-size')
1299+
test_transaction_acceptance(self.nodes[1], self.std_node, tx3, True, False)
13001300

13011301
# Remove witness stuffing, instead add extra witness push on stack
13021302
tx3.vout[0] = CTxOut(tx2.vout[0].nValue - 1000, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))

0 commit comments

Comments
 (0)