Skip to content

Commit ccef102

Browse files
committed
Merge #18044: Use wtxid for transaction relay
0a4f142 Further improve comments around recentRejects (Suhas Daftuar) 0e20cfe Disconnect peers sending wtxidrelay message after VERACK (Suhas Daftuar) cacd852 test: Use wtxid relay generally in functional tests (Fabian Jahr) 8d8099e test: Add tests for wtxid tx relay in segwit test (Fabian Jahr) 9a5392f test: Update test framework p2p protocol version to 70016 (Fabian Jahr) dd78d1d Rename AddInventoryKnown() to AddKnownTx() (Suhas Daftuar) 4eb5155 Make TX_WITNESS_STRIPPED its own rejection reason (Suhas Daftuar) 97141ca Delay getdata requests from peers using txid-based relay (Suhas Daftuar) 46d78d4 Add p2p message "wtxidrelay" (Suhas Daftuar) 2d282e0 ignore non-wtxidrelay compliant invs (Anthony Towns) ac88e2e Add support for tx-relay via wtxid (Suhas Daftuar) 8e68fc2 Add wtxids to recentRejects instead of txids (Suhas Daftuar) 144c385 Add wtxids of confirmed transactions to bloom filter (Suhas Daftuar) 85c78d5 Add wtxid-index to orphan map (Suhas Daftuar) 08b3995 Add a wtxid-index to mapRelay (Suhas Daftuar) 60f0acd Just pass a hash to AddInventoryKnown (Suhas Daftuar) c7eb6b4 Add wtxid to mempool unbroadcast tracking (Amiti Uttarwar) 2b4b90a Add a wtxid-index to the mempool (Suhas Daftuar) Pull request description: Using txids (a transaction's hash, without witness) for transaction relay is problematic, post-segwit -- if a peer gives us a segwit transaction that fails policy checks, it could be because the txid associated with the transaction is definitely unacceptable to our node (regardless of the witness), or it could be that the transaction was malleated and with a different witness, the txid could be accepted to our mempool. We have a bloom filter of recently rejected transactions, whose purpose is to help us avoid redownloading and revalidating transactions that fail to be accepted, but because of this potential for witness malleability to interfere with relay of valid transactions, we do not use the filter for segwit transactions. This issue is discussed at some length in #8279. The effect of this is that whenever a segwit transaction that fails policy checks is relayed, a node would download that transaction from every peer announcing it, because it has no way presently to cache failure. Historically this hasn't been a big problem, but if/when policy for accepting segwit transactions were to change (eg taproot, or any other change), we could expect older nodes talking to newer nodes to be wasting bandwidth because of this. As discussed in that issue, switching to wtxid-based relay solves this problem -- by using an identifier for a transaction that commits to all the data in our relay protocol, we can be certain if a transaction that a peer is announcing is one that we've already tried to process, or if it's something new. This PR introduces support for wtxid-based relay with peers that support it (and remains backwards compatible with peers that use txids for relay, of course). Apart from code correctness, one issue to be aware of is that by downloading from old and new peers alike, we should expect there to be some bandwidth wasted, because sometimes we might download the same transaction via txid-relay as well as wtxid-relay. The last commit in this PR implements a heuristic I want to analyze, which is to just delay relay from txid-relay peers by 2 seconds, if we have at least 1 wtxid-based peer. I've just started running a couple nodes with this heuristic so I can measure how well it works, but I'm open to other ideas for minimizing that issue. In the long run, I think this will be essentially a non-issue, so I don't think it's too big a concern, we just need to bite the bullet and deal with it during upgrade. Finally, this proposal would need a simple BIP describing the changes, which I haven't yet drafted. However, review and testing of this code in the interim would be welcome. To do items: - [x] Write BIP explaining the spec here (1 new p2p message for negotiating wtxid-based relay, along with a new INV type) - [ ] Measure and evaluate a heuristic for minimizing how often a node downloads the same transaction twice, when connected to old and new nodes. ACKs for top commit: naumenkogs: utACK 0a4f142 laanwj: utACK 0a4f142 Tree-SHA512: d8eb8f0688cf0cbe9507bf738e143edab1f595551fdfeddc2b6734686ea26e7f156b6bfde38bad8bbbe8bec1857c7223e1687f8f018de7463dde8ecaa8f450df
2 parents 1397afc + 0a4f142 commit ccef102

18 files changed

+450
-114
lines changed

src/consensus/validation.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,15 @@ enum class TxValidationResult {
3030
TX_MISSING_INPUTS, //!< transaction was missing some of its inputs
3131
TX_PREMATURE_SPEND, //!< transaction spends a coinbase too early, or violates locktime/sequence locks
3232
/**
33-
* Transaction might be missing a witness, have a witness prior to SegWit
33+
* Transaction might have a witness prior to SegWit
3434
* activation, or witness may have been malleated (which includes
3535
* non-standard witnesses).
3636
*/
3737
TX_WITNESS_MUTATED,
38+
/**
39+
* Transaction is missing a witness.
40+
*/
41+
TX_WITNESS_STRIPPED,
3842
/**
3943
* Tx already in mempool or conflicts with a tx in the chain
4044
* (if it conflicts with another tx in mempool, we use MEMPOOL_POLICY as it failed to reach the RBF threshold)

src/net.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -965,11 +965,11 @@ class CNode
965965
}
966966

967967

968-
void AddInventoryKnown(const CInv& inv)
968+
void AddKnownTx(const uint256& hash)
969969
{
970970
if (m_tx_relay != nullptr) {
971971
LOCK(m_tx_relay->cs_tx_inventory);
972-
m_tx_relay->filterInventoryKnown.insert(inv.hash);
972+
m_tx_relay->filterInventoryKnown.insert(hash);
973973
}
974974
}
975975

src/net_processing.cpp

Lines changed: 217 additions & 61 deletions
Large diffs are not rendered by default.

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: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,10 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
8080
if (relay) {
8181
// the mempool tracks locally submitted transactions to make a
8282
// best-effort of initial broadcast
83-
node.mempool->AddUnbroadcastTx(hashTx);
83+
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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const char *GETCFHEADERS="getcfheaders";
4646
const char *CFHEADERS="cfheaders";
4747
const char *GETCFCHECKPT="getcfcheckpt";
4848
const char *CFCHECKPT="cfcheckpt";
49+
const char *WTXIDRELAY="wtxidrelay";
4950
} // namespace NetMsgType
5051

5152
/** All known message types. Keep this in the same order as the list of
@@ -83,6 +84,7 @@ const static std::string allNetMessageTypes[] = {
8384
NetMsgType::CFHEADERS,
8485
NetMsgType::GETCFCHECKPT,
8586
NetMsgType::CFCHECKPT,
87+
NetMsgType::WTXIDRELAY,
8688
};
8789
const static std::vector<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes));
8890

@@ -177,6 +179,8 @@ std::string CInv::GetCommand() const
177179
switch (masked)
178180
{
179181
case MSG_TX: return cmd.append(NetMsgType::TX);
182+
// WTX is not a message type, just an inv type
183+
case MSG_WTX: return cmd.append("wtx");
180184
case MSG_BLOCK: return cmd.append(NetMsgType::BLOCK);
181185
case MSG_FILTERED_BLOCK: return cmd.append(NetMsgType::MERKLEBLOCK);
182186
case MSG_CMPCT_BLOCK: return cmd.append(NetMsgType::CMPCTBLOCK);

src/protocol.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,12 @@ extern const char* GETCFCHECKPT;
261261
* evenly spaced filter headers for blocks on the requested chain.
262262
*/
263263
extern const char* CFCHECKPT;
264+
/**
265+
* Indicates that a node prefers to relay transactions via wtxid, rather than
266+
* txid.
267+
* @since protocol version 70016 as described by BIP 339.
268+
*/
269+
extern const char *WTXIDRELAY;
264270
}; // namespace NetMsgType
265271

266272
/* Get a vector of all valid message types (see above) */
@@ -397,11 +403,12 @@ const uint32_t MSG_TYPE_MASK = 0xffffffff >> 2;
397403
* These numbers are defined by the protocol. When adding a new value, be sure
398404
* to mention it in the respective BIP.
399405
*/
400-
enum GetDataMsg {
406+
enum GetDataMsg : uint32_t {
401407
UNDEFINED = 0,
402408
MSG_TX = 1,
403409
MSG_BLOCK = 2,
404-
// The following can only occur in getdata. Invs always use TX or BLOCK.
410+
MSG_WTX = 5, //!< Defined in BIP 339
411+
// The following can only occur in getdata. Invs always use TX/WTX or BLOCK.
405412
MSG_FILTERED_BLOCK = 3, //!< Defined in BIP37
406413
MSG_CMPCT_BLOCK = 4, //!< Defined in BIP152
407414
MSG_WITNESS_BLOCK = MSG_BLOCK | MSG_WITNESS_FLAG, //!< Defined in BIP144

src/txmempool.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -726,12 +726,12 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
726726
assert(innerUsage == cachedInnerUsage);
727727
}
728728

729-
bool CTxMemPool::CompareDepthAndScore(const uint256& hasha, const uint256& hashb)
729+
bool CTxMemPool::CompareDepthAndScore(const uint256& hasha, const uint256& hashb, bool wtxid)
730730
{
731731
LOCK(cs);
732-
indexed_transaction_set::const_iterator i = mapTx.find(hasha);
732+
indexed_transaction_set::const_iterator i = wtxid ? get_iter_from_wtxid(hasha) : mapTx.find(hasha);
733733
if (i == mapTx.end()) return false;
734-
indexed_transaction_set::const_iterator j = mapTx.find(hashb);
734+
indexed_transaction_set::const_iterator j = wtxid ? get_iter_from_wtxid(hashb) : mapTx.find(hashb);
735735
if (j == mapTx.end()) return true;
736736
uint64_t counta = i->GetCountWithAncestors();
737737
uint64_t countb = j->GetCountWithAncestors();
@@ -811,10 +811,10 @@ CTransactionRef CTxMemPool::get(const uint256& hash) const
811811
return i->GetSharedTx();
812812
}
813813

814-
TxMempoolInfo CTxMemPool::info(const uint256& hash) const
814+
TxMempoolInfo CTxMemPool::info(const uint256& hash, bool wtxid) const
815815
{
816816
LOCK(cs);
817-
indexed_transaction_set::const_iterator i = mapTx.find(hash);
817+
indexed_transaction_set::const_iterator i = (wtxid ? get_iter_from_wtxid(hash) : mapTx.find(hash));
818818
if (i == mapTx.end())
819819
return TxMempoolInfo();
820820
return GetInfo(i);
@@ -917,8 +917,8 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const {
917917

918918
size_t CTxMemPool::DynamicMemoryUsage() const {
919919
LOCK(cs);
920-
// Estimate the overhead of mapTx to be 12 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented.
921-
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 12 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage;
920+
// Estimate the overhead of mapTx to be 15 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented.
921+
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 15 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage;
922922
}
923923

924924
void CTxMemPool::RemoveUnbroadcastTx(const uint256& txid, const bool unchecked) {

src/txmempool.h

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,22 @@ struct mempoolentry_txid
198198
}
199199
};
200200

201+
// extracts a transaction witness-hash from CTxMemPoolEntry or CTransactionRef
202+
struct mempoolentry_wtxid
203+
{
204+
typedef uint256 result_type;
205+
result_type operator() (const CTxMemPoolEntry &entry) const
206+
{
207+
return entry.GetTx().GetWitnessHash();
208+
}
209+
210+
result_type operator() (const CTransactionRef& tx) const
211+
{
212+
return tx->GetWitnessHash();
213+
}
214+
};
215+
216+
201217
/** \class CompareTxMemPoolEntryByDescendantScore
202218
*
203219
* Sort an entry by max(score/size of entry's tx, score/size with all descendants).
@@ -318,6 +334,7 @@ class CompareTxMemPoolEntryByAncestorFee
318334
struct descendant_score {};
319335
struct entry_time {};
320336
struct ancestor_score {};
337+
struct index_by_wtxid {};
321338

322339
class CBlockPolicyEstimator;
323340

@@ -383,8 +400,9 @@ class SaltedTxidHasher
383400
*
384401
* CTxMemPool::mapTx, and CTxMemPoolEntry bookkeeping:
385402
*
386-
* mapTx is a boost::multi_index that sorts the mempool on 4 criteria:
387-
* - transaction hash
403+
* mapTx is a boost::multi_index that sorts the mempool on 5 criteria:
404+
* - transaction hash (txid)
405+
* - witness-transaction hash (wtxid)
388406
* - descendant feerate [we use max(feerate of tx, feerate of tx with all descendants)]
389407
* - time in mempool
390408
* - ancestor feerate [we use min(feerate of tx, feerate of tx with all unconfirmed ancestors)]
@@ -469,6 +487,12 @@ class CTxMemPool
469487
boost::multi_index::indexed_by<
470488
// sorted by txid
471489
boost::multi_index::hashed_unique<mempoolentry_txid, SaltedTxidHasher>,
490+
// sorted by wtxid
491+
boost::multi_index::hashed_unique<
492+
boost::multi_index::tag<index_by_wtxid>,
493+
mempoolentry_wtxid,
494+
SaltedTxidHasher
495+
>,
472496
// sorted by fee rate
473497
boost::multi_index::ordered_non_unique<
474498
boost::multi_index::tag<descendant_score>,
@@ -549,8 +573,11 @@ class CTxMemPool
549573

550574
std::vector<indexed_transaction_set::const_iterator> GetSortedDepthAndScore() const EXCLUSIVE_LOCKS_REQUIRED(cs);
551575

552-
/** track locally submitted transactions to periodically retry initial broadcast */
553-
std::set<uint256> m_unbroadcast_txids GUARDED_BY(cs);
576+
/**
577+
* track locally submitted transactions to periodically retry initial broadcast
578+
* map of txid -> wtxid
579+
*/
580+
std::map<uint256, uint256> m_unbroadcast_txids GUARDED_BY(cs);
554581

555582
public:
556583
indirectmap<COutPoint, const CTransaction*> mapNextTx GUARDED_BY(cs);
@@ -586,7 +613,7 @@ class CTxMemPool
586613

587614
void clear();
588615
void _clear() EXCLUSIVE_LOCKS_REQUIRED(cs); //lock free
589-
bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb);
616+
bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb, bool wtxid=false);
590617
void queryHashes(std::vector<uint256>& vtxid) const;
591618
bool isSpent(const COutPoint& outpoint) const;
592619
unsigned int GetTransactionsUpdated() const;
@@ -689,32 +716,40 @@ class CTxMemPool
689716
return totalTxSize;
690717
}
691718

