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
d4c7ae4554
commit
94f4af6cc4
|
@ -53,9 +53,7 @@ bool matches_category(relay_method method, relay_category category) noexcept
|
||||||
case relay_category::all:
|
case relay_category::all:
|
||||||
return true;
|
return true;
|
||||||
case relay_category::relayable:
|
case relay_category::relayable:
|
||||||
if (method == relay_method::none)
|
return method != relay_method::none;
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
case relay_category::broadcasted:
|
case relay_category::broadcasted:
|
||||||
case relay_category::legacy:
|
case relay_category::legacy:
|
||||||
break;
|
break;
|
||||||
|
@ -65,6 +63,7 @@ bool matches_category(relay_method method, relay_category category) noexcept
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
case relay_method::local:
|
case relay_method::local:
|
||||||
|
case relay_method::stem:
|
||||||
return false;
|
return false;
|
||||||
case relay_method::block:
|
case relay_method::block:
|
||||||
case relay_method::fluff:
|
case relay_method::fluff:
|
||||||
|
@ -80,6 +79,7 @@ void txpool_tx_meta_t::set_relay_method(relay_method method) noexcept
|
||||||
kept_by_block = 0;
|
kept_by_block = 0;
|
||||||
do_not_relay = 0;
|
do_not_relay = 0;
|
||||||
is_local = 0;
|
is_local = 0;
|
||||||
|
dandelionpp_stem = 0;
|
||||||
|
|
||||||
switch (method)
|
switch (method)
|
||||||
{
|
{
|
||||||
|
@ -92,6 +92,9 @@ void txpool_tx_meta_t::set_relay_method(relay_method method) noexcept
|
||||||
default:
|
default:
|
||||||
case relay_method::fluff:
|
case relay_method::fluff:
|
||||||
break;
|
break;
|
||||||
|
case relay_method::stem:
|
||||||
|
dandelionpp_stem = 1;
|
||||||
|
break;
|
||||||
case relay_method::block:
|
case relay_method::block:
|
||||||
kept_by_block = 1;
|
kept_by_block = 1;
|
||||||
break;
|
break;
|
||||||
|
@ -106,9 +109,26 @@ relay_method txpool_tx_meta_t::get_relay_method() const noexcept
|
||||||
return relay_method::none;
|
return relay_method::none;
|
||||||
if (is_local)
|
if (is_local)
|
||||||
return relay_method::local;
|
return relay_method::local;
|
||||||
|
if (dandelionpp_stem)
|
||||||
|
return relay_method::stem;
|
||||||
return relay_method::fluff;
|
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 = {
|
const command_line::arg_descriptor<std::string> arg_db_sync_mode = {
|
||||||
"db-sync-mode"
|
"db-sync-mode"
|
||||||
, "Specify sync option, using format [safe|fast|fastest]:[sync|async]:[<nblocks_per_sync>[blocks]|<nbytes_per_sync>[bytes]]."
|
, "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 max_used_block_height;
|
||||||
uint64_t last_failed_height;
|
uint64_t last_failed_height;
|
||||||
uint64_t receive_time;
|
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
|
// 112 bytes
|
||||||
uint8_t kept_by_block;
|
uint8_t kept_by_block;
|
||||||
uint8_t relayed;
|
uint8_t relayed;
|
||||||
|
@ -168,13 +168,17 @@ struct txpool_tx_meta_t
|
||||||
uint8_t double_spend_seen: 1;
|
uint8_t double_spend_seen: 1;
|
||||||
uint8_t pruned: 1;
|
uint8_t pruned: 1;
|
||||||
uint8_t is_local: 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
|
uint8_t padding[76]; // till 192 bytes
|
||||||
|
|
||||||
void set_relay_method(relay_method method) noexcept;
|
void set_relay_method(relay_method method) noexcept;
|
||||||
relay_method get_relay_method() const 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
|
//! See `relay_category` description
|
||||||
bool matches(const relay_category category) const noexcept
|
bool matches(const relay_category category) const noexcept
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "cryptonote_protocol/enums.h"
|
||||||
|
|
||||||
namespace cryptonote
|
namespace cryptonote
|
||||||
{
|
{
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
|
@ -36,7 +39,9 @@ namespace cryptonote
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
struct tx_verification_context
|
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_failed; //bad tx, should drop connection
|
||||||
bool m_verifivation_impossible; //the transaction is related with an alternative blockchain
|
bool m_verifivation_impossible; //the transaction is related with an alternative blockchain
|
||||||
bool m_added_to_pool;
|
bool m_added_to_pool;
|
||||||
|
|
|
@ -109,7 +109,12 @@
|
||||||
#define CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME 604800 //seconds, one week
|
#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
|
// see src/cryptonote_protocol/levin_notify.cpp
|
||||||
#define CRYPTONOTE_NOISE_MIN_EPOCH 5 // minutes
|
#define CRYPTONOTE_NOISE_MIN_EPOCH 5 // minutes
|
||||||
|
|
|
@ -1324,6 +1324,7 @@ namespace cryptonote
|
||||||
break;
|
break;
|
||||||
case relay_method::block:
|
case relay_method::block:
|
||||||
case relay_method::fluff:
|
case relay_method::fluff:
|
||||||
|
case relay_method::stem:
|
||||||
public_req.txs.push_back(std::move(std::get<1>(tx)));
|
public_req.txs.push_back(std::move(std::get<1>(tx)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1335,9 +1336,9 @@ namespace cryptonote
|
||||||
re-relaying public and private _should_ be acceptable here. */
|
re-relaying public and private _should_ be acceptable here. */
|
||||||
const boost::uuids::uuid source = boost::uuids::nil_uuid();
|
const boost::uuids::uuid source = boost::uuids::nil_uuid();
|
||||||
if (!public_req.txs.empty())
|
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())
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
#include "warnings.h"
|
#include "warnings.h"
|
||||||
#include "common/perf_timer.h"
|
#include "common/perf_timer.h"
|
||||||
#include "crypto/hash.h"
|
#include "crypto/hash.h"
|
||||||
|
#include "crypto/duration.h"
|
||||||
|
|
||||||
#undef MONERO_DEFAULT_LOG_CATEGORY
|
#undef MONERO_DEFAULT_LOG_CATEGORY
|
||||||
#define MONERO_DEFAULT_LOG_CATEGORY "txpool"
|
#define MONERO_DEFAULT_LOG_CATEGORY "txpool"
|
||||||
|
@ -58,6 +59,29 @@ namespace cryptonote
|
||||||
{
|
{
|
||||||
namespace
|
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,
|
//TODO: constants such as these should at least be in the header,
|
||||||
// but probably somewhere more accessible to the rest of the
|
// but probably somewhere more accessible to the rest of the
|
||||||
// codebase. As it stands, it is at best nontrivial to test
|
// codebase. As it stands, it is at best nontrivial to test
|
||||||
|
@ -262,34 +286,51 @@ namespace cryptonote
|
||||||
}
|
}
|
||||||
}else
|
}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
|
try
|
||||||
{
|
{
|
||||||
if (kept_by_block)
|
if (kept_by_block)
|
||||||
m_parsed_tx_cache.insert(std::make_pair(id, tx));
|
m_parsed_tx_cache.insert(std::make_pair(id, tx));
|
||||||
CRITICAL_REGION_LOCAL1(m_blockchain);
|
CRITICAL_REGION_LOCAL1(m_blockchain);
|
||||||
LockedTXN lock(m_blockchain.get_db());
|
LockedTXN lock(m_blockchain.get_db());
|
||||||
m_blockchain.remove_txpool_tx(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))
|
if (!insert_key_images(tx, id, tx_relay))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
m_blockchain.remove_txpool_tx(id);
|
||||||
m_blockchain.add_txpool_tx(id, blob, meta);
|
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);
|
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();
|
lock.commit();
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
|
@ -299,8 +340,9 @@ namespace cryptonote
|
||||||
}
|
}
|
||||||
tvc.m_added_to_pool = true;
|
tvc.m_added_to_pool = true;
|
||||||
|
|
||||||
if(meta.fee > 0 && tx_relay != relay_method::none)
|
static_assert(unsigned(relay_method::none) == 0, "expected relay_method::none value to be zero");
|
||||||
tvc.m_should_be_relayed = true;
|
if(meta.fee > 0)
|
||||||
|
tvc.m_relay = tx_relay;
|
||||||
}
|
}
|
||||||
|
|
||||||
tvc.m_verifivation_failed = false;
|
tvc.m_verifivation_failed = false;
|
||||||
|
@ -553,7 +595,7 @@ namespace cryptonote
|
||||||
td.last_failed_height = meta.last_failed_height;
|
td.last_failed_height = meta.last_failed_height;
|
||||||
td.last_failed_id = meta.last_failed_id;
|
td.last_failed_id = meta.last_failed_id;
|
||||||
td.receive_time = meta.receive_time;
|
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.relayed = meta.relayed;
|
||||||
td.do_not_relay = meta.do_not_relay;
|
td.do_not_relay = meta.do_not_relay;
|
||||||
td.double_spend_seen = meta.double_spend_seen;
|
td.double_spend_seen = meta.double_spend_seen;
|
||||||
|
@ -686,8 +728,13 @@ namespace cryptonote
|
||||||
txs.reserve(m_blockchain.get_txpool_tx_count());
|
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 *){
|
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
|
// 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
|
// 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
|
// 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
|
// 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)
|
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_LOCAL(m_transactions_lock);
|
||||||
CRITICAL_REGION_LOCAL1(m_blockchain);
|
CRITICAL_REGION_LOCAL1(m_blockchain);
|
||||||
const time_t now = time(NULL);
|
|
||||||
LockedTXN lock(m_blockchain.get_db());
|
LockedTXN lock(m_blockchain.get_db());
|
||||||
for (const auto& hash : hashes)
|
for (const auto& hash : hashes)
|
||||||
{
|
{
|
||||||
|
@ -723,9 +772,15 @@ namespace cryptonote
|
||||||
txpool_tx_meta_t meta;
|
txpool_tx_meta_t meta;
|
||||||
if (m_blockchain.get_txpool_tx_meta(hash, 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.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);
|
m_blockchain.update_txpool_tx(hash, meta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -910,7 +965,7 @@ namespace cryptonote
|
||||||
txi.receive_time = include_sensitive_data ? meta.receive_time : 0;
|
txi.receive_time = include_sensitive_data ? meta.receive_time : 0;
|
||||||
txi.relayed = meta.relayed;
|
txi.relayed = meta.relayed;
|
||||||
// In restricted mode we do not include this data:
|
// 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.do_not_relay = meta.do_not_relay;
|
||||||
txi.double_spend_seen = meta.double_spend_seen;
|
txi.double_spend_seen = meta.double_spend_seen;
|
||||||
tx_infos.push_back(std::move(txi));
|
tx_infos.push_back(std::move(txi));
|
||||||
|
@ -962,7 +1017,7 @@ namespace cryptonote
|
||||||
txi.last_failed_block_hash = meta.last_failed_id;
|
txi.last_failed_block_hash = meta.last_failed_id;
|
||||||
txi.receive_time = meta.receive_time;
|
txi.receive_time = meta.receive_time;
|
||||||
txi.relayed = meta.relayed;
|
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.do_not_relay = meta.do_not_relay;
|
||||||
txi.double_spend_seen = meta.double_spend_seen;
|
txi.double_spend_seen = meta.double_spend_seen;
|
||||||
tx_infos.push_back(txi);
|
tx_infos.push_back(txi);
|
||||||
|
|
|
@ -197,10 +197,12 @@ namespace cryptonote
|
||||||
{
|
{
|
||||||
std::vector<blobdata> txs;
|
std::vector<blobdata> txs;
|
||||||
std::string _; // padding
|
std::string _; // padding
|
||||||
|
bool dandelionpp_fluff; //zero initialization defaults to stem mode
|
||||||
|
|
||||||
BEGIN_KV_SERIALIZE_MAP()
|
BEGIN_KV_SERIALIZE_MAP()
|
||||||
KV_SERIALIZE(txs)
|
KV_SERIALIZE(txs)
|
||||||
KV_SERIALIZE(_)
|
KV_SERIALIZE(_)
|
||||||
|
KV_SERIALIZE_OPT(dandelionpp_fluff, true) // backwards compatible mode is fluff
|
||||||
END_KV_SERIALIZE_MAP()
|
END_KV_SERIALIZE_MAP()
|
||||||
};
|
};
|
||||||
typedef epee::misc_utils::struct_init<request_t> request;
|
typedef epee::misc_utils::struct_init<request_t> request;
|
||||||
|
|
|
@ -129,7 +129,7 @@ namespace cryptonote
|
||||||
|
|
||||||
//----------------- i_bc_protocol_layout ---------------------------------------
|
//----------------- i_bc_protocol_layout ---------------------------------------
|
||||||
virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context);
|
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 get_payload_sync_data(HANDSHAKE_DATA::request& hshd, cryptonote_connection_context& context);
|
||||||
bool should_drop_connection(cryptonote_connection_context& context, uint32_t next_stripe);
|
bool should_drop_connection(cryptonote_connection_context& context, uint32_t next_stripe);
|
||||||
|
|
|
@ -926,29 +926,60 @@ namespace cryptonote
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<cryptonote::blobdata> newtxs;
|
relay_method tx_relay;
|
||||||
newtxs.reserve(arg.txs.size());
|
std::vector<blobdata> stem_txs{};
|
||||||
for (size_t i = 0; i < arg.txs.size(); ++i)
|
std::vector<blobdata> fluff_txs{};
|
||||||
|
if (arg.dandelionpp_fluff)
|
||||||
{
|
{
|
||||||
cryptonote::tx_verification_context tvc{};
|
tx_relay = relay_method::fluff;
|
||||||
m_core.handle_incoming_tx({arg.txs[i], crypto::null_hash}, tvc, relay_method::fluff, true);
|
fluff_txs.reserve(arg.txs.size());
|
||||||
if(tvc.m_verifivation_failed)
|
}
|
||||||
|
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");
|
LOG_PRINT_CCONTEXT_L1("Tx verification failed, dropping connection");
|
||||||
drop_connection(context, false, false);
|
drop_connection(context, false, false);
|
||||||
return 1;
|
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
|
//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;
|
return 1;
|
||||||
}
|
}
|
||||||
//------------------------------------------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -2387,14 +2418,14 @@ skip:
|
||||||
}
|
}
|
||||||
//------------------------------------------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------------------------------------------
|
||||||
template<class t_core>
|
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
|
/* Push all outgoing transactions to this function. The behavior needs to
|
||||||
identify how the transaction is going to be relayed, and then update the
|
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
|
local mempool before doing the relay. The code was already updating the
|
||||||
DB twice on received transactions - it is difficult to workaround this
|
DB twice on received transactions - it is difficult to workaround this
|
||||||
due to the internal design. */
|
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>
|
template<class t_core>
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace cryptonote
|
||||||
struct i_cryptonote_protocol
|
struct i_cryptonote_protocol
|
||||||
{
|
{
|
||||||
virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context)=0;
|
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;
|
//virtual bool request_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context)=0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ namespace cryptonote
|
||||||
{
|
{
|
||||||
return false;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,8 @@ namespace cryptonote
|
||||||
{
|
{
|
||||||
none = 0, //!< Received via RPC with `do_not_relay` set
|
none = 0, //!< Received via RPC with `do_not_relay` set
|
||||||
local, //!< Received via RPC; trying to send over i2p/tor, etc.
|
local, //!< Received via RPC; trying to send over i2p/tor, etc.
|
||||||
block, //!< Received in block, takes precedence over others
|
stem, //!< Received/send over network using Dandelion++ stem
|
||||||
fluff //!< Received/sent over public networks
|
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/asio/steady_timer.hpp>
|
||||||
#include <boost/system/system_error.hpp>
|
#include <boost/system/system_error.hpp>
|
||||||
|
#include <boost/uuid/uuid_io.hpp>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
@ -38,8 +39,10 @@
|
||||||
#include "common/expect.h"
|
#include "common/expect.h"
|
||||||
#include "common/varint.h"
|
#include "common/varint.h"
|
||||||
#include "cryptonote_config.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_basic/connection_context.h"
|
||||||
|
#include "cryptonote_core/i_core_events.h"
|
||||||
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
|
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
|
||||||
#include "net/dandelionpp.h"
|
#include "net/dandelionpp.h"
|
||||||
#include "p2p/net_node.h"
|
#include "p2p/net_node.h"
|
||||||
|
@ -61,11 +64,14 @@ namespace levin
|
||||||
{
|
{
|
||||||
namespace
|
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::minutes noise_min_epoch{CRYPTONOTE_NOISE_MIN_EPOCH};
|
||||||
constexpr const std::chrono::seconds noise_epoch_range{CRYPTONOTE_NOISE_EPOCH_RANGE};
|
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_min_delay{CRYPTONOTE_NOISE_MIN_DELAY};
|
||||||
constexpr const std::chrono::seconds noise_delay_range{CRYPTONOTE_NOISE_DELAY_RANGE};
|
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
|
connections (Dandelion++ makes similar assumptions in its stem
|
||||||
algorithm). The randomization yields 95% values between 1s-4s in
|
algorithm). The randomization yields 95% values between 1s-4s in
|
||||||
1/4s increments. */
|
1/4s increments. */
|
||||||
constexpr const fluff_stepsize fluff_average_out{fluff_stepsize{fluff_average_in} / 2};
|
using fluff_duration = crypto::random_poisson_subseconds::result_type;
|
||||||
|
constexpr const fluff_duration fluff_average_out{fluff_duration{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)};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*! Select a randomized duration from 0 to `range`. The precision will be to
|
/*! 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
|
the systems `steady_clock`. As an example, supplying 3 seconds to this
|
||||||
|
@ -132,10 +124,11 @@ namespace levin
|
||||||
return outs;
|
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{};
|
NOTIFY_NEW_TRANSACTIONS::request request{};
|
||||||
request.txs = std::move(txs);
|
request.txs = std::move(txs);
|
||||||
|
request.dandelionpp_fluff = fluff;
|
||||||
|
|
||||||
if (pad)
|
if (pad)
|
||||||
{
|
{
|
||||||
|
@ -172,9 +165,9 @@ namespace levin
|
||||||
return fullBlob;
|
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) {
|
p2p.for_connection(destination, [&blob](detail::p2p_context& context) {
|
||||||
on_levin_traffic(context, true, true, false, blob.size(), get_command_from_message(blob));
|
on_levin_traffic(context, true, true, false, blob.size(), get_command_from_message(blob));
|
||||||
return true;
|
return true;
|
||||||
|
@ -251,7 +244,8 @@ namespace levin
|
||||||
flush_time(std::chrono::steady_clock::time_point::max()),
|
flush_time(std::chrono::steady_clock::time_point::max()),
|
||||||
connection_count(0),
|
connection_count(0),
|
||||||
is_public(is_public),
|
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)
|
for (std::size_t count = 0; !noise.empty() && count < CRYPTONOTE_NOISE_CHANNELS; ++count)
|
||||||
channels.emplace_back(io_service);
|
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
|
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 is_public; //!< Zone is public ipv4/ipv6 connections
|
||||||
const bool pad_txs; //!< Pad txs to the next boundary for privacy
|
const bool pad_txs; //!< Pad txs to the next boundary for privacy
|
||||||
|
bool fluffing; //!< Zone is in Dandelion++ fluff epoch
|
||||||
};
|
};
|
||||||
} // detail
|
} // detail
|
||||||
|
|
||||||
|
@ -362,10 +357,11 @@ namespace levin
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Always send txs in stem mode over i2p/tor, see comments in `send_txs` below.
|
||||||
for (auto& connection : connections)
|
for (auto& connection : connections)
|
||||||
{
|
{
|
||||||
std::sort(connection.first.begin(), connection.first.end()); // don't leak receive order
|
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())
|
if (next_flush != std::chrono::steady_clock::time_point::max())
|
||||||
|
@ -387,29 +383,38 @@ namespace levin
|
||||||
|
|
||||||
void operator()()
|
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;
|
return;
|
||||||
|
|
||||||
assert(zone_->strand.running_in_this_thread());
|
assert(zone->strand.running_in_this_thread());
|
||||||
|
|
||||||
const auto now = std::chrono::steady_clock::now();
|
const auto now = std::chrono::steady_clock::now();
|
||||||
auto next_flush = std::chrono::steady_clock::time_point::max();
|
auto next_flush = std::chrono::steady_clock::time_point::max();
|
||||||
|
|
||||||
random_poisson in_duration(fluff_average_in);
|
crypto::random_poisson_subseconds in_duration(fluff_average_in);
|
||||||
random_poisson out_duration(fluff_average_out);
|
crypto::random_poisson_subseconds out_duration(fluff_average_out);
|
||||||
|
|
||||||
|
|
||||||
|
MDEBUG("Queueing " << txs.size() << " transaction(s) for Dandelion++ fluffing");
|
||||||
|
|
||||||
bool available = false;
|
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;
|
available = true;
|
||||||
if (context.fluff_txs.empty())
|
if (context.fluff_txs.empty())
|
||||||
context.flush_time = now + (context.m_is_income ? in_duration() : out_duration());
|
context.flush_time = now + (context.m_is_income ? in_duration() : out_duration());
|
||||||
|
|
||||||
next_flush = std::min(next_flush, context.flush_time);
|
next_flush = std::min(next_flush, context.flush_time);
|
||||||
context.fluff_txs.reserve(context.fluff_txs.size() + this->txs_.size());
|
context.fluff_txs.reserve(context.fluff_txs.size() + txs.size());
|
||||||
for (const blobdata& tx : this->txs_)
|
for (const blobdata& tx : txs)
|
||||||
context.fluff_txs.push_back(tx); // must copy instead of move (multiple conns)
|
context.fluff_txs.push_back(tx); // must copy instead of move (multiple conns)
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -418,8 +423,8 @@ namespace levin
|
||||||
if (!available)
|
if (!available)
|
||||||
MWARNING("Unable to send transaction(s), no available connections");
|
MWARNING("Unable to send transaction(s), no available connections");
|
||||||
|
|
||||||
if (next_flush < zone_->flush_time)
|
if (next_flush < zone->flush_time)
|
||||||
fluff_flush::queue(std::move(zone_), next_flush);
|
fluff_flush::queue(std::move(zone), next_flush);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -471,6 +476,11 @@ namespace levin
|
||||||
assert(zone->strand.running_in_this_thread());
|
assert(zone->strand.running_in_this_thread());
|
||||||
|
|
||||||
zone->connection_count = zone->map.size();
|
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)
|
for (auto id = zone->map.begin(); id != zone->map.end(); ++id)
|
||||||
{
|
{
|
||||||
const std::size_t i = id - zone->map.begin();
|
const std::size_t i = id - zone->map.begin();
|
||||||
|
@ -479,26 +489,75 @@ namespace levin
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \pre Called within `zone_->strand`.
|
//! \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;
|
return;
|
||||||
|
|
||||||
assert(zone_->strand.running_in_this_thread());
|
assert(zone->strand.running_in_this_thread());
|
||||||
if (zone_->map.update(std::move(out_connections_)))
|
if (zone->map.update(std::move(out_connections)))
|
||||||
post(std::move(zone_));
|
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
|
class change_channels
|
||||||
{
|
{
|
||||||
std::shared_ptr<detail::zone> zone_;
|
std::shared_ptr<detail::zone> zone_;
|
||||||
net::dandelionpp::connection_map map_; // Requires manual copy constructor
|
net::dandelionpp::connection_map map_; // Requires manual copy constructor
|
||||||
|
bool fluffing_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit change_channels(std::shared_ptr<detail::zone> zone, net::dandelionpp::connection_map 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))
|
: zone_(std::move(zone)), map_(std::move(map)), fluffing_(fluffing)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
change_channels(change_channels&&) = default;
|
change_channels(change_channels&&) = default;
|
||||||
|
@ -510,11 +569,15 @@ namespace levin
|
||||||
void operator()()
|
void operator()()
|
||||||
{
|
{
|
||||||
if (!zone_)
|
if (!zone_)
|
||||||
return
|
return;
|
||||||
|
|
||||||
assert(zone_->strand.running_in_this_thread());
|
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_->map = std::move(map_);
|
||||||
|
zone_->fluffing = fluffing_;
|
||||||
update_channels::post(std::move(zone_));
|
update_channels::post(std::move(zone_));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -608,9 +671,10 @@ namespace levin
|
||||||
if (error && error != boost::system::errc::operation_canceled)
|
if (error && error != boost::system::errc::operation_canceled)
|
||||||
throw boost::system::system_error{error, "start_epoch timer failed"};
|
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();
|
const auto start = std::chrono::steady_clock::now();
|
||||||
zone_->strand.dispatch(
|
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_;
|
detail::zone& alias = *zone_;
|
||||||
|
@ -626,10 +690,16 @@ namespace levin
|
||||||
if (!zone_->p2p)
|
if (!zone_->p2p)
|
||||||
throw std::logic_error{"cryptonote::levin::notify cannot have nullptr p2p argument"};
|
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();
|
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)
|
for (std::size_t channel = 0; channel < zone_->channels.size(); ++channel)
|
||||||
send_noise::wait(now, zone_, channel);
|
send_noise::wait(now, zone_, channel);
|
||||||
}
|
}
|
||||||
|
@ -679,7 +749,7 @@ namespace levin
|
||||||
zone_->flush_txs.cancel();
|
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())
|
if (txs.empty())
|
||||||
return true;
|
return true;
|
||||||
|
@ -687,6 +757,17 @@ namespace levin
|
||||||
if (!zone_)
|
if (!zone_)
|
||||||
return false;
|
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())
|
if (!zone_->noise.empty() && !zone_->channels.empty())
|
||||||
{
|
{
|
||||||
// covert send in "noise" channel
|
// 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"
|
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
|
if (tx_relay == relay_method::stem)
|
||||||
const std::string payload = make_tx_payload(std::move(txs), false);
|
{
|
||||||
|
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(
|
epee::byte_slice message = epee::levin::make_fragmented_notify(
|
||||||
zone_->noise, NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan<std::uint8_t>(payload)
|
zone_->noise, NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan<std::uint8_t>(payload)
|
||||||
);
|
);
|
||||||
|
@ -714,9 +804,31 @@ namespace levin
|
||||||
}
|
}
|
||||||
else
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
} // levin
|
} // levin
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
|
|
||||||
#include "byte_slice.h"
|
#include "byte_slice.h"
|
||||||
#include "cryptonote_basic/blobdatatype.h"
|
#include "cryptonote_basic/blobdatatype.h"
|
||||||
|
#include "cryptonote_protocol/enums.h"
|
||||||
#include "cryptonote_protocol/fwd.h"
|
#include "cryptonote_protocol/fwd.h"
|
||||||
#include "net/enums.h"
|
#include "net/enums.h"
|
||||||
#include "span.h"
|
#include "span.h"
|
||||||
|
@ -122,7 +123,7 @@ namespace levin
|
||||||
particular stem.
|
particular stem.
|
||||||
|
|
||||||
\return True iff the notification is queued for sending. */
|
\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
|
} // levin
|
||||||
} // net
|
} // net
|
||||||
|
|
|
@ -331,7 +331,7 @@ namespace nodetool
|
||||||
virtual void callback(p2p_connection_context& context);
|
virtual void callback(p2p_connection_context& context);
|
||||||
//----------------- i_p2p_endpoint -------------------------------------------------------------
|
//----------------- 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 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_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 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);
|
virtual bool drop_connection(const epee::net_utils::connection_context_base& context);
|
||||||
|
|
|
@ -1966,18 +1966,13 @@ namespace nodetool
|
||||||
}
|
}
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
template<class t_payload_net_handler>
|
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;
|
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_);
|
if (network.second.m_notifier.send_txs(std::move(txs), source, core, tx_relay))
|
||||||
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))
|
|
||||||
return network.first;
|
return network.first;
|
||||||
return enet::zone::invalid;
|
return enet::zone::invalid;
|
||||||
};
|
};
|
||||||
|
|
|
@ -50,7 +50,7 @@ namespace nodetool
|
||||||
struct i_p2p_endpoint
|
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 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_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 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;
|
virtual bool drop_connection(const epee::net_utils::connection_context_base& context)=0;
|
||||||
|
@ -75,7 +75,7 @@ namespace nodetool
|
||||||
{
|
{
|
||||||
return false;
|
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;
|
return epee::net_utils::zone::invalid;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1152,7 +1152,7 @@ namespace cryptonote
|
||||||
return true;
|
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");
|
LOG_PRINT_L0("[on_send_raw_tx]: tx accepted, but not relayed");
|
||||||
res.reason = "Not relayed";
|
res.reason = "Not relayed";
|
||||||
|
@ -1162,8 +1162,8 @@ namespace cryptonote
|
||||||
}
|
}
|
||||||
|
|
||||||
NOTIFY_NEW_TRANSACTIONS::request r;
|
NOTIFY_NEW_TRANSACTIONS::request r;
|
||||||
r.txs.push_back(tx_blob);
|
r.txs.push_back(std::move(tx_blob));
|
||||||
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid);
|
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
|
//TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes
|
||||||
res.status = CORE_RPC_STATUS_OK;
|
res.status = CORE_RPC_STATUS_OK;
|
||||||
return true;
|
return true;
|
||||||
|
@ -2776,8 +2776,8 @@ namespace cryptonote
|
||||||
if (!m_core.get_pool_transaction(txid, txblob, relay_category::legacy))
|
if (!m_core.get_pool_transaction(txid, txblob, relay_category::legacy))
|
||||||
{
|
{
|
||||||
NOTIFY_NEW_TRANSACTIONS::request r;
|
NOTIFY_NEW_TRANSACTIONS::request r;
|
||||||
r.txs.push_back(txblob);
|
r.txs.push_back(std::move(txblob));
|
||||||
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid);
|
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
|
//TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -349,10 +349,10 @@ namespace rpc
|
||||||
res.error_details = "Invalid hex";
|
res.error_details = "Invalid hex";
|
||||||
return;
|
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())
|
if (!m_p2p.get_payload_object().is_synchronized())
|
||||||
{
|
{
|
||||||
|
@ -423,7 +423,7 @@ namespace rpc
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!tvc.m_should_be_relayed || !relay)
|
if(tvc.m_relay == relay_method::none || !relay)
|
||||||
{
|
{
|
||||||
MERROR("[SendRawTx]: tx accepted, but not relayed");
|
MERROR("[SendRawTx]: tx accepted, but not relayed");
|
||||||
res.error_details = "Not relayed";
|
res.error_details = "Not relayed";
|
||||||
|
@ -434,8 +434,8 @@ namespace rpc
|
||||||
}
|
}
|
||||||
|
|
||||||
NOTIFY_NEW_TRANSACTIONS::request r;
|
NOTIFY_NEW_TRANSACTIONS::request r;
|
||||||
r.txs.push_back(tx_blob);
|
r.txs.push_back(std::move(tx_blob));
|
||||||
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid);
|
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
|
//TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes
|
||||||
res.status = Message::STATUS_OK;
|
res.status = Message::STATUS_OK;
|
||||||
|
|
|
@ -138,7 +138,7 @@ class DaemonHandler : public RpcHandler
|
||||||
|
|
||||||
bool getBlockHeaderByHash(const crypto::hash& hash_in, cryptonote::rpc::BlockHeaderResponse& response);
|
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;
|
cryptonote::core& m_core;
|
||||||
t_p2p& m_p2p;
|
t_p2p& m_p2p;
|
||||||
|
|
|
@ -116,7 +116,8 @@ struct event_visitor_settings
|
||||||
{
|
{
|
||||||
set_txs_keeped_by_block = 1 << 0,
|
set_txs_keeped_by_block = 1 << 0,
|
||||||
set_txs_do_not_relay = 1 << 1,
|
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)
|
event_visitor_settings(int a_mask = 0)
|
||||||
|
@ -548,6 +549,10 @@ public:
|
||||||
{
|
{
|
||||||
m_tx_relay = cryptonote::relay_method::none;
|
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
|
else
|
||||||
{
|
{
|
||||||
m_tx_relay = cryptonote::relay_method::fluff;
|
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_norelay);
|
||||||
GENERATE_AND_PLAY(txpool_double_spend_local);
|
GENERATE_AND_PLAY(txpool_double_spend_local);
|
||||||
GENERATE_AND_PLAY(txpool_double_spend_keyimage);
|
GENERATE_AND_PLAY(txpool_double_spend_keyimage);
|
||||||
|
GENERATE_AND_PLAY(txpool_stem_loop);
|
||||||
|
|
||||||
// Double spend
|
// Double spend
|
||||||
GENERATE_AND_PLAY(gen_double_spend_in_tx<false>);
|
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");
|
DO_CALLBACK(events, "mark_no_new");
|
||||||
events.push_back(tx_0);
|
events.push_back(tx_0);
|
||||||
DO_CALLBACK(events, "check_txpool_spent_keys");
|
DO_CALLBACK(events, "check_txpool_spent_keys");
|
||||||
DO_CALLBACK(events, "mark_timestamp_change");
|
|
||||||
DO_CALLBACK(events, "check_unchanged");
|
DO_CALLBACK(events, "check_unchanged");
|
||||||
SET_EVENT_VISITOR_SETT(events, 0);
|
SET_EVENT_VISITOR_SETT(events, 0);
|
||||||
DO_CALLBACK(events, "timestamp_change_pause");
|
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();
|
const std::size_t tx_index1 = events.size();
|
||||||
MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0);
|
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, "increase_all_tx_count");
|
||||||
DO_CALLBACK(events, "check_txpool_spent_keys");
|
DO_CALLBACK(events, "check_txpool_spent_keys");
|
||||||
DO_CALLBACK(events, "mark_timestamp_change");
|
DO_CALLBACK(events, "mark_timestamp_change");
|
||||||
|
@ -611,3 +611,30 @@ bool txpool_double_spend_keyimage::generate(std::vector<test_event_entry>& event
|
||||||
return true;
|
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;
|
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