-
Notifications
You must be signed in to change notification settings - Fork 37.6k
Use wtxid for transaction relay #18044
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2b4b90a
c7eb6b4
60f0acd
08b3995
85c78d5
144c385
8e68fc2
ac88e2e
2d282e0
46d78d4
97141ca
4eb5155
dd78d1d
9a5392f
8d8099e
cacd852
0e20cfe
0a4f142
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -261,6 +261,12 @@ extern const char* GETCFCHECKPT; | |
* evenly spaced filter headers for blocks on the requested chain. | ||
*/ | ||
extern const char* CFCHECKPT; | ||
/** | ||
* Indicates that a node prefers to relay transactions via wtxid, rather than | ||
* txid. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this rule a best-effort right now? At tx reception, we may have to request parent based only on the input. If so is the BIP clear enough on the 5) ? Precise also there is no current sanction of the rule by receiver as of today There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, you made a good observation -- our fetching of parents of orphan transactions must be by txid, rather than wtxid. I did try to carefully word the BIP to avoid referencing this situation:
So the BIP says that newly announced transactions must be by wtxid. And if your peer announces a transaction by wtxid, you must fetch it by wtxid (and not txid). The BIP is silent on requests for transactions that are not announced -- we could make this explicit but this has never been documented behavior, and I'm not sure we should commit to a certain handling of this kind of thing anyway... But open to suggestions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would amend the BIP to make its scope explicitly restrained. Add to 5) "Note: this rule doesn't cover cases where a peer needs to request transaction for validation purposes but don't know its wtxid yet (e.g missing parents of a wtxid-announced child)" ? At least to avoid someone raising same concern in the future and saying we don't commit on anything wrt to this issue. |
||
* @since protocol version 70016 as described by BIP 339. | ||
*/ | ||
extern const char *WTXIDRELAY; | ||
}; // namespace NetMsgType | ||
|
||
/* Get a vector of all valid message types (see above) */ | ||
|
@@ -397,11 +403,12 @@ const uint32_t MSG_TYPE_MASK = 0xffffffff >> 2; | |
* These numbers are defined by the protocol. When adding a new value, be sure | ||
* to mention it in the respective BIP. | ||
*/ | ||
enum GetDataMsg { | ||
enum GetDataMsg : uint32_t { | ||
UNDEFINED = 0, | ||
MSG_TX = 1, | ||
MSG_BLOCK = 2, | ||
// The following can only occur in getdata. Invs always use TX or BLOCK. | ||
MSG_WTX = 5, //!< Defined in BIP 339 | ||
// The following can only occur in getdata. Invs always use TX/WTX or BLOCK. | ||
MSG_FILTERED_BLOCK = 3, //!< Defined in BIP37 | ||
MSG_CMPCT_BLOCK = 4, //!< Defined in BIP152 | ||
MSG_WITNESS_BLOCK = MSG_BLOCK | MSG_WITNESS_FLAG, //!< Defined in BIP144 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -726,12 +726,12 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const | |
assert(innerUsage == cachedInnerUsage); | ||
} | ||
|
||
bool CTxMemPool::CompareDepthAndScore(const uint256& hasha, const uint256& hashb) | ||
bool CTxMemPool::CompareDepthAndScore(const uint256& hasha, const uint256& hashb, bool wtxid) | ||
{ | ||
LOCK(cs); | ||
indexed_transaction_set::const_iterator i = mapTx.find(hasha); | ||
indexed_transaction_set::const_iterator i = wtxid ? get_iter_from_wtxid(hasha) : mapTx.find(hasha); | ||
if (i == mapTx.end()) return false; | ||
indexed_transaction_set::const_iterator j = mapTx.find(hashb); | ||
indexed_transaction_set::const_iterator j = wtxid ? get_iter_from_wtxid(hashb) : mapTx.find(hashb); | ||
if (j == mapTx.end()) return true; | ||
uint64_t counta = i->GetCountWithAncestors(); | ||
uint64_t countb = j->GetCountWithAncestors(); | ||
|
@@ -811,10 +811,10 @@ CTransactionRef CTxMemPool::get(const uint256& hash) const | |
return i->GetSharedTx(); | ||
} | ||
|
||
TxMempoolInfo CTxMemPool::info(const uint256& hash) const | ||
TxMempoolInfo CTxMemPool::info(const uint256& hash, bool wtxid) const | ||
{ | ||
LOCK(cs); | ||
indexed_transaction_set::const_iterator i = mapTx.find(hash); | ||
indexed_transaction_set::const_iterator i = (wtxid ? get_iter_from_wtxid(hash) : mapTx.find(hash)); | ||
if (i == mapTx.end()) | ||
return TxMempoolInfo(); | ||
return GetInfo(i); | ||
|
@@ -917,8 +917,8 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const { | |
|
||
size_t CTxMemPool::DynamicMemoryUsage() const { | ||
LOCK(cs); | ||
// Estimate the overhead of mapTx to be 12 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented. | ||
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 12 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage; | ||
// Estimate the overhead of mapTx to be 15 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 54d58e7 3 new pointers for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ariard I don't understand this comment (1c382b5#r385362270). I think 3 pointers for an extra index is probably close to correct, but #18086 may make these kinds of heuristics unnecessary, so that's probably a better approach long term. |
||
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 15 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage; | ||
} | ||
|
||
void CTxMemPool::RemoveUnbroadcastTx(const uint256& txid, const bool unchecked) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -198,6 +198,22 @@ struct mempoolentry_txid | |
} | ||
}; | ||
|
||
// extracts a transaction witness-hash from CTxMemPoolEntry or CTransactionRef | ||
struct mempoolentry_wtxid | ||
{ | ||
typedef uint256 result_type; | ||
result_type operator() (const CTxMemPoolEntry &entry) const | ||
{ | ||
return entry.GetTx().GetWitnessHash(); | ||
} | ||
|
||
result_type operator() (const CTransactionRef& tx) const | ||
{ | ||
return tx->GetWitnessHash(); | ||
} | ||
}; | ||
|
||
|
||
/** \class CompareTxMemPoolEntryByDescendantScore | ||
* | ||
* Sort an entry by max(score/size of entry's tx, score/size with all descendants). | ||
|
@@ -318,6 +334,7 @@ class CompareTxMemPoolEntryByAncestorFee | |
struct descendant_score {}; | ||
struct entry_time {}; | ||
struct ancestor_score {}; | ||
struct index_by_wtxid {}; | ||
|
||
class CBlockPolicyEstimator; | ||
|
||
|
@@ -383,8 +400,9 @@ class SaltedTxidHasher | |
* | ||
* CTxMemPool::mapTx, and CTxMemPoolEntry bookkeeping: | ||
* | ||
* mapTx is a boost::multi_index that sorts the mempool on 4 criteria: | ||
* - transaction hash | ||
* mapTx is a boost::multi_index that sorts the mempool on 5 criteria: | ||
* - transaction hash (txid) | ||
* - witness-transaction hash (wtxid) | ||
* - descendant feerate [we use max(feerate of tx, feerate of tx with all descendants)] | ||
* - time in mempool | ||
* - ancestor feerate [we use min(feerate of tx, feerate of tx with all unconfirmed ancestors)] | ||
|
@@ -469,6 +487,12 @@ class CTxMemPool | |
boost::multi_index::indexed_by< | ||
// sorted by txid | ||
boost::multi_index::hashed_unique<mempoolentry_txid, SaltedTxidHasher>, | ||
// sorted by wtxid | ||
sdaftuar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
boost::multi_index::hashed_unique< | ||
boost::multi_index::tag<index_by_wtxid>, | ||
mempoolentry_wtxid, | ||
SaltedTxidHasher | ||
>, | ||
// sorted by fee rate | ||
boost::multi_index::ordered_non_unique< | ||
boost::multi_index::tag<descendant_score>, | ||
|
@@ -549,8 +573,11 @@ class CTxMemPool | |
|
||
std::vector<indexed_transaction_set::const_iterator> GetSortedDepthAndScore() const EXCLUSIVE_LOCKS_REQUIRED(cs); | ||
|
||
/** track locally submitted transactions to periodically retry initial broadcast */ | ||
std::set<uint256> m_unbroadcast_txids GUARDED_BY(cs); | ||
/** | ||
* track locally submitted transactions to periodically retry initial broadcast | ||
* map of txid -> wtxid | ||
*/ | ||
std::map<uint256, uint256> m_unbroadcast_txids GUARDED_BY(cs); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this change is necessary (or desirable). I guess it was prompted by this comment: #18038 (comment) , but storing this as a map from txid->wtxid is unnecessary and confusing, since it obfuscates the purpose of this data, which is a set of transactions that may need to be rebroadcast. The only place that the wtxid is read from this map is in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My mistake here, effectively you need to announce transactions from the unbroadcast set by wtxid to upgraded peers but as you point out it doesn't mean we need to cache them as a solution. Maybe we should precise unbroadcast set semantic wrt same txid, different wtxids, we only guarantee reattempt to the best mempool candidate known for a given txid at the time we call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really think this "same txid, different wtxid" thing is a complete red herring. The mempool can only ever be aware of one witness for a transaction, so any attempt to announce the transaction via a different wtxid would fail anyway. As Suhas pointed out earlier (#18044 (comment)), to support tracking multiple witnesses we'd need to make significant changes many of our components. Besides, the unbroadcast set is transactions that we created ourselves, so we're not expecting the witnesses to change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree with @jnewbery. There is no real way that we can reasonable start tracking multiple witnesses for the same transaction (either in the mempool or the wallet, ...), so unless this map's storing of wtxid is needed for efficiency, it seems just a set of txid should be sufficient here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😮 wow that's a great point @jnewbery. thank you!! I thought during implementation I ran into something that prevented me from exclusively having txids on the set, but that evaluation is simply wrong. I've taken a first pass at reverting unbroadcast to a set here: amitiuttarwar@f51cccd. I'll need to revisit with fresh eyes and then will PR. I agree set makes more sense than a map- its simpler, communicates the intent better, and there's no noticeable efficiency gain with the map. Since we check the transaction is the mempool, the difference between map vs set is the difference between calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @amitiuttarwar mapTX is a multiindex and we use a hashed index not a ordered index so it's O(1) lookup. Not sure exactly how mapTX relates to m_unboardcast_txids though... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @amitiuttarwar that patch looks correct from a quick skim. I'll review fully once you open a PR. As @JeremyRubin points out, we're using a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
public: | ||
indirectmap<COutPoint, const CTransaction*> mapNextTx GUARDED_BY(cs); | ||
|
@@ -586,7 +613,7 @@ class CTxMemPool | |
|
||
void clear(); | ||
void _clear() EXCLUSIVE_LOCKS_REQUIRED(cs); //lock free | ||
bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb); | ||
bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb, bool wtxid=false); | ||
void queryHashes(std::vector<uint256>& vtxid) const; | ||
bool isSpent(const COutPoint& outpoint) const; | ||
unsigned int GetTransactionsUpdated() const; | ||
|
@@ -689,32 +716,40 @@ class CTxMemPool | |
return totalTxSize; | ||
} | ||
|
||
bool exists(const uint256& hash) const | ||
bool exists(const uint256& hash, bool wtxid=false) const | ||
{ | ||
LOCK(cs); | ||
if (wtxid) { | ||
return (mapTx.get<index_by_wtxid>().count(hash) != 0); | ||
} | ||
return (mapTx.count(hash) != 0); | ||
} | ||
|
||
CTransactionRef get(const uint256& hash) const; | ||
TxMempoolInfo info(const uint256& hash) const; | ||
txiter get_iter_from_wtxid(const uint256& wtxid) const EXCLUSIVE_LOCKS_REQUIRED(cs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above, I think it would be clearer if this was combined with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In commit "Add a wtxid-index to the mempool" I think this would be a nice improvement. |
||
{ | ||
AssertLockHeld(cs); | ||
return mapTx.project<0>(mapTx.get<index_by_wtxid>().find(wtxid)); | ||
} | ||
TxMempoolInfo info(const uint256& hash, bool wtxid=false) const; | ||
std::vector<TxMempoolInfo> infoAll() const; | ||
|
||
size_t DynamicMemoryUsage() const; | ||
|
||
/** Adds a transaction to the unbroadcast set */ | ||
void AddUnbroadcastTx(const uint256& txid) { | ||
void AddUnbroadcastTx(const uint256& txid, const uint256& wtxid) { | ||
LOCK(cs); | ||
// Sanity Check: the transaction should also be in the mempool | ||
if (exists(txid)) { | ||
m_unbroadcast_txids.insert(txid); | ||
m_unbroadcast_txids[txid] = wtxid; | ||
sdaftuar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
/** Removes a transaction from the unbroadcast set */ | ||
void RemoveUnbroadcastTx(const uint256& txid, const bool unchecked = false); | ||
|
||
/** Returns transactions in unbroadcast set */ | ||
std::set<uint256> GetUnbroadcastTxs() const { | ||
std::map<uint256, uint256> GetUnbroadcastTxs() const { | ||
LOCK(cs); | ||
return m_unbroadcast_txids; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -939,7 +939,7 @@ bool MemPoolAccept::PolicyScriptChecks(ATMPArgs& args, Workspace& ws, Precompute | |
if (!tx.HasWitness() && CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) && | ||
!CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) { | ||
// Only the witness is missing, so the transaction itself may be fine. | ||
state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, | ||
state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED, | ||
state.GetRejectReason(), state.GetDebugMessage()); | ||
} | ||
return false; // state filled in by CheckInputScripts | ||
|
@@ -5083,19 +5083,22 @@ bool LoadMempool(CTxMemPool& pool) | |
} | ||
|
||
// TODO: remove this try except in v0.22 | ||
std::map<uint256, uint256> unbroadcast_txids; | ||
try { | ||
std::set<uint256> unbroadcast_txids; | ||
file >> unbroadcast_txids; | ||
unbroadcast = unbroadcast_txids.size(); | ||
|
||
for (const auto& txid : unbroadcast_txids) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarification: does moving this out of the try block have any behavior change? Is it possible that a semi-corrupt unbroadcast_txids would partially deserialize and then have only some entries? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the try block was introduced to specifically catch the error when upgrading around not having an unbroadcast set written to disk. so I think having the initialization & iteration separate actually captures that intent better. but lmk if you think of any specific ways this logic could be problematic |
||
pool.AddUnbroadcastTx(txid); | ||
} | ||
} catch (const std::exception&) { | ||
// mempool.dat files created prior to v0.21 will not have an | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest: clearing unbroadcast_txids here in case has garbage data. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looking into this- is there really a way that the CAutoFile There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes I believe so? Fortunately for std::map we deserialize an element and then insert it into the map, but is a possibility that we say that there should be 10 elements and there are only 5 elements, or there are 5 elements and their are actually 10. In the event the file is corrupt, we should likely treat it as if the entire thing is corrupt, rather than continuing to process. Perhaps we should add an exception to throw from Unserialize if there are not the correct amount of entries? I'm not sure to the degree we need to worry about files on our own local being corrupt, and there are other forms of corruption that can occur. But because it is a behavior change, I'm noting it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to be clear the issue only comes up when you don't have enough elements or when you have a half element presently. |
||
// unbroadcast set. No need to log a failure if parsing fails here. | ||
} | ||
|
||
for (const auto& elem : unbroadcast_txids) { | ||
// Don't add unbroadcast transactions that didn't get back into the | ||
// mempool. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this correct? Should this transaction get queued somewhere for rebroadcasting? Or is it a known precondition that because it failed to get into the mempool before this line it will fail again? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't think of why we'd want it in the unbroadcast if it didn't make it back in the mempool, but just double checking. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the unbroadcast set should always be a subset of the mempool. it just maintains txids, so we don't have the capability to do much if its not found.
do you mean re-attempt mempool submission? bc we def wouldn't want to broadcast or rebroadcast a transaction we don't have in our mempool! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes. it's possible that we may want to resubmit it to the mempool if it was previously in our mempool. What changed to cause it to no longer be accepted? |
||
const CTransactionRef& added_tx = pool.get(elem.first); | ||
if (added_tx != nullptr) { | ||
pool.AddUnbroadcastTx(elem.first, added_tx->GetWitnessHash()); | ||
} | ||
} | ||
} catch (const std::exception& e) { | ||
LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing anyway.\n", e.what()); | ||
return false; | ||
|
@@ -5111,7 +5114,7 @@ bool DumpMempool(const CTxMemPool& pool) | |
|
||
std::map<uint256, CAmount> mapDeltas; | ||
std::vector<TxMempoolInfo> vinfo; | ||
std::set<uint256> unbroadcast_txids; | ||
std::map<uint256, uint256> unbroadcast_txids; | ||
|
||
static Mutex dump_mutex; | ||
LOCK(dump_mutex); | ||
|
Uh oh!
There was an error while loading. Please reload this page.