Adding Dandelion++ support to public networks:
- New flag in NOTIFY_NEW_TRANSACTION to indicate stem mode - Stem loops detected in tx_pool.cpp - Embargo timeout for a blackhole attack during stem phase
This commit is contained in:
parent
7c74e1919e
commit
02d887c2e5
@ -53,9 +53,7 @@ bool matches_category(relay_method method, relay_category category) noexcept
|
||||
case relay_category::all:
|
||||
return true;
|
||||
case relay_category::relayable:
|
||||
if (method == relay_method::none)
|
||||
return false;
|
||||
return true;
|
||||
return method != relay_method::none;
|
||||
case relay_category::broadcasted:
|
||||
case relay_category::legacy:
|
||||
break;
|
||||
@ -65,6 +63,7 @@ bool matches_category(relay_method method, relay_category category) noexcept
|
||||
{
|
||||
default:
|
||||
case relay_method::local:
|
||||
case relay_method::stem:
|
||||
return false;
|
||||
case relay_method::block:
|
||||
case relay_method::fluff:
|
||||
@ -80,6 +79,7 @@ void txpool_tx_meta_t::set_relay_method(relay_method method) noexcept
|
||||
kept_by_block = 0;
|
||||
do_not_relay = 0;
|
||||
is_local = 0;
|
||||
dandelionpp_stem = 0;
|
||||
|
||||
switch (method)
|
||||
{
|
||||
@ -92,6 +92,9 @@ void txpool_tx_meta_t::set_relay_method(relay_method method) noexcept
|
||||
default:
|
||||
case relay_method::fluff:
|
||||
break;
|
||||
case relay_method::stem:
|
||||
dandelionpp_stem = 1;
|
||||
break;
|
||||
case relay_method::block:
|
||||
kept_by_block = 1;
|
||||
break;
|
||||
@ -106,9 +109,26 @@ relay_method txpool_tx_meta_t::get_relay_method() const noexcept
|
||||
return relay_method::none;
|
||||
if (is_local)
|
||||
return relay_method::local;
|
||||
if (dandelionpp_stem)
|
||||
return relay_method::stem;
|
||||
return relay_method::fluff;
|
||||
}
|
||||
|
||||
bool txpool_tx_meta_t::upgrade_relay_method(relay_method method) noexcept
|
||||
{
|
||||
static_assert(relay_method::none < relay_method::local, "bad relay_method value");
|
||||
static_assert(relay_method::local < relay_method::stem, "bad relay_method value");
|
||||
static_assert(relay_method::stem < relay_method::fluff, "bad relay_method value");
|
||||
static_assert(relay_method::fluff < relay_method::block, "bad relay_method value");
|
||||
|
||||
if (get_relay_method() < method)
|
||||
{
|
||||
set_relay_method(method);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const command_line::arg_descriptor<std::string> arg_db_sync_mode = {
|
||||
"db-sync-mode"
|
||||
, "Specify sync option, using format [safe|fast|fastest]:[sync|async]:[<nblocks_per_sync>[blocks]|<nbytes_per_sync>[bytes]]."
|
||||
|
@ -160,7 +160,7 @@ struct txpool_tx_meta_t
|
||||
uint64_t max_used_block_height;
|
||||
uint64_t last_failed_height;
|
||||
uint64_t receive_time;
|
||||
uint64_t last_relayed_time;
|
||||
uint64_t last_relayed_time; //!< If Dandelion++ stem, randomized embargo timestamp. Otherwise, last relayed timestmap.
|
||||
// 112 bytes
|
||||
uint8_t kept_by_block;
|
||||
uint8_t relayed;
|
||||
@ -168,13 +168,17 @@ struct txpool_tx_meta_t
|
||||
uint8_t double_spend_seen: 1;
|
||||
uint8_t pruned: 1;
|
||||
uint8_t is_local: 1;
|
||||
uint8_t bf_padding: 5;
|
||||
uint8_t dandelionpp_stem : 1;
|
||||
uint8_t bf_padding: 4;
|
||||
|
||||
uint8_t padding[76]; // till 192 bytes
|
||||
|
||||
void set_relay_method(relay_method method) noexcept;
|
||||
relay_method get_relay_method() const noexcept;
|
||||
|
||||
//! \return True if `get_relay_method()` now returns `method`.
|
||||
bool upgrade_relay_method(relay_method method) noexcept;
|
||||
|
||||
//! See `relay_category` description
|
||||
bool matches(const relay_category category) const noexcept
|
||||
{
|
||||
|
70
src/crypto/duration.h
Normal file
70
src/crypto/duration.h
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2020, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include "crypto/crypto.h"
|
||||
|
||||
namespace crypto
|
||||
{
|
||||
//! Generate poisson distributed values in discrete `D` time units.
|
||||
template<typename D>
|
||||
struct random_poisson_duration
|
||||
{
|
||||
using result_type = D; //!< std::chrono::duration time unit precision
|
||||
using rep = typename result_type::rep; //!< Type used to represent duration value
|
||||
|
||||
//! \param average for generated durations
|
||||
explicit random_poisson_duration(result_type average)
|
||||
: dist(average.count() < 0 ? 0 : average.count())
|
||||
{}
|
||||
|
||||
//! Generate a crypto-secure random duration
|
||||
result_type operator()()
|
||||
{
|
||||
crypto::random_device rand{};
|
||||
return result_type{dist(rand)};
|
||||
}
|
||||
|
||||
private:
|
||||
std::poisson_distribution<rep> dist;
|
||||
};
|
||||
|
||||
/* A custom duration is used for subsecond precision because of the
|
||||
variance. If 5000 milliseconds is given, 95% of the values fall between
|
||||
4859ms-5141ms in 1ms increments (not enough time variance). Providing 1/4
|
||||
seconds would yield 95% of the values between 3s-7.25s in 1/4s
|
||||
increments. */
|
||||
|
||||
//! Generate random durations with 1 second precision
|
||||
using random_poisson_seconds = random_poisson_duration<std::chrono::seconds>;
|
||||
//! Generate random duration with 1/4 second precision
|
||||
using random_poisson_subseconds =
|
||||
random_poisson_duration<std::chrono::duration<std::chrono::milliseconds::rep, std::ratio<1, 4>>>;
|
||||
}
|
@ -29,6 +29,9 @@
|
||||
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cryptonote_protocol/enums.h"
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
/************************************************************************/
|
||||
@ -36,7 +39,9 @@ namespace cryptonote
|
||||
/************************************************************************/
|
||||
struct tx_verification_context
|
||||
{
|
||||
bool m_should_be_relayed;
|
||||
static_assert(unsigned(relay_method::none) == 0, "default m_relay initialization is not to relay_method::none");
|
||||
|
||||
relay_method m_relay; // gives indication on how tx should be relayed (if at all)
|
||||
bool m_verifivation_failed; //bad tx, should drop connection
|
||||
bool m_verifivation_impossible; //the transaction is related with an alternative blockchain
|
||||
bool m_added_to_pool;
|
||||
|
@ -102,7 +102,12 @@
|
||||
#define CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME 604800 //seconds, one week
|
||||
|
||||
|
||||
#define CRYPTONOTE_DANDELIONPP_FLUSH_AVERAGE 5 // seconds
|
||||
#define CRYPTONOTE_DANDELIONPP_STEMS 2 // number of outgoing stem connections per epoch
|
||||
#define CRYPTONOTE_DANDELIONPP_FLUFF_PROBABILITY 10 // out of 100
|
||||
#define CRYPTONOTE_DANDELIONPP_MIN_EPOCH 10 // minutes
|
||||
#define CRYPTONOTE_DANDELIONPP_EPOCH_RANGE 30 // seconds
|
||||
#define CRYPTONOTE_DANDELIONPP_FLUSH_AVERAGE 5 // seconds average for poisson distributed fluff flush
|
||||
#define CRYPTONOTE_DANDELIONPP_EMBARGO_AVERAGE 173 // seconds (see tx_pool.cpp for more info)
|
||||
|
||||
// see src/cryptonote_protocol/levin_notify.cpp
|
||||
#define CRYPTONOTE_NOISE_MIN_EPOCH 5 // minutes
|
||||
|
@ -1284,6 +1284,7 @@ namespace cryptonote
|
||||
break;
|
||||
case relay_method::block:
|
||||
case relay_method::fluff:
|
||||
case relay_method::stem:
|
||||
public_req.txs.push_back(std::move(std::get<1>(tx)));
|
||||
break;
|
||||
}
|
||||
@ -1295,9 +1296,9 @@ namespace cryptonote
|
||||
re-relaying public and private _should_ be acceptable here. */
|
||||
const boost::uuids::uuid source = boost::uuids::nil_uuid();
|
||||
if (!public_req.txs.empty())
|
||||
get_protocol()->relay_transactions(public_req, source, epee::net_utils::zone::public_);
|
||||
get_protocol()->relay_transactions(public_req, source, epee::net_utils::zone::public_, relay_method::fluff);
|
||||
if (!private_req.txs.empty())
|
||||
get_protocol()->relay_transactions(private_req, source, epee::net_utils::zone::invalid);
|
||||
get_protocol()->relay_transactions(private_req, source, epee::net_utils::zone::invalid, relay_method::local);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include "warnings.h"
|
||||
#include "common/perf_timer.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "crypto/duration.h"
|
||||
|
||||
#undef MONERO_DEFAULT_LOG_CATEGORY
|
||||
#define MONERO_DEFAULT_LOG_CATEGORY "txpool"
|
||||
@ -58,6 +59,29 @@ namespace cryptonote
|
||||
{
|
||||
namespace
|
||||
{
|
||||
/*! The Dandelion++ has formula for calculating the average embargo timeout:
|
||||
(-k*(k-1)*hop)/(2*log(1-ep))
|
||||
where k is the number of hops before this node and ep is the probability
|
||||
that one of the k hops hits their embargo timer, and hop is the average
|
||||
time taken between hops. So decreasing ep will make it more probable
|
||||
that "this" node is the first to expire the embargo timer. Increasing k
|
||||
will increase the number of nodes that will be "hidden" as a prior
|
||||
recipient of the tx.
|
||||
|
||||
As example, k=5 and ep=0.1 means "this" embargo timer has a 90%
|
||||
probability of being the first to expire amongst 5 nodes that saw the
|
||||
tx before "this" one. These values are independent to the fluff
|
||||
probability, but setting a low k with a low p (fluff probability) is
|
||||
not ideal since a blackhole is more likely to reveal earlier nodes in
|
||||
the chain.
|
||||
|
||||
This value was calculated with k=10, ep=0.10, and hop = 175 ms. A
|
||||
testrun from a recent Intel laptop took ~80ms to
|
||||
receive+parse+proces+send transaction. At least 50ms will be added to
|
||||
the latency if crossing an ocean. So 175ms is the fudge factor for
|
||||
a single hop with 173s being the embargo timer. */
|
||||
constexpr const std::chrono::seconds dandelionpp_embargo_average{CRYPTONOTE_DANDELIONPP_EMBARGO_AVERAGE};
|
||||
|
||||
//TODO: constants such as these should at least be in the header,
|
||||
// but probably somewhere more accessible to the rest of the
|
||||
// codebase. As it stands, it is at best nontrivial to test
|
||||
@ -262,34 +286,51 @@ namespace cryptonote
|
||||
}
|
||||
}else
|
||||
{
|
||||
//update transactions container
|
||||
meta.weight = tx_weight;
|
||||
meta.fee = fee;
|
||||
meta.max_used_block_id = max_used_block_id;
|
||||
meta.max_used_block_height = max_used_block_height;
|
||||
meta.last_failed_height = 0;
|
||||
meta.last_failed_id = null_hash;
|
||||
meta.receive_time = receive_time;
|
||||
meta.last_relayed_time = time(NULL);
|
||||
meta.relayed = relayed;
|
||||
meta.set_relay_method(tx_relay);
|
||||
meta.double_spend_seen = false;
|
||||
meta.pruned = tx.pruned;
|
||||
meta.bf_padding = 0;
|
||||
memset(meta.padding, 0, sizeof(meta.padding));
|
||||
|
||||
try
|
||||
{
|
||||
if (kept_by_block)
|
||||
m_parsed_tx_cache.insert(std::make_pair(id, tx));
|
||||
CRITICAL_REGION_LOCAL1(m_blockchain);
|
||||
LockedTXN lock(m_blockchain.get_db());
|
||||
m_blockchain.remove_txpool_tx(id);
|
||||
if (!insert_key_images(tx, id, tx_relay))
|
||||
return false;
|
||||
|
||||
m_blockchain.add_txpool_tx(id, blob, meta);
|
||||
m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id);
|
||||
const bool existing_tx = m_blockchain.get_txpool_tx_meta(id, meta);
|
||||
if (existing_tx)
|
||||
{
|
||||
/* If Dandelion++ loop. Do not use txes in the `local` state in the
|
||||
loop detection - txes in that state should be outgoing over i2p/tor
|
||||
then routed back via public dandelion++ stem. Pretend to be
|
||||
another stem node in that situation, a loop over the public
|
||||
network hasn't been hit yet. */
|
||||
if (tx_relay == relay_method::stem && meta.dandelionpp_stem)
|
||||
tx_relay = relay_method::fluff;
|
||||
}
|
||||
else
|
||||
meta.set_relay_method(relay_method::none);
|
||||
|
||||
if (meta.upgrade_relay_method(tx_relay) || !existing_tx) // synchronize with embargo timer or stem/fluff out-of-order messages
|
||||
{
|
||||
//update transactions container
|
||||
meta.last_relayed_time = std::numeric_limits<decltype(meta.last_relayed_time)>::max();
|
||||
meta.receive_time = receive_time;
|
||||
meta.weight = tx_weight;
|
||||
meta.fee = fee;
|
||||
meta.max_used_block_id = max_used_block_id;
|
||||
meta.max_used_block_height = max_used_block_height;
|
||||
meta.last_failed_height = 0;
|
||||
meta.last_failed_id = null_hash;
|
||||
meta.relayed = relayed;
|
||||
meta.double_spend_seen = false;
|
||||
meta.pruned = tx.pruned;
|
||||
meta.bf_padding = 0;
|
||||
memset(meta.padding, 0, sizeof(meta.padding));
|
||||
|
||||
if (!insert_key_images(tx, id, tx_relay))
|
||||
return false;
|
||||
|
||||
m_blockchain.remove_txpool_tx(id);
|
||||
m_blockchain.add_txpool_tx(id, blob, meta);
|
||||
m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id);
|
||||
}
|
||||
lock.commit();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
@ -299,8 +340,9 @@ namespace cryptonote
|
||||
}
|
||||
tvc.m_added_to_pool = true;
|
||||
|
||||
if(meta.fee > 0 && tx_relay != relay_method::none)
|
||||
tvc.m_should_be_relayed = true;
|
||||
static_assert(unsigned(relay_method::none) == 0, "expected relay_method::none value to be zero");
|
||||
if(meta.fee > 0)
|
||||
tvc.m_relay = tx_relay;
|
||||
}
|
||||
|
||||
tvc.m_verifivation_failed = false;
|
||||
@ -553,7 +595,7 @@ namespace cryptonote
|
||||
td.last_failed_height = meta.last_failed_height;
|
||||
td.last_failed_id = meta.last_failed_id;
|
||||
td.receive_time = meta.receive_time;
|
||||
td.last_relayed_time = meta.last_relayed_time;
|
||||
td.last_relayed_time = meta.dandelionpp_stem ? 0 : meta.last_relayed_time;
|
||||
td.relayed = meta.relayed;
|
||||
td.do_not_relay = meta.do_not_relay;
|
||||
td.double_spend_seen = meta.double_spend_seen;
|
||||
@ -686,8 +728,13 @@ namespace cryptonote
|
||||
txs.reserve(m_blockchain.get_txpool_tx_count());
|
||||
m_blockchain.for_all_txpool_txes([this, now, &txs](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *){
|
||||
// 0 fee transactions are never relayed
|
||||
if(!meta.pruned && meta.fee > 0 && !meta.do_not_relay && now - meta.last_relayed_time > get_relay_delay(now, meta.receive_time))
|
||||
if(!meta.pruned && meta.fee > 0 && !meta.do_not_relay)
|
||||
{
|
||||
if (!meta.dandelionpp_stem && now - meta.last_relayed_time <= get_relay_delay(now, meta.receive_time))
|
||||
return true;
|
||||
if (meta.dandelionpp_stem && meta.last_relayed_time < now) // for dandelion++ stem, this value is the embargo timeout
|
||||
return true;
|
||||
|
||||
// if the tx is older than half the max lifetime, we don't re-relay it, to avoid a problem
|
||||
// mentioned by smooth where nodes would flush txes at slightly different times, causing
|
||||
// flushed txes to be re-added when received from a node which was just about to flush it
|
||||
@ -712,9 +759,11 @@ namespace cryptonote
|
||||
//---------------------------------------------------------------------------------
|
||||
void tx_memory_pool::set_relayed(const epee::span<const crypto::hash> hashes, const relay_method method)
|
||||
{
|
||||
crypto::random_poisson_seconds embargo_duration{dandelionpp_embargo_average};
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
|
||||
CRITICAL_REGION_LOCAL(m_transactions_lock);
|
||||
CRITICAL_REGION_LOCAL1(m_blockchain);
|
||||
const time_t now = time(NULL);
|
||||
LockedTXN lock(m_blockchain.get_db());
|
||||
for (const auto& hash : hashes)
|
||||
{
|
||||
@ -723,9 +772,15 @@ namespace cryptonote
|
||||
txpool_tx_meta_t meta;
|
||||
if (m_blockchain.get_txpool_tx_meta(hash, meta))
|
||||
{
|
||||
// txes can be received as "stem" or "fluff" in either order
|
||||
meta.upgrade_relay_method(method);
|
||||
meta.relayed = true;
|
||||
meta.last_relayed_time = now;
|
||||
meta.set_relay_method(method);
|
||||
|
||||
if (meta.dandelionpp_stem)
|
||||
meta.last_relayed_time = std::chrono::system_clock::to_time_t(now + embargo_duration());
|
||||
else
|
||||
meta.last_relayed_time = std::chrono::system_clock::to_time_t(now);
|
||||
|
||||
m_blockchain.update_txpool_tx(hash, meta);
|
||||
}
|
||||
}
|
||||
@ -910,7 +965,7 @@ namespace cryptonote
|
||||
txi.receive_time = include_sensitive_data ? meta.receive_time : 0;
|
||||
txi.relayed = meta.relayed;
|
||||
// In restricted mode we do not include this data:
|
||||
txi.last_relayed_time = include_sensitive_data ? meta.last_relayed_time : 0;
|
||||
txi.last_relayed_time = (include_sensitive_data && !meta.dandelionpp_stem) ? meta.last_relayed_time : 0;
|
||||
txi.do_not_relay = meta.do_not_relay;
|
||||
txi.double_spend_seen = meta.double_spend_seen;
|
||||
tx_infos.push_back(std::move(txi));
|
||||
@ -962,7 +1017,7 @@ namespace cryptonote
|
||||
txi.last_failed_block_hash = meta.last_failed_id;
|
||||
txi.receive_time = meta.receive_time;
|
||||
txi.relayed = meta.relayed;
|
||||
txi.last_relayed_time = meta.last_relayed_time;
|
||||
txi.last_relayed_time = meta.dandelionpp_stem ? 0 : meta.last_relayed_time;
|
||||
txi.do_not_relay = meta.do_not_relay;
|
||||
txi.double_spend_seen = meta.double_spend_seen;
|
||||
tx_infos.push_back(txi);
|
||||
|
@ -197,10 +197,12 @@ namespace cryptonote
|
||||
{
|
||||
std::vector<blobdata> txs;
|
||||
std::string _; // padding
|
||||
bool dandelionpp_fluff; //zero initialization defaults to stem mode
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(txs)
|
||||
KV_SERIALIZE(_)
|
||||
KV_SERIALIZE_OPT(dandelionpp_fluff, true) // backwards compatible mode is fluff
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
typedef epee::misc_utils::struct_init<request_t> request;
|
||||
|
@ -129,7 +129,7 @@ namespace cryptonote
|
||||
|
||||
//----------------- i_bc_protocol_layout ---------------------------------------
|
||||
virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context);
|
||||
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone);
|
||||
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone, relay_method tx_relay);
|
||||
//----------------------------------------------------------------------------------
|
||||
//bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, cryptonote_connection_context& context);
|
||||
bool should_drop_connection(cryptonote_connection_context& context, uint32_t next_stripe);
|
||||
|
@ -926,29 +926,60 @@ namespace cryptonote
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<cryptonote::blobdata> newtxs;
|
||||
newtxs.reserve(arg.txs.size());
|
||||
for (size_t i = 0; i < arg.txs.size(); ++i)
|
||||
relay_method tx_relay;
|
||||
std::vector<blobdata> stem_txs{};
|
||||
std::vector<blobdata> fluff_txs{};
|
||||
if (arg.dandelionpp_fluff)
|
||||
{
|
||||
cryptonote::tx_verification_context tvc{};
|
||||
m_core.handle_incoming_tx({arg.txs[i], crypto::null_hash}, tvc, relay_method::fluff, true);
|
||||
if(tvc.m_verifivation_failed)
|
||||
tx_relay = relay_method::fluff;
|
||||
fluff_txs.reserve(arg.txs.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
tx_relay = relay_method::stem;
|
||||
stem_txs.reserve(arg.txs.size());
|
||||
}
|
||||
|
||||
for (auto& tx : arg.txs)
|
||||
{
|
||||
tx_verification_context tvc{};
|
||||
if (!m_core.handle_incoming_tx({tx, crypto::null_hash}, tvc, tx_relay, true))
|
||||
{
|
||||
LOG_PRINT_CCONTEXT_L1("Tx verification failed, dropping connection");
|
||||
drop_connection(context, false, false);
|
||||
return 1;
|
||||
}
|
||||
if(tvc.m_should_be_relayed)
|
||||
newtxs.push_back(std::move(arg.txs[i]));
|
||||
}
|
||||
arg.txs = std::move(newtxs);
|
||||
|
||||
if(arg.txs.size())
|
||||
switch (tvc.m_relay)
|
||||
{
|
||||
case relay_method::local:
|
||||
case relay_method::stem:
|
||||
stem_txs.push_back(std::move(tx));
|
||||
break;
|
||||
case relay_method::block:
|
||||
case relay_method::fluff:
|
||||
fluff_txs.push_back(std::move(tx));
|
||||
break;
|
||||
default:
|
||||
case relay_method::none:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!stem_txs.empty())
|
||||
{
|
||||
//TODO: add announce usage here
|
||||
relay_transactions(arg, context.m_connection_id, context.m_remote_address.get_zone());
|
||||
arg.dandelionpp_fluff = false;
|
||||
arg.txs = std::move(stem_txs);
|
||||
relay_transactions(arg, context.m_connection_id, context.m_remote_address.get_zone(), relay_method::stem);
|
||||
}
|
||||
if (!fluff_txs.empty())
|
||||
{
|
||||
//TODO: add announce usage here
|
||||
arg.dandelionpp_fluff = true;
|
||||
arg.txs = std::move(fluff_txs);
|
||||
relay_transactions(arg, context.m_connection_id, context.m_remote_address.get_zone(), relay_method::fluff);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------
|
||||
@ -2387,14 +2418,14 @@ skip:
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------
|
||||
template<class t_core>
|
||||
bool t_cryptonote_protocol_handler<t_core>::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone)
|
||||
bool t_cryptonote_protocol_handler<t_core>::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone, relay_method tx_relay)
|
||||
{
|
||||
/* Push all outgoing transactions to this function. The behavior needs to
|
||||
identify how the transaction is going to be relayed, and then update the
|
||||
local mempool before doing the relay. The code was already updating the
|
||||
DB twice on received transactions - it is difficult to workaround this
|
||||
due to the internal design. */
|
||||
return m_p2p->send_txs(std::move(arg.txs), zone, source, m_core) != epee::net_utils::zone::invalid;
|
||||
return m_p2p->send_txs(std::move(arg.txs), zone, source, m_core, tx_relay) != epee::net_utils::zone::invalid;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------
|
||||
template<class t_core>
|
||||
|
@ -41,7 +41,7 @@ namespace cryptonote
|
||||
struct i_cryptonote_protocol
|
||||
{
|
||||
virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context)=0;
|
||||
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone)=0;
|
||||
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone, relay_method tx_relay)=0;
|
||||
//virtual bool request_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context)=0;
|
||||
};
|
||||
|
||||
@ -54,7 +54,7 @@ namespace cryptonote
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone)
|
||||
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone, relay_method tx_relay)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -37,7 +37,8 @@ namespace cryptonote
|
||||
{
|
||||
none = 0, //!< Received via RPC with `do_not_relay` set
|
||||
local, //!< Received via RPC; trying to send over i2p/tor, etc.
|
||||
block, //!< Received in block, takes precedence over others
|
||||
fluff //!< Received/sent over public networks
|
||||
stem, //!< Received/send over network using Dandelion++ stem
|
||||
fluff, //!< Received/sent over network using Dandelion++ fluff
|
||||
block //!< Received in block, takes precedence over others
|
||||
};
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <stdexcept>
|
||||
@ -38,8 +39,10 @@
|
||||
#include "common/expect.h"
|
||||
#include "common/varint.h"
|
||||
#include "cryptonote_config.h"
|
||||
#include "crypto/random.h"
|
||||
#include "crypto/crypto.h"
|
||||
#include "crypto/duration.h"
|
||||
#include "cryptonote_basic/connection_context.h"
|
||||
#include "cryptonote_core/i_core_events.h"
|
||||
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
|
||||
#include "net/dandelionpp.h"
|
||||
#include "p2p/net_node.h"
|
||||
@ -61,11 +64,14 @@ namespace levin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr std::size_t connection_id_reserve_size = 100;
|
||||
constexpr const std::size_t connection_id_reserve_size = 100;
|
||||
|
||||
constexpr const std::chrono::minutes noise_min_epoch{CRYPTONOTE_NOISE_MIN_EPOCH};
|
||||
constexpr const std::chrono::seconds noise_epoch_range{CRYPTONOTE_NOISE_EPOCH_RANGE};
|
||||
|
||||
constexpr const std::chrono::minutes dandelionpp_min_epoch{CRYPTONOTE_DANDELIONPP_MIN_EPOCH};
|
||||
constexpr const std::chrono::seconds dandelionpp_epoch_range{CRYPTONOTE_DANDELIONPP_EPOCH_RANGE};
|
||||
|
||||
constexpr const std::chrono::seconds noise_min_delay{CRYPTONOTE_NOISE_MIN_DELAY};
|
||||
constexpr const std::chrono::seconds noise_delay_range{CRYPTONOTE_NOISE_DELAY_RANGE};
|
||||
|
||||
@ -83,22 +89,8 @@ namespace levin
|
||||
connections (Dandelion++ makes similar assumptions in its stem
|
||||
algorithm). The randomization yields 95% values between 1s-4s in
|
||||
1/4s increments. */
|
||||
constexpr const fluff_stepsize fluff_average_out{fluff_stepsize{fluff_average_in} / 2};
|
||||
|
||||
class random_poisson
|
||||
{
|
||||
std::poisson_distribution<fluff_stepsize::rep> dist;
|
||||
public:
|
||||
explicit random_poisson(fluff_stepsize average)
|
||||
: dist(average.count() < 0 ? 0 : average.count())
|
||||
{}
|
||||
|
||||
fluff_stepsize operator()()
|
||||
{
|
||||
crypto::random_device rand{};
|
||||
return fluff_stepsize{dist(rand)};
|
||||
}
|
||||
};
|
||||
using fluff_duration = crypto::random_poisson_subseconds::result_type;
|
||||
constexpr const fluff_duration fluff_average_out{fluff_duration{fluff_average_in} / 2};
|
||||
|
||||
/*! Select a randomized duration from 0 to `range`. The precision will be to
|
||||
the systems `steady_clock`. As an example, supplying 3 seconds to this
|
||||
@ -132,10 +124,11 @@ namespace levin
|
||||
return outs;
|
||||
}
|
||||
|
||||
std::string make_tx_payload(std::vector<blobdata>&& txs, const bool pad)
|
||||
std::string make_tx_payload(std::vector<blobdata>&& txs, const bool pad, const bool fluff)
|
||||
{
|
||||
NOTIFY_NEW_TRANSACTIONS::request request{};
|
||||
request.txs = std::move(txs);
|
||||
request.dandelionpp_fluff = fluff;
|
||||
|
||||
if (pad)
|
||||
{
|
||||
@ -172,9 +165,9 @@ namespace levin
|
||||
return fullBlob;
|
||||
}
|
||||
|
||||
bool make_payload_send_txs(connections& p2p, std::vector<blobdata>&& txs, const boost::uuids::uuid& destination, const bool pad)
|
||||
bool make_payload_send_txs(connections& p2p, std::vector<blobdata>&& txs, const boost::uuids::uuid& destination, const bool pad, const bool fluff)
|
||||
{
|
||||
const cryptonote::blobdata blob = make_tx_payload(std::move(txs), pad);
|
||||
const cryptonote::blobdata blob = make_tx_payload(std::move(txs), pad, fluff);
|
||||
p2p.for_connection(destination, [&blob](detail::p2p_context& context) {
|
||||
on_levin_traffic(context, true, true, false, blob.size(), get_command_from_message(blob));
|
||||
return true;
|
||||
@ -251,7 +244,8 @@ namespace levin
|
||||
flush_time(std::chrono::steady_clock::time_point::max()),
|
||||
connection_count(0),
|
||||
is_public(is_public),
|
||||
pad_txs(pad_txs)
|
||||
pad_txs(pad_txs),
|
||||
fluffing(false)
|
||||
{
|
||||
for (std::size_t count = 0; !noise.empty() && count < CRYPTONOTE_NOISE_CHANNELS; ++count)
|
||||
channels.emplace_back(io_service);
|
||||
@ -268,6 +262,7 @@ namespace levin
|
||||
std::atomic<std::size_t> connection_count; //!< Only update in strand, can be read at any time
|
||||
const bool is_public; //!< Zone is public ipv4/ipv6 connections
|
||||
const bool pad_txs; //!< Pad txs to the next boundary for privacy
|
||||
bool fluffing; //!< Zone is in Dandelion++ fluff epoch
|
||||
};
|
||||
} // detail
|
||||
|
||||
@ -362,10 +357,11 @@ namespace levin
|
||||
return true;
|
||||
});
|
||||
|
||||
// Always send txs in stem mode over i2p/tor, see comments in `send_txs` below.
|
||||
for (auto& connection : connections)
|
||||
{
|
||||
std::sort(connection.first.begin(), connection.first.end()); // don't leak receive order
|
||||
make_payload_send_txs(*zone_->p2p, std::move(connection.first), connection.second, zone_->pad_txs);
|
||||
make_payload_send_txs(*zone_->p2p, std::move(connection.first), connection.second, zone_->pad_txs, zone_->is_public);
|
||||
}
|
||||
|
||||
if (next_flush != std::chrono::steady_clock::time_point::max())
|
||||
@ -387,29 +383,38 @@ namespace levin
|
||||
|
||||
void operator()()
|
||||
{
|
||||
if (!zone_ || !zone_->p2p || txs_.empty())
|
||||
run(std::move(zone_), epee::to_span(txs_), source_);
|
||||
}
|
||||
|
||||
static void run(std::shared_ptr<detail::zone> zone, epee::span<const blobdata> txs, const boost::uuids::uuid& source)
|
||||
{
|
||||
if (!zone || !zone->p2p || txs.empty())
|
||||
return;
|
||||
|
||||
assert(zone_->strand.running_in_this_thread());
|
||||
assert(zone->strand.running_in_this_thread());
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
auto next_flush = std::chrono::steady_clock::time_point::max();
|
||||
|
||||
random_poisson in_duration(fluff_average_in);
|
||||
random_poisson out_duration(fluff_average_out);
|
||||
crypto::random_poisson_subseconds in_duration(fluff_average_in);
|
||||
crypto::random_poisson_subseconds out_duration(fluff_average_out);
|
||||
|
||||
|
||||
MDEBUG("Queueing " << txs.size() << " transaction(s) for Dandelion++ fluffing");
|
||||
|
||||
bool available = false;
|
||||
zone_->p2p->foreach_connection([this, now, &in_duration, &out_duration, &next_flush, &available] (detail::p2p_context& context)
|
||||
zone->p2p->foreach_connection([txs, now, &zone, &source, &in_duration, &out_duration, &next_flush, &available] (detail::p2p_context& context)
|
||||
{
|
||||
if (this->source_ != context.m_connection_id && (this->zone_->is_public || !context.m_is_income))
|
||||
// When i2p/tor, only fluff to outbound connections
|
||||
if (source != context.m_connection_id && (zone->is_public || !context.m_is_income))
|
||||
{
|
||||
available = true;
|
||||
if (context.fluff_txs.empty())
|
||||
context.flush_time = now + (context.m_is_income ? in_duration() : out_duration());
|
||||
|
||||
next_flush = std::min(next_flush, context.flush_time);
|
||||
context.fluff_txs.reserve(context.fluff_txs.size() + this->txs_.size());
|
||||
for (const blobdata& tx : this->txs_)
|
||||
context.fluff_txs.reserve(context.fluff_txs.size() + txs.size());
|
||||
for (const blobdata& tx : txs)
|
||||
context.fluff_txs.push_back(tx); // must copy instead of move (multiple conns)
|
||||
}
|
||||
return true;
|
||||
@ -418,8 +423,8 @@ namespace levin
|
||||
if (!available)
|
||||
MWARNING("Unable to send transaction(s), no available connections");
|
||||
|
||||
if (next_flush < zone_->flush_time)
|
||||
fluff_flush::queue(std::move(zone_), next_flush);
|
||||
if (next_flush < zone->flush_time)
|
||||
fluff_flush::queue(std::move(zone), next_flush);
|
||||
}
|
||||
};
|
||||
|
||||
@ -471,6 +476,11 @@ namespace levin
|
||||
assert(zone->strand.running_in_this_thread());
|
||||
|
||||
zone->connection_count = zone->map.size();
|
||||
|
||||
// only noise uses the "noise channels", only update when enabled
|
||||
if (zone->noise.empty())
|
||||
return;
|
||||
|
||||
for (auto id = zone->map.begin(); id != zone->map.end(); ++id)
|
||||
{
|
||||
const std::size_t i = id - zone->map.begin();
|
||||
@ -479,26 +489,75 @@ namespace levin
|
||||
}
|
||||
|
||||
//! \pre Called within `zone_->strand`.
|
||||
void operator()()
|
||||
static void run(std::shared_ptr<detail::zone> zone, std::vector<boost::uuids::uuid> out_connections)
|
||||
{
|
||||
if (!zone_)
|
||||
if (!zone)
|
||||
return;
|
||||
|
||||
assert(zone_->strand.running_in_this_thread());
|
||||
if (zone_->map.update(std::move(out_connections_)))
|
||||
post(std::move(zone_));
|
||||
assert(zone->strand.running_in_this_thread());
|
||||
if (zone->map.update(std::move(out_connections)))
|
||||
post(std::move(zone));
|
||||
}
|
||||
|
||||
//! \pre Called within `zone_->strand`.
|
||||
void operator()()
|
||||
{
|
||||
run(std::move(zone_), std::move(out_connections_));
|
||||
}
|
||||
};
|
||||
|
||||
//! Swaps out noise channels entirely; new epoch start.
|
||||
//! Checks fluff status for this node, and then does stem or fluff for txes
|
||||
struct dandelionpp_notify
|
||||
{
|
||||
std::shared_ptr<detail::zone> zone_;
|
||||
i_core_events* core_;
|
||||
std::vector<blobdata> txs_;
|
||||
boost::uuids::uuid source_;
|
||||
|
||||
//! \pre Called in `zone_->strand`
|
||||
void operator()()
|
||||
{
|
||||
if (!zone_ || !core_ || txs_.empty())
|
||||
return;
|
||||
|
||||
if (zone_->fluffing)
|
||||
{
|
||||
core_->on_transactions_relayed(epee::to_span(txs_), relay_method::fluff);
|
||||
fluff_notify::run(std::move(zone_), epee::to_span(txs_), source_);
|
||||
}
|
||||
else // forward tx in stem
|
||||
{
|
||||
core_->on_transactions_relayed(epee::to_span(txs_), relay_method::stem);
|
||||
for (int tries = 2; 0 < tries; tries--)
|
||||
{
|
||||
const boost::uuids::uuid destination = zone_->map.get_stem(source_);
|
||||
if (!destination.is_nil() && make_payload_send_txs(*zone_->p2p, std::vector<blobdata>{txs_}, destination, zone_->pad_txs, false))
|
||||
{
|
||||
/* Source is intentionally omitted in debug log for privacy - a
|
||||
nil uuid indicates source is that node. */
|
||||
MDEBUG("Sent " << txs_.size() << " transaction(s) to " << destination << " using Dandelion++ stem");
|
||||
return;
|
||||
}
|
||||
|
||||
// connection list may be outdated, try again
|
||||
update_channels::run(zone_, get_out_connections(*zone_->p2p));
|
||||
}
|
||||
|
||||
MERROR("Unable to send transaction(s) via Dandelion++ stem");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//! Swaps out noise/dandelionpp channels entirely; new epoch start.
|
||||
class change_channels
|
||||
{
|
||||
std::shared_ptr<detail::zone> zone_;
|
||||
net::dandelionpp::connection_map map_; // Requires manual copy constructor
|
||||
bool fluffing_;
|
||||
|
||||
public:
|
||||
explicit change_channels(std::shared_ptr<detail::zone> zone, net::dandelionpp::connection_map map)
|
||||
: zone_(std::move(zone)), map_(std::move(map))
|
||||
explicit change_channels(std::shared_ptr<detail::zone> zone, net::dandelionpp::connection_map map, const bool fluffing)
|
||||
: zone_(std::move(zone)), map_(std::move(map)), fluffing_(fluffing)
|
||||
{}
|
||||
|
||||
change_channels(change_channels&&) = default;
|
||||
@ -510,11 +569,15 @@ namespace levin
|
||||
void operator()()
|
||||
{
|
||||
if (!zone_)
|
||||
return
|
||||
return;
|
||||
|
||||
assert(zone_->strand.running_in_this_thread());
|
||||
|
||||
if (zone_->is_public)
|
||||
MDEBUG("Starting new Dandelion++ epoch: " << (fluffing_ ? "fluff" : "stem"));
|
||||
|
||||
zone_->map = std::move(map_);
|
||||
zone_->fluffing = fluffing_;
|
||||
update_channels::post(std::move(zone_));
|
||||
}
|
||||
};
|
||||
@ -608,9 +671,10 @@ namespace levin
|
||||
if (error && error != boost::system::errc::operation_canceled)
|
||||
throw boost::system::system_error{error, "start_epoch timer failed"};
|
||||
|
||||
const bool fluffing = crypto::rand_idx(unsigned(100)) < CRYPTONOTE_DANDELIONPP_FLUFF_PROBABILITY;
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
zone_->strand.dispatch(
|
||||
change_channels{zone_, net::dandelionpp::connection_map{get_out_connections(*(zone_->p2p)), count_}}
|
||||
change_channels{zone_, net::dandelionpp::connection_map{get_out_connections(*(zone_->p2p)), count_}, fluffing}
|
||||
);
|
||||
|
||||
detail::zone& alias = *zone_;
|
||||
@ -626,10 +690,16 @@ namespace levin
|
||||
if (!zone_->p2p)
|
||||
throw std::logic_error{"cryptonote::levin::notify cannot have nullptr p2p argument"};
|
||||
|
||||
if (!zone_->noise.empty())
|
||||
const bool noise_enabled = !zone_->noise.empty();
|
||||
if (noise_enabled || is_public)
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
start_epoch{zone_, noise_min_epoch, noise_epoch_range, CRYPTONOTE_NOISE_CHANNELS}();
|
||||
const auto min_epoch = noise_enabled ? noise_min_epoch : dandelionpp_min_epoch;
|
||||
const auto epoch_range = noise_enabled ? noise_epoch_range : dandelionpp_epoch_range;
|
||||
const std::size_t out_count = noise_enabled ? CRYPTONOTE_NOISE_CHANNELS : CRYPTONOTE_DANDELIONPP_STEMS;
|
||||
|
||||
start_epoch{zone_, min_epoch, epoch_range, out_count}();
|
||||
|
||||
for (std::size_t channel = 0; channel < zone_->channels.size(); ++channel)
|
||||
send_noise::wait(now, zone_, channel);
|
||||
}
|
||||
@ -679,7 +749,7 @@ namespace levin
|
||||
zone_->flush_txs.cancel();
|
||||
}
|
||||
|
||||
bool notify::send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source)
|
||||
bool notify::send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source, i_core_events& core, relay_method tx_relay)
|
||||
{
|
||||
if (txs.empty())
|
||||
return true;
|
||||
@ -687,6 +757,17 @@ namespace levin
|
||||
if (!zone_)
|
||||
return false;
|
||||
|
||||
/* If noise is enabled in a zone, it always takes precedence. The technique
|
||||
provides good protection against ISP adversaries, but not sybil
|
||||
adversaries. Noise is currently only enabled over I2P/Tor - those
|
||||
networks provide protection against sybil attacks (we only send to
|
||||
outgoing connections).
|
||||
|
||||
If noise is disabled, Dandelion++ is used for public networks only.
|
||||
Dandelion++ over I2P/Tor should be an interesting case to investigate,
|
||||
but the mempool/stempool needs to know the zone a tx originated from to
|
||||
work properly. */
|
||||
|
||||
if (!zone_->noise.empty() && !zone_->channels.empty())
|
||||
{
|
||||
// covert send in "noise" channel
|
||||
@ -694,8 +775,17 @@ namespace levin
|
||||
CRYPTONOTE_MAX_FRAGMENTS * CRYPTONOTE_NOISE_BYTES <= LEVIN_DEFAULT_MAX_PACKET_SIZE, "most nodes will reject this fragment setting"
|
||||
);
|
||||
|
||||
// padding is not useful when using noise mode
|
||||
const std::string payload = make_tx_payload(std::move(txs), false);
|
||||
if (tx_relay == relay_method::stem)
|
||||
{
|
||||
MWARNING("Dandelion++ stem not supported over noise networks");
|
||||
tx_relay = relay_method::local; // do not put into stempool embargo (hopefully not there already!).
|
||||
}
|
||||
|
||||
core.on_transactions_relayed(epee::to_span(txs), tx_relay);
|
||||
|
||||
// Padding is not useful when using noise mode. Send as stem so receiver
|
||||
// forwards in Dandelion++ mode.
|
||||
const std::string payload = make_tx_payload(std::move(txs), false, false);
|
||||
epee::byte_slice message = epee::levin::make_fragmented_notify(
|
||||
zone_->noise, NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan<std::uint8_t>(payload)
|
||||
);
|
||||
@ -714,9 +804,31 @@ namespace levin
|
||||
}
|
||||
else
|
||||
{
|
||||
zone_->strand.dispatch(fluff_notify{zone_, std::move(txs), source});
|
||||
switch (tx_relay)
|
||||
{
|
||||
default:
|
||||
case relay_method::none:
|
||||
case relay_method::block:
|
||||
return false;
|
||||
case relay_method::stem:
|
||||
tx_relay = relay_method::fluff; // don't set stempool embargo when skipping to fluff
|
||||
/* fallthrough */
|
||||
case relay_method::local:
|
||||
if (zone_->is_public)
|
||||
{
|
||||
// this will change a local tx to stem or fluff ...
|
||||
zone_->strand.dispatch(
|
||||
dandelionpp_notify{zone_, std::addressof(core), std::move(txs), source}
|
||||
);
|
||||
break;
|
||||
}
|
||||
/* fallthrough */
|
||||
case relay_method::fluff:
|
||||
core.on_transactions_relayed(epee::to_span(txs), tx_relay);
|
||||
zone_->strand.dispatch(fluff_notify{zone_, std::move(txs), source});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // levin
|
||||
|
@ -35,6 +35,7 @@
|
||||
|
||||
#include "byte_slice.h"
|
||||
#include "cryptonote_basic/blobdatatype.h"
|
||||
#include "cryptonote_protocol/enums.h"
|
||||
#include "cryptonote_protocol/fwd.h"
|
||||
#include "net/enums.h"
|
||||
#include "span.h"
|
||||
@ -122,7 +123,7 @@ namespace levin
|
||||
particular stem.
|
||||
|
||||
\return True iff the notification is queued for sending. */
|
||||
bool send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source);
|
||||
bool send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source, i_core_events& core, relay_method tx_relay);
|
||||
};
|
||||
} // levin
|
||||
} // net
|
||||
|
@ -334,7 +334,7 @@ namespace nodetool
|
||||
virtual void callback(p2p_connection_context& context);
|
||||
//----------------- i_p2p_endpoint -------------------------------------------------------------
|
||||
virtual bool relay_notify_to_list(int command, const epee::span<const uint8_t> data_buff, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections);
|
||||
virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core);
|
||||
virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core, cryptonote::relay_method tx_relay);
|
||||
virtual bool invoke_command_to_peer(int command, const epee::span<const uint8_t> req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context);
|
||||
virtual bool invoke_notify_to_peer(int command, const epee::span<const uint8_t> req_buff, const epee::net_utils::connection_context_base& context);
|
||||
virtual bool drop_connection(const epee::net_utils::connection_context_base& context);
|
||||
|
@ -1975,18 +1975,13 @@ namespace nodetool
|
||||
}
|
||||
//-----------------------------------------------------------------------------------
|
||||
template<class t_payload_net_handler>
|
||||
epee::net_utils::zone node_server<t_payload_net_handler>::send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core)
|
||||
epee::net_utils::zone node_server<t_payload_net_handler>::send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core, const cryptonote::relay_method tx_relay)
|
||||
{
|
||||
namespace enet = epee::net_utils;
|
||||
|
||||
const auto send = [&txs, &source, &core] (std::pair<const enet::zone, network_zone>& network)
|
||||
const auto send = [&txs, &source, &core, tx_relay] (std::pair<const enet::zone, network_zone>& network)
|
||||
{
|
||||
const bool is_public = (network.first == enet::zone::public_);
|
||||
const cryptonote::relay_method tx_relay = is_public ?
|
||||
cryptonote::relay_method::fluff : cryptonote::relay_method::local;
|
||||
|
||||
core.on_transactions_relayed(epee::to_span(txs), tx_relay);
|
||||
if (network.second.m_notifier.send_txs(std::move(txs), source))
|
||||
if (network.second.m_notifier.send_txs(std::move(txs), source, core, tx_relay))
|
||||
return network.first;
|
||||
return enet::zone::invalid;
|
||||
};
|
||||
|
@ -50,7 +50,7 @@ namespace nodetool
|
||||
struct i_p2p_endpoint
|
||||
{
|
||||
virtual bool relay_notify_to_list(int command, const epee::span<const uint8_t> data_buff, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections)=0;
|
||||
virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core)=0;
|
||||
virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core, cryptonote::relay_method tx_relay)=0;
|
||||
virtual bool invoke_command_to_peer(int command, const epee::span<const uint8_t> req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context)=0;
|
||||
virtual bool invoke_notify_to_peer(int command, const epee::span<const uint8_t> req_buff, const epee::net_utils::connection_context_base& context)=0;
|
||||
virtual bool drop_connection(const epee::net_utils::connection_context_base& context)=0;
|
||||
@ -75,7 +75,7 @@ namespace nodetool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core)
|
||||
virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core, cryptonote::relay_method tx_relay)
|
||||
{
|
||||
return epee::net_utils::zone::invalid;
|
||||
}
|
||||
|
@ -1152,7 +1152,7 @@ namespace cryptonote
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!tvc.m_should_be_relayed)
|
||||
if(tvc.m_relay == relay_method::none)
|
||||
{
|
||||
LOG_PRINT_L0("[on_send_raw_tx]: tx accepted, but not relayed");
|
||||
res.reason = "Not relayed";
|
||||
@ -1162,8 +1162,8 @@ namespace cryptonote
|
||||
}
|
||||
|
||||
NOTIFY_NEW_TRANSACTIONS::request r;
|
||||
r.txs.push_back(tx_blob);
|
||||
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid);
|
||||
r.txs.push_back(std::move(tx_blob));
|
||||
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid, relay_method::local);
|
||||
//TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes
|
||||
res.status = CORE_RPC_STATUS_OK;
|
||||
return true;
|
||||
@ -2776,8 +2776,8 @@ namespace cryptonote
|
||||
if (!m_core.get_pool_transaction(txid, txblob, relay_category::legacy))
|
||||
{
|
||||
NOTIFY_NEW_TRANSACTIONS::request r;
|
||||
r.txs.push_back(txblob);
|
||||
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid);
|
||||
r.txs.push_back(std::move(txblob));
|
||||
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid, relay_method::local);
|
||||
//TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes
|
||||
}
|
||||
else
|
||||
|
@ -349,10 +349,10 @@ namespace rpc
|
||||
res.error_details = "Invalid hex";
|
||||
return;
|
||||
}
|
||||
handleTxBlob(tx_blob, req.relay, res);
|
||||
handleTxBlob(std::move(tx_blob), req.relay, res);
|
||||
}
|
||||
|
||||
void DaemonHandler::handleTxBlob(const std::string& tx_blob, bool relay, SendRawTx::Response& res)
|
||||
void DaemonHandler::handleTxBlob(std::string&& tx_blob, bool relay, SendRawTx::Response& res)
|
||||
{
|
||||
if (!m_p2p.get_payload_object().is_synchronized())
|
||||
{
|
||||
@ -423,7 +423,7 @@ namespace rpc
|
||||
return;
|
||||
}
|
||||
|
||||
if(!tvc.m_should_be_relayed || !relay)
|
||||
if(tvc.m_relay == relay_method::none || !relay)
|
||||
{
|
||||
MERROR("[SendRawTx]: tx accepted, but not relayed");
|
||||
res.error_details = "Not relayed";
|
||||
@ -434,8 +434,8 @@ namespace rpc
|
||||
}
|
||||
|
||||
NOTIFY_NEW_TRANSACTIONS::request r;
|
||||
r.txs.push_back(tx_blob);
|
||||
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid);
|
||||
r.txs.push_back(std::move(tx_blob));
|
||||
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid, relay_method::local);
|
||||
|
||||
//TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes
|
||||
res.status = Message::STATUS_OK;
|
||||
|
@ -138,7 +138,7 @@ class DaemonHandler : public RpcHandler
|
||||
|
||||
bool getBlockHeaderByHash(const crypto::hash& hash_in, cryptonote::rpc::BlockHeaderResponse& response);
|
||||
|
||||
void handleTxBlob(const std::string& tx_blob, bool relay, SendRawTx::Response& res);
|
||||
void handleTxBlob(std::string&& tx_blob, bool relay, SendRawTx::Response& res);
|
||||
|
||||
cryptonote::core& m_core;
|
||||
t_p2p& m_p2p;
|
||||
|
@ -116,7 +116,8 @@ struct event_visitor_settings
|
||||
{
|
||||
set_txs_keeped_by_block = 1 << 0,
|
||||
set_txs_do_not_relay = 1 << 1,
|
||||
set_local_relay = 1 << 2
|
||||
set_local_relay = 1 << 2,
|
||||
set_txs_stem = 1 << 3
|
||||
};
|
||||
|
||||
event_visitor_settings(int a_mask = 0)
|
||||
@ -548,6 +549,10 @@ public:
|
||||
{
|
||||
m_tx_relay = cryptonote::relay_method::none;
|
||||
}
|
||||
else if (settings.mask & event_visitor_settings::set_txs_stem)
|
||||
{
|
||||
m_tx_relay = cryptonote::relay_method::stem;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_tx_relay = cryptonote::relay_method::fluff;
|
||||
|
@ -162,6 +162,7 @@ int main(int argc, char* argv[])
|
||||
GENERATE_AND_PLAY(txpool_double_spend_norelay);
|
||||
GENERATE_AND_PLAY(txpool_double_spend_local);
|
||||
GENERATE_AND_PLAY(txpool_double_spend_keyimage);
|
||||
GENERATE_AND_PLAY(txpool_stem_loop);
|
||||
|
||||
// Double spend
|
||||
GENERATE_AND_PLAY(gen_double_spend_in_tx<false>);
|
||||
|
@ -552,7 +552,6 @@ bool txpool_double_spend_local::generate(std::vector<test_event_entry>& events)
|
||||
DO_CALLBACK(events, "mark_no_new");
|
||||
events.push_back(tx_0);
|
||||
DO_CALLBACK(events, "check_txpool_spent_keys");
|
||||
DO_CALLBACK(events, "mark_timestamp_change");
|
||||
DO_CALLBACK(events, "check_unchanged");
|
||||
SET_EVENT_VISITOR_SETT(events, 0);
|
||||
DO_CALLBACK(events, "timestamp_change_pause");
|
||||
@ -580,6 +579,7 @@ bool txpool_double_spend_keyimage::generate(std::vector<test_event_entry>& event
|
||||
const std::size_t tx_index1 = events.size();
|
||||
MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0);
|
||||
|
||||
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_stem);
|
||||
DO_CALLBACK(events, "increase_all_tx_count");
|
||||
DO_CALLBACK(events, "check_txpool_spent_keys");
|
||||
DO_CALLBACK(events, "mark_timestamp_change");
|
||||
@ -611,3 +611,30 @@ bool txpool_double_spend_keyimage::generate(std::vector<test_event_entry>& event
|
||||
return true;
|
||||
}
|
||||
|
||||
bool txpool_stem_loop::generate(std::vector<test_event_entry>& events) const
|
||||
{
|
||||
INIT_MEMPOOL_TEST();
|
||||
|
||||
DO_CALLBACK(events, "check_txpool_spent_keys");
|
||||
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_stem);
|
||||
DO_CALLBACK(events, "mark_no_new");
|
||||
|
||||
MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0);
|
||||
|
||||
DO_CALLBACK(events, "increase_all_tx_count");
|
||||
DO_CALLBACK(events, "check_txpool_spent_keys");
|
||||
DO_CALLBACK(events, "mark_timestamp_change");
|
||||
DO_CALLBACK(events, "check_new_hidden");
|
||||
DO_CALLBACK(events, "timestamp_change_pause");
|
||||
events.push_back(tx_0);
|
||||
DO_CALLBACK(events, "increase_broadcasted_tx_count");
|
||||
DO_CALLBACK(events, "check_txpool_spent_keys");
|
||||
DO_CALLBACK(events, "mark_timestamp_change");
|
||||
DO_CALLBACK(events, "check_new_broadcasted");
|
||||
DO_CALLBACK(events, "timestamp_change_pause");
|
||||
DO_CALLBACK(events, "mark_no_new");
|
||||
events.push_back(tx_0);
|
||||
DO_CALLBACK(events, "check_unchanged");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -127,3 +127,12 @@ struct txpool_double_spend_keyimage : txpool_double_spend_base
|
||||
|
||||
bool generate(std::vector<test_event_entry>& events) const;
|
||||
};
|
||||
|
||||
struct txpool_stem_loop : txpool_double_spend_base
|
||||
{
|
||||
txpool_stem_loop()
|
||||
: txpool_double_spend_base()
|
||||
{}
|
||||
|
||||
bool generate(std::vector<test_event_entry>& events) const;
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user