692-
bool exists(const uint256& hash) const
719+
bool exists(const uint256& hash, bool wtxid=false) const
693720
{
694721
LOCK(cs);
722+
if (wtxid) {
723+
return (mapTx.get<index_by_wtxid>().count(hash) != 0);
724+
}
695725
return (mapTx.count(hash) != 0);
696726
}
697727

698728
CTransactionRef get(const uint256& hash) const;
699-
TxMempoolInfo info(const uint256& hash) const;
729+
txiter get_iter_from_wtxid(const uint256& wtxid) const EXCLUSIVE_LOCKS_REQUIRED(cs)
730+
{
731+
AssertLockHeld(cs);
732+
return mapTx.project<0>(mapTx.get<index_by_wtxid>().find(wtxid));
733+
}
734+
TxMempoolInfo info(const uint256& hash, bool wtxid=false) const;
700735
std::vector<TxMempoolInfo> infoAll() const;
701736

702737
size_t DynamicMemoryUsage() const;
703738

704739
/** Adds a transaction to the unbroadcast set */
705-
void AddUnbroadcastTx(const uint256& txid) {
740+
void AddUnbroadcastTx(const uint256& txid, const uint256& wtxid) {
706741
LOCK(cs);
707742
// Sanity Check: the transaction should also be in the mempool
708743
if (exists(txid)) {
709-
m_unbroadcast_txids.insert(txid);
744+
m_unbroadcast_txids[txid] = wtxid;
710745
}
711746
}
712747

713748
/** Removes a transaction from the unbroadcast set */
714749
void RemoveUnbroadcastTx(const uint256& txid, const bool unchecked = false);
715750

716751
/** Returns transactions in unbroadcast set */
717-
std::set<uint256> GetUnbroadcastTxs() const {
752+
std::map<uint256, uint256> GetUnbroadcastTxs() const {
718753
LOCK(cs);
719754
return m_unbroadcast_txids;
720755
}

src/validation.cpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,7 @@ bool MemPoolAccept::PolicyScriptChecks(ATMPArgs& args, Workspace& ws, Precompute
939939
if (!tx.HasWitness() && CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) &&
940940
!CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) {
941941
// Only the witness is missing, so the transaction itself may be fine.
942-
state.Invalid(TxValidationResult::TX_WITNESS_MUTATED,
942+
state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED,
943943
state.GetRejectReason(), state.GetDebugMessage());
944944
}
945945
return false; // state filled in by CheckInputScripts
@@ -5084,19 +5084,22 @@ bool LoadMempool(CTxMemPool& pool)
50845084
}
50855085

50865086
// TODO: remove this try except in v0.22
5087+
std::map<uint256, uint256> unbroadcast_txids;
50875088
try {
5088-
std::set<uint256> unbroadcast_txids;
50895089
file >> unbroadcast_txids;
50905090
unbroadcast = unbroadcast_txids.size();
5091-
5092-
for (const auto& txid : unbroadcast_txids) {
5093-
pool.AddUnbroadcastTx(txid);
5094-
}
50955091
} catch (const std::exception&) {
50965092
// mempool.dat files created prior to v0.21 will not have an
50975093
// unbroadcast set. No need to log a failure if parsing fails here.
50985094
}
5099-
5095+
for (const auto& elem : unbroadcast_txids) {
5096+
// Don't add unbroadcast transactions that didn't get back into the
5097+
// mempool.
5098+
const CTransactionRef& added_tx = pool.get(elem.first);
5099+
if (added_tx != nullptr) {
5100+
pool.AddUnbroadcastTx(elem.first, added_tx->GetWitnessHash());
5101+
}
5102+
}
51005103
} catch (const std::exception& e) {
51015104
LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing anyway.\n", e.what());
51025105
return false;
@@ -5112,7 +5115,7 @@ bool DumpMempool(const CTxMemPool& pool)
51125115

51135116
std::map<uint256, CAmount> mapDeltas;
51145117
std::vector<TxMempoolInfo> vinfo;
5115-
std::set<uint256> unbroadcast_txids;
5118+
std::map<uint256, uint256> unbroadcast_txids;
51165119

51175120
static Mutex dump_mutex;
51185121
LOCK(dump_mutex);

0 commit comments

Comments
 (0)