Skip to content

Commit 5f5b120

Browse files
navidRjeffro256
authored andcommitted
tx relay v2: rework tx request management and relay logic
Implement new relay logic with threshold-based peer dropping, single-peer tx hash requests, per-connection in-flight limits with queuing, and multiindex-based request tracking. Consolidate add/remove request paths, pass tx hashes directly into send_txs/relay_txs, and add unit tests for the request manager. Co-authored-by: j-berman <justinberman@protonmail.com> Co-authored-by: Boog900 <boog900@tutanota.com>
1 parent c182abb commit 5f5b120

36 files changed

Lines changed: 1159 additions & 219 deletions

contrib/epee/include/math_helper.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#pragma once
2828

2929
#include <cstdint> // uint64_t
30+
#include <cstdlib> // NULL
31+
#include <utility>
3032

3133
#ifdef _WIN32
3234
#include <sysinfoapi.h> // GetSystemTimeAsFileTime
@@ -41,6 +43,7 @@ namespace math_helper
4143
template<typename get_interval, bool start_immediate = true>
4244
class once_a_time
4345
{
46+
get_interval m_get_interval;
4447
uint64_t get_time() const
4548
{
4649
#ifdef _WIN32
@@ -61,11 +64,12 @@ namespace math_helper
6164

6265
void set_next_interval()
6366
{
64-
m_interval = get_interval()();
67+
m_interval = m_get_interval();
6568
}
6669

6770
public:
68-
once_a_time()
71+
once_a_time(get_interval gi = get_interval())
72+
: m_get_interval(std::move(gi))
6973
{
7074
m_last_worked_time = 0;
7175
if(!start_immediate)

contrib/epee/include/net/http_protocol_handler.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "net_utils_base.h"
3737
#include "http_auth.h"
3838
#include "http_base.h"
39+
#include "syncobj.h"
3940

4041
#undef MONERO_DEFAULT_LOG_CATEGORY
4142
#define MONERO_DEFAULT_LOG_CATEGORY "net.http"

contrib/epee/include/net/levin_protocol_handler_async.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
#include "time_helper.h"
4141
#include "int-util.h"
4242

43+
#include "cryptonote_basic/connection_context.h"
44+
4345
#include <random>
4446
#include <chrono>
4547

@@ -480,7 +482,7 @@ class async_protocol_handler
480482

481483
buff_to_invoke = {buff_to_invoke.data(), std::size_t(inner_size)};
482484

483-
const size_t max_bytes = m_connection_context.get_max_bytes(m_current_head.m_command);
485+
const size_t max_bytes = cryptonote::get_command_max_bytes(m_current_head.m_command);
484486
if(buff_to_invoke.size() > std::min<size_t>(max_packet_size, max_bytes))
485487
{
486488
MERROR(m_connection_context << "Maximum packet size exceed!, m_max_packet_size = " << std::min<size_t>(max_packet_size, max_bytes)
@@ -580,7 +582,7 @@ class async_protocol_handler
580582
m_cache_in_buffer.erase(sizeof(bucket_head2));
581583
m_state = stream_state_body;
582584
m_oponent_protocol_ver = m_current_head.m_protocol_version;
583-
const size_t max_bytes = m_connection_context.get_max_bytes(m_current_head.m_command);
585+
const size_t max_bytes = cryptonote::get_command_max_bytes(m_current_head.m_command);
584586
if(m_current_head.m_cb > std::min<size_t>(max_packet_size, max_bytes))
585587
{
586588
LOG_ERROR_CC(m_connection_context, "Maximum packet size exceed!, m_max_packet_size = " << std::min<size_t>(max_packet_size, max_bytes)

src/blockchain_utilities/blockchain_import.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ int check_flush(cryptonote::core &core, std::vector<block_complete_entry> &block
175175
tx_verification_context tvc = AUTO_VAL_INIT(tvc);
176176
CHECK_AND_ASSERT_THROW_MES(tx_blob.prunable_hash == crypto::null_hash,
177177
"block entry must not contain pruned txs");
178-
core.handle_incoming_tx(tx_blob.blob, tvc, relay_method::block, true);
178+
crypto::hash txid;
179+
core.handle_incoming_tx(tx_blob.blob, tvc, relay_method::block, true, txid);
179180
if(tvc.m_verifivation_failed)
180181
{
181182
cryptonote::transaction transaction;

src/cryptonote_basic/connection_context.cpp

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -30,47 +30,9 @@
3030
#include "connection_context.h"
3131

3232
#include <boost/optional/optional.hpp>
33-
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
34-
#include "p2p/p2p_protocol_defs.h"
3533

3634
namespace cryptonote
3735
{
38-
std::size_t cryptonote_connection_context::get_max_bytes(const int command) noexcept
39-
{
40-
switch (command)
41-
{
42-
case nodetool::COMMAND_HANDSHAKE_T<cryptonote::CORE_SYNC_DATA>::ID:
43-
return 65536;
44-
case nodetool::COMMAND_TIMED_SYNC_T<cryptonote::CORE_SYNC_DATA>::ID:
45-
return 65536;
46-
case nodetool::COMMAND_PING::ID:
47-
return 4096;
48-
case nodetool::COMMAND_REQUEST_SUPPORT_FLAGS::ID:
49-
return 4096;
50-
case cryptonote::NOTIFY_NEW_BLOCK::ID:
51-
return 1024 * 1024 * 128; // 128 MB (max packet is a bit less than 100 MB though)
52-
case cryptonote::NOTIFY_NEW_TRANSACTIONS::ID:
53-
return 1024 * 1024 * 128; // 128 MB (max packet is a bit less than 100 MB though)
54-
case cryptonote::NOTIFY_REQUEST_GET_OBJECTS::ID:
55-
return 1024 * 1024 * 2; // 2 MB
56-
case cryptonote::NOTIFY_RESPONSE_GET_OBJECTS::ID:
57-
return 1024 * 1024 * 128; // 128 MB (max packet is a bit less than 100 MB though)
58-
case cryptonote::NOTIFY_REQUEST_CHAIN::ID:
59-
return 512 * 1024; // 512 kB
60-
case cryptonote::NOTIFY_RESPONSE_CHAIN_ENTRY::ID:
61-
return 1024 * 1024 * 4; // 4 MB
62-
case cryptonote::NOTIFY_NEW_FLUFFY_BLOCK::ID:
63-
return 1024 * 1024 * 4; // 4 MB, but it does not includes transaction data
64-
case cryptonote::NOTIFY_REQUEST_FLUFFY_MISSING_TX::ID:
65-
return 1024 * 1024; // 1 MB
66-
case cryptonote::NOTIFY_GET_TXPOOL_COMPLEMENT::ID:
67-
return 1024 * 1024 * 4; // 4 MB
68-
default:
69-
break;
70-
};
71-
return std::numeric_limits<size_t>::max();
72-
}
73-
7436
void cryptonote_connection_context::set_state_normal()
7537
{
7638
m_state = state_normal;

src/cryptonote_basic/connection_context.h

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,59 @@
3232
#pragma once
3333
#include <unordered_set>
3434
#include <atomic>
35-
#include <algorithm>
35+
#include <memory>
36+
#include <shared_mutex>
3637
#include <boost/date_time/posix_time/posix_time.hpp>
3738
#include <boost/optional/optional_fwd.hpp>
38-
#include "net/net_utils_base.h"
3939
#include "crypto/hash.h"
40+
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
41+
#include "net/net_utils_base.h"
42+
#include "p2p/p2p_protocol_defs.h"
43+
#include "syncobj.h"
4044

4145
namespace cryptonote
4246
{
47+
//! \return Maximum number of bytes permissible for `command`.
48+
constexpr std::size_t get_command_max_bytes(const int command) noexcept
49+
{
50+
switch (command)
51+
{
52+
case nodetool::COMMAND_HANDSHAKE_T<cryptonote::CORE_SYNC_DATA>::ID:
53+
return 65536;
54+
case nodetool::COMMAND_TIMED_SYNC_T<cryptonote::CORE_SYNC_DATA>::ID:
55+
return 65536;
56+
case nodetool::COMMAND_PING::ID:
57+
return 4096;
58+
case nodetool::COMMAND_REQUEST_SUPPORT_FLAGS::ID:
59+
return 4096;
60+
case cryptonote::NOTIFY_NEW_BLOCK::ID:
61+
return 1024 * 1024 * 128; // 128 MB (max packet is a bit less than 100 MB though)
62+
case cryptonote::NOTIFY_NEW_TRANSACTIONS::ID:
63+
return 1024 * 1024 * 128; // 128 MB (max packet is a bit less than 100 MB though)
64+
case cryptonote::NOTIFY_REQUEST_GET_OBJECTS::ID:
65+
return 1024 * 1024 * 2; // 2 MB
66+
case cryptonote::NOTIFY_RESPONSE_GET_OBJECTS::ID:
67+
return 1024 * 1024 * 128; // 128 MB (max packet is a bit less than 100 MB though)
68+
case cryptonote::NOTIFY_REQUEST_CHAIN::ID:
69+
return 512 * 1024; // 512 kB
70+
case cryptonote::NOTIFY_RESPONSE_CHAIN_ENTRY::ID:
71+
return 1024 * 1024 * 4; // 4 MB
72+
case cryptonote::NOTIFY_NEW_FLUFFY_BLOCK::ID:
73+
return 1024 * 1024 * 128; // 128 MB (max packet is a bit less than 100 MB though, fluffy blocks can be full)
74+
case cryptonote::NOTIFY_REQUEST_FLUFFY_MISSING_TX::ID:
75+
return 1024 * 1024; // 1 MB
76+
case cryptonote::NOTIFY_GET_TXPOOL_COMPLEMENT::ID:
77+
return 1024 * 1024 * 4; // 4 MB
78+
case cryptonote::NOTIFY_TX_POOL_HASH::ID:
79+
return 1024 * 1024 * 2; // 2 MB
80+
case cryptonote::NOTIFY_REQUEST_TX_POOL_TXS::ID:
81+
return 1024 * 1024 * 2; // 2 MB
82+
default:
83+
break;
84+
};
85+
return std::numeric_limits<size_t>::max();
86+
}
87+
4388
struct cryptonote_connection_context: public epee::net_utils::connection_context_base
4489
{
4590
cryptonote_connection_context(): m_state(state_before_handshake), m_remote_blockchain_height(0), m_last_response_height(0),
@@ -89,9 +134,6 @@ namespace cryptonote
89134
static constexpr int handshake_command() noexcept { return 1001; }
90135
bool handshake_complete() const noexcept { return m_state != state_before_handshake; }
91136

92-
//! \return Maximum number of bytes permissible for `command`.
93-
static size_t get_max_bytes(int command) noexcept;
94-
95137
//! Use this instead of `m_state = state_normal`.
96138
void set_state_normal();
97139

src/cryptonote_config.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
#define P2P_DEFAULT_PEERS_IN_HANDSHAKE 250
146146
#define P2P_MAX_PEERS_IN_HANDSHAKE 250
147147
#define P2P_DEFAULT_CONNECTION_TIMEOUT 5000 //5 seconds
148+
#define P2P_DEFAULT_REQUEST_TIMEOUT (P2P_DEFAULT_CONNECTION_TIMEOUT*6) // 30 seconds
148149
#define P2P_DEFAULT_SOCKS_CONNECT_TIMEOUT 45 // seconds
149150
#define P2P_DEFAULT_PING_CONNECTION_TIMEOUT 2000 //2 seconds
150151
#define P2P_DEFAULT_INVOKE_TIMEOUT 60*2*1000 //2 minutes
@@ -154,14 +155,17 @@
154155
#define P2P_DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT 2
155156
#define P2P_DEFAULT_LIMIT_RATE_UP 8192 // kB/s
156157
#define P2P_DEFAULT_LIMIT_RATE_DOWN 32768 // kB/s
158+
#define P2P_REQUEST_FAILURE_THRESHOLD_PERCENTAGE 70 // if more than 70% of requests fail, the peer is dropped
159+
#define P2P_MIN_SAMPLE_SIZE_FOR_DROPPING 5 // minimum number of requests to consider dropping a peer for failed requests
157160

158161
#define P2P_FAILED_ADDR_FORGET_SECONDS (60*60) //1 hour
159162
#define P2P_IP_BLOCKTIME (60*60*24) //24 hour
160163
#define P2P_IP_FAILS_BEFORE_BLOCK 10
161164
#define P2P_IDLE_CONNECTION_KILL_INTERVAL (5*60) //5 minutes
162165

163166
#define P2P_SUPPORT_FLAG_FLUFFY_BLOCKS 0x01
164-
#define P2P_SUPPORT_FLAGS P2P_SUPPORT_FLAG_FLUFFY_BLOCKS
167+
#define P2P_SUPPORT_FLAG_TX_RELAY_V2 0x02
168+
#define P2P_SUPPORT_FLAGS (P2P_SUPPORT_FLAG_FLUFFY_BLOCKS | P2P_SUPPORT_FLAG_TX_RELAY_V2)
165169

166170
#define RPC_IP_FAILS_BEFORE_BLOCK 3
167171

src/cryptonote_core/cryptonote_core.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ namespace cryptonote
135135
"sync-pruned-blocks"
136136
, "Allow syncing from nodes with only pruned blocks"
137137
};
138-
139138
static const command_line::arg_descriptor<bool> arg_test_drop_download = {
140139
"test-drop-download"
141140
, "For net tests: in download, discard ALL blocks instead checking/saving them (very fast)"
@@ -821,7 +820,7 @@ namespace cryptonote
821820
return false;
822821
}
823822
//-----------------------------------------------------------------------------------------------
824-
bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed)
823+
bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed, crypto::hash& txid)
825824
{
826825
tvc = {};
827826

@@ -838,7 +837,6 @@ namespace cryptonote
838837
}
839838

840839
transaction tx;
841-
crypto::hash txid;
842840
if (!parse_and_validate_tx_from_blob(tx_blob, tx, txid))
843841
{
844842
LOG_PRINT_L1("Incoming transactions failed to parse, rejected");
@@ -1173,6 +1171,9 @@ namespace cryptonote
11731171
NOTIFY_NEW_TRANSACTIONS::request public_req{};
11741172
NOTIFY_NEW_TRANSACTIONS::request private_req{};
11751173
NOTIFY_NEW_TRANSACTIONS::request stem_req{};
1174+
std::vector<crypto::hash> public_tx_hashes{};
1175+
std::vector<crypto::hash> private_tx_hashes{};
1176+
std::vector<crypto::hash> stem_tx_hashes{};
11761177
for (auto& tx : txs)
11771178
{
11781179
switch (std::get<2>(tx))
@@ -1182,14 +1183,17 @@ namespace cryptonote
11821183
break;
11831184
case relay_method::local:
11841185
private_req.txs.push_back(std::move(std::get<1>(tx)));
1186+
private_tx_hashes.push_back(std::move(std::get<0>(tx)));
11851187
break;
11861188
case relay_method::forward:
11871189
stem_req.txs.push_back(std::move(std::get<1>(tx)));
1190+
stem_tx_hashes.push_back(std::move(std::get<0>(tx)));
11881191
break;
11891192
case relay_method::block:
11901193
case relay_method::fluff:
11911194
case relay_method::stem:
11921195
public_req.txs.push_back(std::move(std::get<1>(tx)));
1196+
public_tx_hashes.push_back(std::move(std::get<0>(tx)));
11931197
break;
11941198
}
11951199
}
@@ -1200,11 +1204,11 @@ namespace cryptonote
12001204
re-relaying public and private _should_ be acceptable here. */
12011205
const boost::uuids::uuid source = boost::uuids::nil_uuid();
12021206
if (!public_req.txs.empty())
1203-
get_protocol()->relay_transactions(public_req, source, epee::net_utils::zone::public_, relay_method::fluff);
1207+
get_protocol()->relay_transactions(public_req, std::move(public_tx_hashes), source, epee::net_utils::zone::public_, relay_method::fluff);
12041208
if (!private_req.txs.empty())
1205-
get_protocol()->relay_transactions(private_req, source, epee::net_utils::zone::invalid, relay_method::local);
1209+
get_protocol()->relay_transactions(private_req, std::move(private_tx_hashes), source, epee::net_utils::zone::invalid, relay_method::local);
12061210
if (!stem_req.txs.empty())
1207-
get_protocol()->relay_transactions(stem_req, source, epee::net_utils::zone::public_, relay_method::stem);
1211+
get_protocol()->relay_transactions(stem_req, std::move(stem_tx_hashes), source, epee::net_utils::zone::public_, relay_method::stem);
12081212
}
12091213
return true;
12101214
}
@@ -1918,9 +1922,9 @@ namespace cryptonote
19181922
m_blockchain_storage.flush_invalid_blocks();
19191923
}
19201924
//-----------------------------------------------------------------------------------------------
1921-
bool core::get_txpool_complement(std::vector<crypto::hash> hashes, std::vector<cryptonote::blobdata> &txes)
1925+
bool core::get_txpool_complement(std::vector<crypto::hash> hashes, std::vector<crypto::hash> &inv_txes)
19221926
{
1923-
return m_mempool.get_complement(std::move(hashes), txes);
1927+
return m_mempool.get_complement(std::move(hashes), inv_txes);
19241928
}
19251929
//-----------------------------------------------------------------------------------------------
19261930
bool core::update_blockchain_pruning()

src/cryptonote_core/cryptonote_core.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,11 @@ namespace cryptonote
123123
* @param tvc metadata about the transaction's validity
124124
* @param tx_relay how the transaction was received
125125
* @param relayed whether or not the transaction was relayed to us
126+
* @param txid return by reference
126127
*
127128
* @return true if the transaction was accepted, false otherwise
128129
*/
129-
bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed);
130+
bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed, crypto::hash& txid);
130131

131132
/**
132133
* @brief handles a single incoming block
@@ -905,13 +906,13 @@ namespace cryptonote
905906
void flush_invalid_blocks();
906907

907908
/**
908-
* @brief returns the set of transactions in the txpool which are not in the argument
909+
* @brief returns the set of transaction hashes in the txpool which are not in the argument
909910
*
910911
* @param hashes hashes of transactions to exclude from the result
911912
*
912913
* @return true iff success, false otherwise
913914
*/
914-
bool get_txpool_complement(std::vector<crypto::hash> hashes, std::vector<cryptonote::blobdata> &txes);
915+
bool get_txpool_complement(std::vector<crypto::hash> hashes, std::vector<crypto::hash> &inv_txes);
915916

916917
/**
917918
* @brief validates some simple properties of a transaction

src/cryptonote_core/tx_pool.cpp

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -684,43 +684,23 @@ namespace cryptonote
684684
return true;
685685
}
686686
//---------------------------------------------------------------------------------
687-
bool tx_memory_pool::get_complement(std::vector<crypto::hash> hashes, std::vector<cryptonote::blobdata> &txes) const
687+
bool tx_memory_pool::get_complement(std::vector<crypto::hash> hashes, std::vector<crypto::hash> &inv_txes) const
688688
{
689689
CRITICAL_REGION_LOCAL(m_transactions_lock);
690690
CRITICAL_REGION_LOCAL1(m_blockchain);
691691

692692
// Sort so we can do binary search later
693693
std::sort(hashes.begin(), hashes.end());
694694

695-
m_blockchain.for_all_txpool_txes([this, &hashes, &txes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*) {
696-
const auto tx_relay_method = meta.get_relay_method();
697-
if (tx_relay_method != relay_method::block && tx_relay_method != relay_method::fluff)
698-
return true;
699-
695+
m_blockchain.for_all_txpool_txes([this, &hashes, &inv_txes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*) {
700696
// Do binary search for our pool TXID in given list, skip to next if already present
701697
const auto hash_it = std::lower_bound(hashes.cbegin(), hashes.cend(), txid);
702698
if (hash_it != hashes.cend() && *hash_it == txid)
703699
return true;
704700

705-
{
706-
cryptonote::blobdata bd;
707-
try
708-
{
709-
if (!m_blockchain.get_txpool_tx_blob(txid, bd, cryptonote::relay_category::broadcasted))
710-
{
711-
MERROR("Failed to get blob for txpool transaction " << txid);
712-
return true;
713-
}
714-
txes.emplace_back(std::move(bd));
715-
}
716-
catch (const std::exception &e)
717-
{
718-
MERROR("Failed to get blob for txpool transaction " << txid << ": " << e.what());
719-
return true;
720-
}
721-
}
701+
inv_txes.push_back(txid);
722702
return true;
723-
}, false);
703+
}, false, cryptonote::relay_category::broadcasted);
724704
return true;
725705
}
726706
//---------------------------------------------------------------------------------

0 commit comments

Comments
 (0)