@@ -410,6 +410,9 @@ struct CNodeState {
410
410
// ! A rolling bloom filter of all announced tx CInvs to this peer.
411
411
CRollingBloomFilter m_recently_announced_invs = CRollingBloomFilter{INVENTORY_MAX_RECENT_RELAY, 0.000001 };
412
412
413
+ // ! Whether this peer relays txs via wtxid
414
+ bool m_wtxid_relay{false };
415
+
413
416
CNodeState (CAddress addrIn, std::string addrNameIn, bool is_inbound, bool is_manual) :
414
417
address (addrIn), name(std::move(addrNameIn)), m_is_inbound(is_inbound),
415
418
m_is_manual_connection (is_manual)
@@ -836,7 +839,8 @@ void PeerLogicValidation::ReattemptInitialBroadcast(CScheduler& scheduler) const
836
839
for (const auto & elem : unbroadcast_txids) {
837
840
// Sanity check: all unbroadcast txns should exist in the mempool
838
841
if (m_mempool.exists (elem.first )) {
839
- RelayTransaction (elem.first , *connman);
842
+ LOCK (cs_main);
843
+ RelayTransaction (elem.first , elem.second , *connman);
840
844
} else {
841
845
m_mempool.RemoveUnbroadcastTx (elem.first , true );
842
846
}
@@ -1405,6 +1409,7 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
1405
1409
{
1406
1410
case MSG_TX:
1407
1411
case MSG_WITNESS_TX:
1412
+ case MSG_WTX:
1408
1413
{
1409
1414
assert (recentRejects);
1410
1415
if (::ChainActive ().Tip ()->GetBlockHash () != hashRecentRejectsChainTip)
@@ -1419,16 +1424,20 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
1419
1424
1420
1425
{
1421
1426
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
+ }
1423
1432
}
1424
1433
1425
1434
{
1426
1435
LOCK (g_cs_recent_confirmed_transactions);
1427
1436
if (g_recent_confirmed_transactions->contains (inv.hash )) return true ;
1428
1437
}
1429
1438
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 );
1432
1441
}
1433
1442
case MSG_BLOCK:
1434
1443
case MSG_WITNESS_BLOCK:
@@ -1438,11 +1447,17 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
1438
1447
return true ;
1439
1448
}
1440
1449
1441
- void RelayTransaction (const uint256& txid, const CConnman& connman)
1450
+ void RelayTransaction (const uint256& txid, const uint256& wtxid, const CConnman& connman)
1442
1451
{
1443
- connman.ForEachNode ([&txid](CNode* pnode)
1452
+ connman.ForEachNode ([&txid, &wtxid ](CNode* pnode)
1444
1453
{
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
+ }
1446
1461
});
1447
1462
}
1448
1463
@@ -1640,9 +1655,9 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
1640
1655
}
1641
1656
1642
1657
// ! 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)
1644
1659
{
1645
- auto txinfo = mempool.info (txid );
1660
+ auto txinfo = mempool.info (txid_or_wtxid, use_wtxid );
1646
1661
if (txinfo.tx ) {
1647
1662
// If a TX could have been INVed in reply to a MEMPOOL request,
1648
1663
// or is older than UNCONDITIONAL_RELAY_DELAY, permit the request
@@ -1654,13 +1669,12 @@ CTransactionRef static FindTxForGetData(const CNode& peer, const uint256& txid,
1654
1669
1655
1670
{
1656
1671
LOCK (cs_main);
1657
-
1658
1672
// 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 )) {
1660
1674
// If it was, it can be relayed from either the mempool...
1661
1675
if (txinfo.tx ) return std::move (txinfo.tx );
1662
1676
// ... or the relay pool.
1663
- auto mi = mapRelay.find (txid );
1677
+ auto mi = mapRelay.find (txid_or_wtxid );
1664
1678
if (mi != mapRelay.end ()) return mi->second ;
1665
1679
}
1666
1680
}
@@ -1684,7 +1698,7 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm
1684
1698
// Process as many TX items from the front of the getdata queue as
1685
1699
// possible, since they're common and it's efficient to batch process
1686
1700
// 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 )) {
1688
1702
if (interruptMsgProc) return ;
1689
1703
// The send buffer provides backpressure. If there's no space in
1690
1704
// the buffer, pause processing until the next call.
@@ -1697,11 +1711,12 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm
1697
1711
continue ;
1698
1712
}
1699
1713
1700
- CTransactionRef tx = FindTxForGetData (pfrom, inv.hash , mempool_req, now);
1714
+ CTransactionRef tx = FindTxForGetData (pfrom, inv.hash , inv. type == MSG_WTX, mempool_req, now);
1701
1715
if (tx) {
1716
+ // WTX and WITNESS_TX imply we serialize with witness
1702
1717
int nSendFlags = (inv.type == MSG_TX ? SERIALIZE_TRANSACTION_NO_WITNESS : 0 );
1703
1718
connman.PushMessage (&pfrom, msgMaker.Make (nSendFlags, NetMsgType::TX, *tx));
1704
- mempool.RemoveUnbroadcastTx (inv. hash );
1719
+ mempool.RemoveUnbroadcastTx (tx-> GetHash () );
1705
1720
// As we're going to send tx, make sure its unconfirmed parents are made requestable.
1706
1721
for (const auto & txin : tx->vin ) {
1707
1722
auto txinfo = mempool.info (txin.prevout .hash );
@@ -1980,7 +1995,7 @@ void static ProcessOrphanTx(CConnman& connman, CTxMemPool& mempool, std::set<uin
1980
1995
if (setMisbehaving.count (fromPeer)) continue ;
1981
1996
if (AcceptToMemoryPool (mempool, orphan_state, porphanTx, &removed_txn, false /* bypass_limits */ , 0 /* nAbsurdFee */ )) {
1982
1997
LogPrint (BCLog::MEMPOOL, " accepted orphan tx %s\n " , orphanHash.ToString ());
1983
- RelayTransaction (orphanHash, connman);
1998
+ RelayTransaction (orphanHash, porphanTx-> GetWitnessHash (), connman);
1984
1999
for (unsigned int i = 0 ; i < orphanTx.vout .size (); i++) {
1985
2000
auto it_by_prev = mapOrphanTransactionsByPrev.find (COutPoint (orphanHash, i));
1986
2001
if (it_by_prev != mapOrphanTransactionsByPrev.end ()) {
@@ -2841,23 +2856,47 @@ void ProcessMessage(
2841
2856
const CTransaction& tx = *ptx;
2842
2857
2843
2858
const uint256& txid = ptx->GetHash ();
2844
- pfrom. AddInventoryKnown (txid );
2859
+ const uint256& wtxid = ptx-> GetWitnessHash ( );
2845
2860
2846
2861
LOCK2 (cs_main, g_cs_orphans);
2847
2862
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
+
2848
2876
TxValidationState state;
2849
2877
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);
2854
2881
2855
2882
std::list<CTransactionRef> lRemovedTxn;
2856
2883
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) &&
2858
2897
AcceptToMemoryPool (mempool, state, ptx, &lRemovedTxn, false /* bypass_limits */ , 0 /* nAbsurdFee */ )) {
2859
2898
mempool.check (&::ChainstateActive ().CoinsTip ());
2860
- RelayTransaction (tx.GetHash (), connman);
2899
+ RelayTransaction (tx.GetHash (), tx. GetWitnessHash (), connman);
2861
2900
for (unsigned int i = 0 ; i < tx.vout .size (); i++) {
2862
2901
auto it_by_prev = mapOrphanTransactionsByPrev.find (COutPoint (txid, i));
2863
2902
if (it_by_prev != mapOrphanTransactionsByPrev.end ()) {
@@ -2890,10 +2929,17 @@ void ProcessMessage(
2890
2929
uint32_t nFetchFlags = GetFetchFlags (pfrom);
2891
2930
const auto current_time = GetTime<std::chrono::microseconds>();
2892
2931
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
+ }
2897
2943
}
2898
2944
AddOrphanTx (ptx, pfrom.GetId ());
2899
2945
@@ -2933,7 +2979,7 @@ void ProcessMessage(
2933
2979
LogPrintf (" Not relaying non-mempool transaction %s from forcerelay peer=%d\n " , tx.GetHash ().ToString (), pfrom.GetId ());
2934
2980
} else {
2935
2981
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);
2937
2983
}
2938
2984
}
2939
2985
}
@@ -3573,7 +3619,7 @@ void ProcessMessage(
3573
3619
vRecv >> vInv;
3574
3620
if (vInv.size () <= MAX_PEER_TX_IN_FLIGHT + MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
3575
3621
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 ) {
3577
3623
// If we receive a NOTFOUND message for a txid we requested, erase
3578
3624
// it from our data structures for this peer.
3579
3625
auto in_flight_it = state->m_tx_download .m_tx_in_flight .find (inv.hash );
@@ -3858,17 +3904,19 @@ namespace {
3858
3904
class CompareInvMempoolOrder
3859
3905
{
3860
3906
CTxMemPool *mp;
3907
+ bool m_wtxid_relay;
3861
3908
public:
3862
- explicit CompareInvMempoolOrder (CTxMemPool *_mempool)
3909
+ explicit CompareInvMempoolOrder (CTxMemPool *_mempool, bool use_wtxid )
3863
3910
{
3864
3911
mp = _mempool;
3912
+ m_wtxid_relay = use_wtxid;
3865
3913
}
3866
3914
3867
3915
bool operator ()(std::set<uint256>::iterator a, std::set<uint256>::iterator b)
3868
3916
{
3869
3917
/* As std::make_heap produces a max-heap, we want the entries with the
3870
3918
* fewest ancestors/highest fee to sort later. */
3871
- return mp->CompareDepthAndScore (*b, *a);
3919
+ return mp->CompareDepthAndScore (*b, *a, m_wtxid_relay );
3872
3920
}
3873
3921
};
3874
3922
}
@@ -4175,8 +4223,8 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
4175
4223
LOCK (pto->m_tx_relay ->cs_filter );
4176
4224
4177
4225
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);
4180
4228
pto->m_tx_relay ->setInventoryTxToSend .erase (hash);
4181
4229
// Don't send transactions that peers will not put into their mempool
4182
4230
if (txinfo.fee < filterrate.GetFee (txinfo.vsize )) {
@@ -4211,7 +4259,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
4211
4259
}
4212
4260
// Topologically and fee-rate sort the inventory we send for privacy and priority reasons.
4213
4261
// 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 );
4215
4263
std::make_heap (vInvTx.begin (), vInvTx.end (), compareInvMempoolOrder);
4216
4264
// No reason to drain out at many times the network's capacity,
4217
4265
// especially since we have many peers and some will draw much shorter delays.
@@ -4230,18 +4278,20 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
4230
4278
continue ;
4231
4279
}
4232
4280
// 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 );
4234
4282
if (!txinfo.tx ) {
4235
4283
continue ;
4236
4284
}
4285
+ auto txid = txinfo.tx ->GetHash ();
4286
+ auto wtxid = txinfo.tx ->GetWitnessHash ();
4237
4287
// Peer told you to not send transactions at that feerate? Don't bother sending it.
4238
4288
if (txinfo.fee < filterrate.GetFee (txinfo.vsize )) {
4239
4289
continue ;
4240
4290
}
4241
4291
if (pto->m_tx_relay ->pfilter && !pto->m_tx_relay ->pfilter ->IsRelevantAndUpdate (*txinfo.tx )) continue ;
4242
4292
// Send
4243
4293
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));
4245
4295
nRelayedTransactions++;
4246
4296
{
4247
4297
// Expire old relay messages
@@ -4251,12 +4301,12 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
4251
4301
vRelayExpiration.pop_front ();
4252
4302
}
4253
4303
4254
- auto ret = mapRelay.insert ( std::make_pair (hash , std::move (txinfo.tx ) ));
4304
+ auto ret = mapRelay.emplace (txid , std::move (txinfo.tx ));
4255
4305
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 );
4257
4307
}
4258
4308
// 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 );
4260
4310
if (ret2.second ) {
4261
4311
vRelayExpiration.emplace_back (nNow + std::chrono::microseconds{RELAY_TX_CACHE_TIME}.count (), ret2.first );
4262
4312
}
@@ -4266,6 +4316,14 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
4266
4316
vInv.clear ();
4267
4317
}
4268
4318
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
+ }
4269
4327
}
4270
4328
}
4271
4329
}
@@ -4390,7 +4448,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
4390
4448
// Erase this entry from tx_process_time (it may be added back for
4391
4449
// processing at a later time, see below)
4392
4450
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);
4394
4452
if (!AlreadyHave (inv, m_mempool)) {
4395
4453
// If this transaction was last requested more than 1 minute ago,
4396
4454
// then request.
0 commit comments