Skip to content

Commit

Permalink
Merge #19569: Enable fetching of orphan parents from wtxid peers
Browse files Browse the repository at this point in the history
10b7a6d refactor: make txmempool interface use GenTxid (Pieter Wuille)
5c124e1 refactor: make FindTxForGetData use GenTxid (Pieter Wuille)
a2bfac8 refactor: use GenTxid in tx request functions (Pieter Wuille)
e65d115 test: request parents of orphan from wtxid relay peer (Anthony Towns)
900d7f6 p2p: enable fetching of orphans from wtxid peers (Pieter Wuille)
9efd86a refactor: add GenTxid (=txid or wtxid) type and use it for tx request logic (Pieter Wuille)
d362f19 doc: list support for BIP 339 in doc/bips.md (Pieter Wuille)

Pull request description:

  This is based on #18044 (comment).

  A new type `GenTxid` is added to protocol.h, which represents a tagged txid-or-wtxid. The tx request logic is updated to use these instead of uint256s, permitting per-announcement distinguishing of txid/wtxid (instead of assuming that everything we want to request from a wtxid peer is wtx). Then the restriction of orphan-parent requesting to non-wtxid peers is lifted.

  Also document BIP339 in doc/bips.md.

ACKs for top commit:
  jnewbery:
    Code review ACK 10b7a6d
  jonatack:
    ACK 10b7a6d
  ajtowns:
    ACK 10b7a6d -- code review. Using gtxid to replace the is_txid_or_wtxid flag for the mempool functions is nice.
  naumenkogs:
    utACK 10b7a6d

Tree-SHA512: d518d13ffd71f8d2b3c175dc905362a7259689e6022a97a0b4f14f1f9fdd87475cf5af70cb12338d1e5d31b52c12e4faaea436114056a2ae9669cb506240758b
  • Loading branch information
fanquake committed Jul 31, 2020
2 parents a63a26f + 10b7a6d commit f7c73b0
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 63 deletions.
3 changes: 2 additions & 1 deletion doc/bips.md
@@ -1,4 +1,4 @@
BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.19.0**):
BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.21.0**):

* [`BIP 9`](https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki): The changes allowing multiple soft-forks to be deployed in parallel have been implemented since **v0.12.1** ([PR #7575](https://github.com/bitcoin/bitcoin/pull/7575))
* [`BIP 11`](https://github.com/bitcoin/bips/blob/master/bip-0011.mediawiki): Multisig outputs are standard since **v0.6.0** ([PR #669](https://github.com/bitcoin/bitcoin/pull/669)).
Expand Down Expand Up @@ -42,3 +42,4 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.19.0**):
* [`BIP 173`](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki): Bech32 addresses for native Segregated Witness outputs are supported as of **v0.16.0** ([PR 11167](https://github.com/bitcoin/bitcoin/pull/11167)). Bech32 addresses are generated by default as of **v0.20.0** ([PR 16884](https://github.com/bitcoin/bitcoin/pull/16884)).
* [`BIP 174`](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki): RPCs to operate on Partially Signed Bitcoin Transactions (PSBT) are present as of **v0.17.0** ([PR 13557](https://github.com/bitcoin/bitcoin/pull/13557)).
* [`BIP 176`](https://github.com/bitcoin/bips/blob/master/bip-0176.mediawiki): Bits Denomination [QT only] is supported as of **v0.16.0** ([PR 12035](https://github.com/bitcoin/bitcoin/pull/12035)).
* [`BIP 339`](https://github.com/bitcoin/bips/blob/master/bip-0339.mediawiki): Relay of transactions by wtxid is supported as of **v0.21.0** ([PR 18044](https://github.com/bitcoin/bitcoin/pull/18044)).
101 changes: 52 additions & 49 deletions src/net_processing.cpp
Expand Up @@ -236,7 +236,7 @@ namespace {
/** When our tip was last updated. */
std::atomic<int64_t> g_last_tip_update(0);

/** Relay map */
/** Relay map (txid or wtxid -> CTransactionRef) */
typedef std::map<uint256, CTransactionRef> MapRelay;
MapRelay mapRelay GUARDED_BY(cs_main);
/** Expiration-time ordered list of (expire time, relay map entry) pairs. */
Expand Down Expand Up @@ -398,7 +398,7 @@ struct CNodeState {
/* Track when to attempt download of announced transactions (process
* time in micros -> txid)
*/
std::multimap<std::chrono::microseconds, uint256> m_tx_process_time;
std::multimap<std::chrono::microseconds, GenTxid> m_tx_process_time;

//! Store all the transactions a peer has recently announced
std::set<uint256> m_tx_announced;
Expand Down Expand Up @@ -751,34 +751,34 @@ static void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vec
}
}

void EraseTxRequest(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
void EraseTxRequest(const GenTxid& gtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
g_already_asked_for.erase(txid);
g_already_asked_for.erase(gtxid.GetHash());
}

std::chrono::microseconds GetTxRequestTime(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
std::chrono::microseconds GetTxRequestTime(const GenTxid& gtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
auto it = g_already_asked_for.find(txid);
auto it = g_already_asked_for.find(gtxid.GetHash());
if (it != g_already_asked_for.end()) {
return it->second;
}
return {};
}

void UpdateTxRequestTime(const uint256& txid, std::chrono::microseconds request_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
void UpdateTxRequestTime(const GenTxid& gtxid, std::chrono::microseconds request_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
auto it = g_already_asked_for.find(txid);
auto it = g_already_asked_for.find(gtxid.GetHash());
if (it == g_already_asked_for.end()) {
g_already_asked_for.insert(std::make_pair(txid, request_time));
g_already_asked_for.insert(std::make_pair(gtxid.GetHash(), request_time));
} else {
g_already_asked_for.update(it, request_time);
}
}

std::chrono::microseconds CalculateTxGetDataTime(const uint256& txid, std::chrono::microseconds current_time, bool use_inbound_delay, bool use_txid_delay) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
std::chrono::microseconds CalculateTxGetDataTime(const GenTxid& gtxid, std::chrono::microseconds current_time, bool use_inbound_delay, bool use_txid_delay) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
std::chrono::microseconds process_time;
const auto last_request_time = GetTxRequestTime(txid);
const auto last_request_time = GetTxRequestTime(gtxid);
// First time requesting this tx
if (last_request_time.count() == 0) {
process_time = current_time;
Expand All @@ -797,23 +797,23 @@ std::chrono::microseconds CalculateTxGetDataTime(const uint256& txid, std::chron
return process_time;
}

void RequestTx(CNodeState* state, const uint256& txid, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
void RequestTx(CNodeState* state, const GenTxid& gtxid, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
CNodeState::TxDownloadState& peer_download_state = state->m_tx_download;
if (peer_download_state.m_tx_announced.size() >= MAX_PEER_TX_ANNOUNCEMENTS ||
peer_download_state.m_tx_process_time.size() >= MAX_PEER_TX_ANNOUNCEMENTS ||
peer_download_state.m_tx_announced.count(txid)) {
peer_download_state.m_tx_announced.count(gtxid.GetHash())) {
// Too many queued announcements from this peer, or we already have
// this announcement
return;
}
peer_download_state.m_tx_announced.insert(txid);
peer_download_state.m_tx_announced.insert(gtxid.GetHash());

// Calculate the time to try requesting this transaction. Use
// fPreferredDownload as a proxy for outbound peers.
const auto process_time = CalculateTxGetDataTime(txid, current_time, !state->fPreferredDownload, !state->m_wtxid_relay && g_wtxid_relay_peers > 0);
const auto process_time = CalculateTxGetDataTime(gtxid, current_time, !state->fPreferredDownload, !state->m_wtxid_relay && g_wtxid_relay_peers > 0);

peer_download_state.m_tx_process_time.emplace(process_time, txid);
peer_download_state.m_tx_process_time.emplace(process_time, gtxid);
}

} // namespace
Expand Down Expand Up @@ -1453,7 +1453,7 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
if (g_recent_confirmed_transactions->contains(inv.hash)) return true;
}

return recentRejects->contains(inv.hash) || mempool.exists(inv.hash, inv.IsMsgWtx());
return recentRejects->contains(inv.hash) || mempool.exists(ToGenTxid(inv));
}
case MSG_BLOCK:
case MSG_WITNESS_BLOCK:
Expand Down Expand Up @@ -1671,9 +1671,9 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
}

//! Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed).
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)
CTransactionRef static FindTxForGetData(const CNode& peer, const GenTxid& gtxid, const std::chrono::seconds mempool_req, const std::chrono::seconds now) LOCKS_EXCLUDED(cs_main)
{
auto txinfo = mempool.info(txid_or_wtxid, use_wtxid);
auto txinfo = mempool.info(gtxid);
if (txinfo.tx) {
// If a TX could have been INVed in reply to a MEMPOOL request,
// or is older than UNCONDITIONAL_RELAY_DELAY, permit the request
Expand All @@ -1686,11 +1686,11 @@ CTransactionRef static FindTxForGetData(const CNode& peer, const uint256& txid_o
{
LOCK(cs_main);
// Otherwise, the transaction must have been announced recently.
if (State(peer.GetId())->m_recently_announced_invs.contains(txid_or_wtxid)) {
if (State(peer.GetId())->m_recently_announced_invs.contains(gtxid.GetHash())) {
// If it was, it can be relayed from either the mempool...
if (txinfo.tx) return std::move(txinfo.tx);
// ... or the relay pool.
auto mi = mapRelay.find(txid_or_wtxid);
auto mi = mapRelay.find(gtxid.GetHash());
if (mi != mapRelay.end()) return mi->second;
}
}
Expand Down Expand Up @@ -1727,7 +1727,7 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm
continue;
}

CTransactionRef tx = FindTxForGetData(pfrom, inv.hash, inv.IsMsgWtx(), mempool_req, now);
CTransactionRef tx = FindTxForGetData(pfrom, ToGenTxid(inv), mempool_req, now);
if (tx) {
// WTX and WITNESS_TX imply we serialize with witness
int nSendFlags = (inv.IsMsgTx() ? SERIALIZE_TRANSACTION_NO_WITNESS : 0);
Expand Down Expand Up @@ -2647,7 +2647,9 @@ void ProcessMessage(
if (interruptMsgProc)
return;

// ignore INVs that don't match wtxidrelay setting
// Ignore INVs that don't match wtxidrelay setting.
// Note that orphan parent fetching always uses MSG_TX GETDATAs regardless of the wtxidrelay setting.
// This is fine as no INV messages are involved in that process.
if (State(pfrom.GetId())->m_wtxid_relay) {
if (inv.IsMsgTx()) continue;
} else {
Expand Down Expand Up @@ -2678,7 +2680,7 @@ void ProcessMessage(
pfrom.fDisconnect = true;
return;
} else if (!fAlreadyHave && !chainman.ActiveChainstate().IsInitialBlockDownload()) {
RequestTx(State(pfrom.GetId()), inv.hash, current_time);
RequestTx(State(pfrom.GetId()), ToGenTxid(inv), current_time);
}
}
}
Expand Down Expand Up @@ -2931,9 +2933,11 @@ void ProcessMessage(

TxValidationState state;

nodestate->m_tx_download.m_tx_announced.erase(hash);
nodestate->m_tx_download.m_tx_in_flight.erase(hash);
EraseTxRequest(hash);
for (const GenTxid& gtxid : {GenTxid(false, txid), GenTxid(true, wtxid)}) {
nodestate->m_tx_download.m_tx_announced.erase(gtxid.GetHash());
nodestate->m_tx_download.m_tx_in_flight.erase(gtxid.GetHash());
EraseTxRequest(gtxid);
}

std::list<CTransactionRef> lRemovedTxn;

Expand Down Expand Up @@ -2985,17 +2989,15 @@ void ProcessMessage(
uint32_t nFetchFlags = GetFetchFlags(pfrom);
const auto current_time = GetTime<std::chrono::microseconds>();

if (!State(pfrom.GetId())->m_wtxid_relay) {
for (const CTxIn& txin : tx.vin) {
// Here, we only have the txid (and not wtxid) of the
// inputs, so we only request parents from
// non-wtxid-relay peers.
// Eventually we should replace this with an improved
// protocol for getting all unconfirmed parents.
CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash);
pfrom.AddKnownTx(txin.prevout.hash);
if (!AlreadyHave(_inv, mempool)) RequestTx(State(pfrom.GetId()), _inv.hash, current_time);
}
for (const CTxIn& txin : tx.vin) {
// Here, we only have the txid (and not wtxid) of the
// inputs, so we only request in txid mode, even for
// wtxidrelay peers.
// Eventually we should replace this with an improved
// protocol for getting all unconfirmed parents.
CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash);
pfrom.AddKnownTx(txin.prevout.hash);
if (!AlreadyHave(_inv, mempool)) RequestTx(State(pfrom.GetId()), ToGenTxid(_inv), current_time);
}
AddOrphanTx(ptx, pfrom.GetId());

Expand Down Expand Up @@ -4356,14 +4358,15 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
std::set<uint256>::iterator it = vInvTx.back();
vInvTx.pop_back();
uint256 hash = *it;
CInv inv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash);
// Remove it from the to-be-sent set
pto->m_tx_relay->setInventoryTxToSend.erase(it);
// Check if not in the filter already
if (pto->m_tx_relay->filterInventoryKnown.contains(hash)) {
continue;
}
// Not in the mempool anymore? don't bother sending it.
auto txinfo = m_mempool.info(hash, state.m_wtxid_relay);
auto txinfo = m_mempool.info(ToGenTxid(inv));
if (!txinfo.tx) {
continue;
}
Expand All @@ -4376,7 +4379,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
// Send
State(pto->GetId())->m_recently_announced_invs.insert(hash);
vInv.push_back(CInv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash));
vInv.push_back(inv);
nRelayedTransactions++;
{
// Expire old relay messages
Expand Down Expand Up @@ -4529,24 +4532,24 @@ bool PeerLogicValidation::SendMessages(CNode* pto)

auto& tx_process_time = state.m_tx_download.m_tx_process_time;
while (!tx_process_time.empty() && tx_process_time.begin()->first <= current_time && state.m_tx_download.m_tx_in_flight.size() < MAX_PEER_TX_IN_FLIGHT) {
const uint256 txid = tx_process_time.begin()->second;
const GenTxid gtxid = tx_process_time.begin()->second;
// Erase this entry from tx_process_time (it may be added back for
// processing at a later time, see below)
tx_process_time.erase(tx_process_time.begin());
CInv inv(state.m_wtxid_relay ? MSG_WTX : (MSG_TX | GetFetchFlags(*pto)), txid);
CInv inv(gtxid.IsWtxid() ? MSG_WTX : (MSG_TX | GetFetchFlags(*pto)), gtxid.GetHash());
if (!AlreadyHave(inv, m_mempool)) {
// If this transaction was last requested more than 1 minute ago,
// then request.
const auto last_request_time = GetTxRequestTime(inv.hash);
const auto last_request_time = GetTxRequestTime(gtxid);
if (last_request_time <= current_time - GETDATA_TX_INTERVAL) {
LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), pto->GetId());
vGetData.push_back(inv);
if (vGetData.size() >= MAX_GETDATA_SZ) {
connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));
vGetData.clear();
}
UpdateTxRequestTime(inv.hash, current_time);
state.m_tx_download.m_tx_in_flight.emplace(inv.hash, current_time);
UpdateTxRequestTime(gtxid, current_time);
state.m_tx_download.m_tx_in_flight.emplace(gtxid.GetHash(), current_time);
} else {
// This transaction is in flight from someone else; queue
// up processing to happen after the download times out
Expand All @@ -4560,13 +4563,13 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
// would open us up to an attacker using inbound
// wtxid-relay to prevent us from requesting transactions
// from outbound txid-relay peers).
const auto next_process_time = CalculateTxGetDataTime(txid, current_time, !state.fPreferredDownload, false);
tx_process_time.emplace(next_process_time, txid);
const auto next_process_time = CalculateTxGetDataTime(gtxid, current_time, !state.fPreferredDownload, false);
tx_process_time.emplace(next_process_time, gtxid);
}
} else {
// We have already seen this transaction, no need to download.
state.m_tx_download.m_tx_announced.erase(inv.hash);
state.m_tx_download.m_tx_in_flight.erase(inv.hash);
state.m_tx_download.m_tx_announced.erase(gtxid.GetHash());
state.m_tx_download.m_tx_in_flight.erase(gtxid.GetHash());
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/primitives/transaction.h
Expand Up @@ -12,6 +12,8 @@
#include <serialize.h>
#include <uint256.h>

#include <tuple>

static const int SERIALIZE_TRANSACTION_NO_WITNESS = 0x40000000;

/** An outpoint - a combination of a transaction hash and an index n into its vout */
Expand Down Expand Up @@ -388,4 +390,17 @@ typedef std::shared_ptr<const CTransaction> CTransactionRef;
static inline CTransactionRef MakeTransactionRef() { return std::make_shared<const CTransaction>(); }
template <typename Tx> static inline CTransactionRef MakeTransactionRef(Tx&& txIn) { return std::make_shared<const CTransaction>(std::forward<Tx>(txIn)); }

/** A generic txid reference (txid or wtxid). */
class GenTxid
{
const bool m_is_wtxid;
const uint256 m_hash;
public:
GenTxid(bool is_wtxid, const uint256& hash) : m_is_wtxid(is_wtxid), m_hash(hash) {}
bool IsWtxid() const { return m_is_wtxid; }
const uint256& GetHash() const { return m_hash; }
friend bool operator==(const GenTxid& a, const GenTxid& b) { return a.m_is_wtxid == b.m_is_wtxid && a.m_hash == b.m_hash; }
friend bool operator<(const GenTxid& a, const GenTxid& b) { return std::tie(a.m_is_wtxid, a.m_hash) < std::tie(b.m_is_wtxid, b.m_hash); }
};

#endif // BITCOIN_PRIMITIVES_TRANSACTION_H
6 changes: 6 additions & 0 deletions src/protocol.cpp
Expand Up @@ -241,3 +241,9 @@ std::vector<std::string> serviceFlagsToStr(uint64_t flags)

return str_flags;
}

GenTxid ToGenTxid(const CInv& inv)
{
assert(inv.IsGenTxMsg());
return {inv.IsMsgWtx(), inv.hash};
}
4 changes: 4 additions & 0 deletions src/protocol.h
Expand Up @@ -11,6 +11,7 @@
#define BITCOIN_PROTOCOL_H

#include <netaddress.h>
#include <primitives/transaction.h>
#include <serialize.h>
#include <uint256.h>
#include <version.h>
Expand Down Expand Up @@ -442,4 +443,7 @@ class CInv
uint256 hash;
};

/** Convert a TX/WITNESS_TX/WTX CInv to a GenTxid. */
GenTxid ToGenTxid(const CInv& inv);

#endif // BITCOIN_PROTOCOL_H
6 changes: 4 additions & 2 deletions src/txmempool.cpp
Expand Up @@ -811,15 +811,17 @@ CTransactionRef CTxMemPool::get(const uint256& hash) const
return i->GetSharedTx();
}

TxMempoolInfo CTxMemPool::info(const uint256& hash, bool wtxid) const
TxMempoolInfo CTxMemPool::info(const GenTxid& gtxid) const
{
LOCK(cs);
indexed_transaction_set::const_iterator i = (wtxid ? get_iter_from_wtxid(hash) : mapTx.find(hash));
indexed_transaction_set::const_iterator i = (gtxid.IsWtxid() ? get_iter_from_wtxid(gtxid.GetHash()) : mapTx.find(gtxid.GetHash()));
if (i == mapTx.end())
return TxMempoolInfo();
return GetInfo(i);
}

TxMempoolInfo CTxMemPool::info(const uint256& txid) const { return info(GenTxid{false, txid}); }

void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeDelta)
{
{
Expand Down

0 comments on commit f7c73b0

Please sign in to comment.