aboutsummaryrefslogtreecommitdiff
path: root/src/crypto
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/crypto/CryptonightR_JIT.c6
-rw-r--r--src/crypto/crypto.cpp17
-rw-r--r--src/crypto/crypto.h1
-rw-r--r--src/crypto/random.c15
-rw-r--r--src/crypto/random.h1
-rw-r--r--src/cryptonote_basic/connection_context.h2
-rw-r--r--src/cryptonote_basic/miner.cpp2
-rw-r--r--src/cryptonote_config.h10
-rw-r--r--src/cryptonote_core/blockchain.cpp45
-rw-r--r--src/cryptonote_core/blockchain.h13
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.cpp2
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.h15
-rw-r--r--src/cryptonote_core/tx_sanity_check.cpp4
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl72
-rw-r--r--src/cryptonote_protocol/levin_notify.cpp574
-rw-r--r--src/cryptonote_protocol/levin_notify.h132
16 files changed, 791 insertions, 120 deletions
diff --git a/src/crypto/CryptonightR_JIT.c b/src/crypto/CryptonightR_JIT.c
index ee8f3f36f..ffe7820a2 100644
--- a/src/crypto/CryptonightR_JIT.c
+++ b/src/crypto/CryptonightR_JIT.c
@@ -63,7 +63,7 @@ int v4_generate_JIT_code(const struct V4_Instruction* code, v4_random_math_JIT_f
#if !(defined(_MSC_VER) || defined(__MINGW32__))
if (mprotect((void*)buf, buf_size, PROT_READ | PROT_WRITE))
- return 1;
+ return -1;
#endif
APPEND_CODE(prologue, sizeof(prologue));
@@ -111,13 +111,13 @@ int v4_generate_JIT_code(const struct V4_Instruction* code, v4_random_math_JIT_f
#if !(defined(_MSC_VER) || defined(__MINGW32__))
if (mprotect((void*)buf, buf_size, PROT_READ | PROT_EXEC))
- return 1;
+ return -1;
#endif
__builtin___clear_cache((char*)buf, (char*)JIT_code);
return 0;
#else
- return 1;
+ return -1;
#endif
}
diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp
index 3f06c4f3f..0ec992de9 100644
--- a/src/crypto/crypto.cpp
+++ b/src/crypto/crypto.cpp
@@ -88,13 +88,24 @@ namespace crypto {
return &reinterpret_cast<const unsigned char &>(scalar);
}
- void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes)
+ boost::mutex &get_random_lock()
{
static boost::mutex random_lock;
- boost::lock_guard<boost::mutex> lock(random_lock);
+ return random_lock;
+ }
+
+ void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes)
+ {
+ boost::lock_guard<boost::mutex> lock(get_random_lock());
generate_random_bytes_not_thread_safe(N, bytes);
}
+ void add_extra_entropy_thread_safe(const void *ptr, size_t bytes)
+ {
+ boost::lock_guard<boost::mutex> lock(get_random_lock());
+ add_extra_entropy_not_thread_safe(ptr, bytes);
+ }
+
static inline bool less32(const unsigned char *k0, const unsigned char *k1)
{
for (int n = 31; n >= 0; --n)
@@ -275,8 +286,6 @@ namespace crypto {
buf.key = pub;
try_again:
random_scalar(k);
- if (((const uint32_t*)(&k))[7] == 0) // we don't want tiny numbers here
- goto try_again;
ge_scalarmult_base(&tmp3, &k);
ge_p3_tobytes(&buf.comm, &tmp3);
hash_to_scalar(&buf, sizeof(s_comm), sig.c);
diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h
index bac456f60..8ce321f71 100644
--- a/src/crypto/crypto.h
+++ b/src/crypto/crypto.h
@@ -147,6 +147,7 @@ namespace crypto {
};
void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes);
+ void add_extra_entropy_thread_safe(const void *ptr, size_t bytes);
/* Generate N random bytes
*/
diff --git a/src/crypto/random.c b/src/crypto/random.c
index 74b202661..766b5f558 100644
--- a/src/crypto/random.c
+++ b/src/crypto/random.c
@@ -146,3 +146,18 @@ void generate_random_bytes_not_thread_safe(size_t n, void *result) {
}
}
}
+
+void add_extra_entropy_not_thread_safe(const void *ptr, size_t bytes)
+{
+ size_t i;
+
+ while (bytes > 0)
+ {
+ hash_permutation(&state);
+ const size_t round_bytes = bytes > HASH_DATA_AREA ? HASH_DATA_AREA : bytes;
+ for (i = 0; i < round_bytes; ++i)
+ state.b[i] ^= ((const uint8_t*)ptr)[i];
+ bytes -= round_bytes;
+ ptr = cpadd(ptr, round_bytes);
+ }
+}
diff --git a/src/crypto/random.h b/src/crypto/random.h
index ccb9f4853..21a66d776 100644
--- a/src/crypto/random.h
+++ b/src/crypto/random.h
@@ -33,3 +33,4 @@
#include <stddef.h>
void generate_random_bytes_not_thread_safe(size_t n, void *result);
+void add_extra_entropy_not_thread_safe(const void *ptr, size_t bytes);
diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h
index 96398a90b..51076e8c0 100644
--- a/src/cryptonote_basic/connection_context.h
+++ b/src/cryptonote_basic/connection_context.h
@@ -31,8 +31,10 @@
#pragma once
#include <unordered_set>
#include <atomic>
+#include <boost/date_time/posix_time/posix_time.hpp>
#include "net/net_utils_base.h"
#include "copyable_atomic.h"
+#include "crypto/hash.h"
namespace cryptonote
{
diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp
index e594eb049..2dad2795e 100644
--- a/src/cryptonote_basic/miner.cpp
+++ b/src/cryptonote_basic/miner.cpp
@@ -91,7 +91,7 @@ namespace cryptonote
const command_line::arg_descriptor<std::string> arg_extra_messages = {"extra-messages-file", "Specify file for extra messages to include into coinbase transactions", "", true};
const command_line::arg_descriptor<std::string> arg_start_mining = {"start-mining", "Specify wallet address to mining for", "", true};
const command_line::arg_descriptor<uint32_t> arg_mining_threads = {"mining-threads", "Specify mining threads count", 0, true};
- const command_line::arg_descriptor<bool> arg_bg_mining_enable = {"bg-mining-enable", "enable/disable background mining", true, true};
+ const command_line::arg_descriptor<bool> arg_bg_mining_enable = {"bg-mining-enable", "enable background mining", true, true};
const command_line::arg_descriptor<bool> arg_bg_mining_ignore_battery = {"bg-mining-ignore-battery", "if true, assumes plugged in when unable to query system power status", false, true};
const command_line::arg_descriptor<uint64_t> arg_bg_mining_min_idle_interval_seconds = {"bg-mining-min-idle-interval", "Specify min lookback interval in seconds for determining idle state", miner::BACKGROUND_MINING_DEFAULT_MIN_IDLE_INTERVAL_IN_SECONDS, true};
const command_line::arg_descriptor<uint16_t> arg_bg_mining_idle_threshold_percentage = {"bg-mining-idle-threshold", "Specify minimum avg idle percentage over lookback interval", miner::BACKGROUND_MINING_DEFAULT_IDLE_THRESHOLD_PERCENTAGE, true};
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
index b68bb41e1..173b454f6 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
@@ -100,6 +100,16 @@
#define CRYPTONOTE_MEMPOOL_TX_LIVETIME (86400*3) //seconds, three days
#define CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME 604800 //seconds, one week
+// see src/cryptonote_protocol/levin_notify.cpp
+#define CRYPTONOTE_NOISE_MIN_EPOCH 5 // minutes
+#define CRYPTONOTE_NOISE_EPOCH_RANGE 30 // seconds
+#define CRYPTONOTE_NOISE_MIN_DELAY 10 // seconds
+#define CRYPTONOTE_NOISE_DELAY_RANGE 5 // seconds
+#define CRYPTONOTE_NOISE_BYTES 3*1024 // 3 KiB
+#define CRYPTONOTE_NOISE_CHANNELS 2 // Max outgoing connections per zone used for noise/covert sending
+
+#define CRYPTONOTE_MAX_FRAGMENTS 20 // ~20 * NOISE_BYTES max payload size for covert/noise send
+
#define COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT 1000
#define P2P_LOCAL_WHITE_PEERLIST_LIMIT 1000
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 0ea81f19a..8ed0e526a 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -715,7 +715,6 @@ block Blockchain::pop_block_from_blockchain()
m_blocks_longhash_table.clear();
m_scan_table.clear();
m_blocks_txs_check.clear();
- m_check_txin_table.clear();
CHECK_AND_ASSERT_THROW_MES(update_next_cumulative_weight_limit(), "Error updating next cumulative weight limit");
uint64_t top_block_height;
@@ -1739,7 +1738,8 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
const uint64_t prev_height = alt_chain.size() ? prev_data.height : m_db->get_block_height(b.prev_id);
bei.height = prev_height + 1;
uint64_t block_reward = get_outs_money_amount(b.miner_tx);
- bei.already_generated_coins = block_reward + (alt_chain.size() ? prev_data.already_generated_coins : m_db->get_block_already_generated_coins(prev_height));
+ const uint64_t prev_generated_coins = alt_chain.size() ? prev_data.already_generated_coins : m_db->get_block_already_generated_coins(prev_height);
+ bei.already_generated_coins = (block_reward < (MONEY_SUPPLY - prev_generated_coins)) ? prev_generated_coins + block_reward : MONEY_SUPPLY;
// verify that the block's timestamp is within the acceptable range
// (not earlier than the median of the last X blocks)
@@ -2612,7 +2612,7 @@ void Blockchain::on_new_tx_from_block(const cryptonote::transaction &tx)
// This function overloads its sister function with
// an extra value (hash of highest block that holds an output used as input)
// as a return-by-reference.
-bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block)
+bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
@@ -2643,7 +2643,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_heigh
return true;
}
//------------------------------------------------------------------
-bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context &tvc)
+bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context &tvc) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
@@ -2752,7 +2752,7 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const
}
return false;
}
-bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys)
+bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys) const
{
PERF_TIMER(expand_transaction_2);
CHECK_AND_ASSERT_MES(tx.version == 2, false, "Transaction version is not 2");
@@ -2828,7 +2828,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr
// check_tx_input() rather than here, and use this function simply
// to iterate the inputs as necessary (splitting the task
// using threads, etc.)
-bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height)
+bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height) const
{
PERF_TIMER(check_tx_inputs);
LOG_PRINT_L3("Blockchain::" << __func__);
@@ -2947,13 +2947,6 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
}
}
}
- auto it = m_check_txin_table.find(tx_prefix_hash);
- if(it == m_check_txin_table.end())
- {
- m_check_txin_table.emplace(tx_prefix_hash, std::unordered_map<crypto::key_image, bool>());
- it = m_check_txin_table.find(tx_prefix_hash);
- assert(it != m_check_txin_table.end());
- }
std::vector<std::vector<rct::ctkey>> pubkeys(tx.vin.size());
std::vector < uint64_t > results;
@@ -2985,29 +2978,12 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
{
// basically, make sure number of inputs == number of signatures
CHECK_AND_ASSERT_MES(sig_index < tx.signatures.size(), false, "wrong transaction: not signature entry for input with index= " << sig_index);
-
-#if defined(CACHE_VIN_RESULTS)
- auto itk = it->second.find(in_to_key.k_image);
- if(itk != it->second.end())
- {
- if(!itk->second)
- {
- MERROR_VER("Failed ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index);
- return false;
- }
-
- // txin has been verified already, skip
- sig_index++;
- continue;
- }
-#endif
}
// make sure that output being spent matches up correctly with the
// signature spending it.
if (!check_tx_input(tx.version, in_to_key, tx_prefix_hash, tx.version == 1 ? tx.signatures[sig_index] : std::vector<crypto::signature>(), tx.rct_signatures, pubkeys[sig_index], pmax_used_block_height))
{
- it->second[in_to_key.k_image] = false;
MERROR_VER("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index);
if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain()
{
@@ -3030,7 +3006,6 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
check_ring_signature(tx_prefix_hash, in_to_key.k_image, pubkeys[sig_index], tx.signatures[sig_index], results[sig_index]);
if (!results[sig_index])
{
- it->second[in_to_key.k_image] = false;
MERROR_VER("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index);
if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain()
@@ -3040,7 +3015,6 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
return false;
}
- it->second[in_to_key.k_image] = true;
}
}
@@ -3058,7 +3032,6 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
for (size_t i = 0; i < tx.vin.size(); i++)
{
const txin_to_key& in_to_key = boost::get<txin_to_key>(tx.vin[i]);
- it->second[in_to_key.k_image] = results[i];
if(!failed && !results[i])
failed = true;
}
@@ -3232,7 +3205,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
}
//------------------------------------------------------------------
-void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, const std::vector<rct::ctkey> &pubkeys, const std::vector<crypto::signature>& sig, uint64_t &result)
+void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, const std::vector<rct::ctkey> &pubkeys, const std::vector<crypto::signature>& sig, uint64_t &result) const
{
std::vector<const crypto::public_key *> p_output_keys;
p_output_keys.reserve(pubkeys.size());
@@ -3418,7 +3391,7 @@ bool Blockchain::is_tx_spendtime_unlocked(uint64_t unlock_time) const
// This function locates all outputs associated with a given input (mixins)
// and validates that they exist and are usable. It also checks the ring
// signature for each input.
-bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const rct::rctSig &rct_signatures, std::vector<rct::ctkey> &output_keys, uint64_t* pmax_related_block_height)
+bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const rct::rctSig &rct_signatures, std::vector<rct::ctkey> &output_keys, uint64_t* pmax_related_block_height) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
@@ -4255,7 +4228,6 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync)
m_blocks_longhash_table.clear();
m_scan_table.clear();
m_blocks_txs_check.clear();
- m_check_txin_table.clear();
// when we're well clear of the precomputed hashes, free the memory
if (!m_blocks_hash_check.empty() && m_db->height() > m_blocks_hash_check.size() + 4096)
@@ -4544,7 +4516,6 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
m_fake_pow_calc_time = 0;
m_scan_table.clear();
- m_check_txin_table.clear();
TIME_MEASURE_FINISH(prepare);
m_fake_pow_calc_time = prepare / blocks_entry.size();
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index d95f2dceb..f58059a6d 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -548,7 +548,7 @@ namespace cryptonote
*
* @return false if any input is invalid, otherwise true
*/
- bool check_tx_inputs(transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false);
+ bool check_tx_inputs(transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false) const;
/**
* @brief get fee quantization mask
@@ -615,7 +615,7 @@ namespace cryptonote
*
* @return false if any outputs do not conform, otherwise true
*/
- bool check_tx_outputs(const transaction& tx, tx_verification_context &tvc);
+ bool check_tx_outputs(const transaction& tx, tx_verification_context &tvc) const;
/**
* @brief gets the block weight limit based on recent blocks
@@ -1020,7 +1020,6 @@ namespace cryptonote
// metadata containers
std::unordered_map<crypto::hash, std::unordered_map<crypto::key_image, std::vector<output_data_t>>> m_scan_table;
std::unordered_map<crypto::hash, crypto::hash> m_blocks_longhash_table;
- std::unordered_map<crypto::hash, std::unordered_map<crypto::key_image, bool>> m_check_txin_table;
// SHA-3 hashes for each block and for fast pow checking
std::vector<crypto::hash> m_blocks_hash_of_hashes;
@@ -1127,7 +1126,7 @@ namespace cryptonote
*
* @return false if any output is not yet unlocked, or is missing, otherwise true
*/
- bool check_tx_input(size_t tx_version,const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const rct::rctSig &rct_signatures, std::vector<rct::ctkey> &output_keys, uint64_t* pmax_related_block_height);
+ bool check_tx_input(size_t tx_version,const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const rct::rctSig &rct_signatures, std::vector<rct::ctkey> &output_keys, uint64_t* pmax_related_block_height) const;
/**
* @brief validate a transaction's inputs and their keys
@@ -1149,7 +1148,7 @@ namespace cryptonote
*
* @return false if any validation step fails, otherwise true
*/
- bool check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height = NULL);
+ bool check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height = NULL) const;
/**
* @brief performs a blockchain reorganization according to the longest chain rule
@@ -1429,7 +1428,7 @@ namespace cryptonote
* @param result false if the ring signature is invalid, otherwise true
*/
void check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image,
- const std::vector<rct::ctkey> &pubkeys, const std::vector<crypto::signature> &sig, uint64_t &result);
+ const std::vector<rct::ctkey> &pubkeys, const std::vector<crypto::signature> &sig, uint64_t &result) const;
/**
* @brief loads block hashes from compiled-in data set
@@ -1449,7 +1448,7 @@ namespace cryptonote
* can be reconstituted by the receiver. This function expands
* that implicit data.
*/
- bool expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys);
+ bool expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys) const;
/**
* @brief invalidates any cached block template
diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp
index 854f3d1c5..4cf71e558 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.cpp
+++ b/src/cryptonote_core/cryptonote_tx_utils.cpp
@@ -354,7 +354,7 @@ namespace cryptonote
if (shuffle_outs)
{
- std::shuffle(destinations.begin(), destinations.end(), std::default_random_engine(crypto::rand<unsigned int>()));
+ std::shuffle(destinations.begin(), destinations.end(), crypto::random_device{});
}
// sort ins by their key image
diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h
index d38aa7474..b03eb6e70 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.h
+++ b/src/cryptonote_core/cryptonote_tx_utils.h
@@ -83,6 +83,21 @@ namespace cryptonote
tx_destination_entry(uint64_t a, const account_public_address &ad, bool is_subaddress) : amount(a), addr(ad), is_subaddress(is_subaddress), is_integrated(false) { }
tx_destination_entry(const std::string &o, uint64_t a, const account_public_address &ad, bool is_subaddress) : original(o), amount(a), addr(ad), is_subaddress(is_subaddress), is_integrated(false) { }
+ std::string address(network_type nettype, const crypto::hash &payment_id) const
+ {
+ if (!original.empty())
+ {
+ return original;
+ }
+
+ if (is_integrated)
+ {
+ return get_account_integrated_address_as_str(nettype, addr, reinterpret_cast<const crypto::hash8 &>(payment_id));
+ }
+
+ return get_account_address_as_str(nettype, is_subaddress, addr);
+ }
+
BEGIN_SERIALIZE_OBJECT()
FIELD(original)
VARINT_FIELD(amount)
diff --git a/src/cryptonote_core/tx_sanity_check.cpp b/src/cryptonote_core/tx_sanity_check.cpp
index e95350f76..03cbb5c26 100644
--- a/src/cryptonote_core/tx_sanity_check.cpp
+++ b/src/cryptonote_core/tx_sanity_check.cpp
@@ -82,7 +82,7 @@ bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob
if (rct_indices.size() < n_indices * 8 / 10)
{
- MERROR("unique indices is only " << rct_indices.size() << "/" << n_indices);
+ MERROR("amount of unique indices is too low (amount of rct indices is " << rct_indices.size() << ", out of total " << n_indices << "indices.");
return false;
}
@@ -90,7 +90,7 @@ bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob
uint64_t median = epee::misc_utils::median(offsets);
if (median < n_available * 6 / 10)
{
- MERROR("median is " << median << "/" << n_available);
+ MERROR("median offset index is too low (median is " << median << " out of total " << n_available << "offsets). Transactions should contain a higher fraction of recent outputs.");
return false;
}
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index a1fa9484c..65115ee72 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -342,7 +342,7 @@ namespace cryptonote
if(m_core.have_block(hshd.top_id))
{
- if (target > hshd.current_height)
+ if (target > m_core.get_current_blockchain_height())
{
MINFO(context << "peer is not ahead of us and we're syncing, disconnecting");
return false;
@@ -438,7 +438,7 @@ namespace cryptonote
MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, "Received NOTIFY_NEW_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)");
if(context.m_state != cryptonote_connection_context::state_normal)
return 1;
- if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks
+ if(!is_synchronized() || m_no_sync) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks
{
LOG_DEBUG_CC(context, "Received new block while syncing, ignored");
return 1;
@@ -508,7 +508,7 @@ namespace cryptonote
MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, "Received NOTIFY_NEW_FLUFFY_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)");
if(context.m_state != cryptonote_connection_context::state_normal)
return 1;
- if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks
+ if(!is_synchronized() || m_no_sync) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks
{
LOG_DEBUG_CC(context, "Received new block while syncing, ignored");
return 1;
@@ -899,7 +899,7 @@ namespace cryptonote
// while syncing, core will lock for a long time, so we ignore
// those txes as they aren't really needed anyway, and avoid a
// long block before replying
- if(!is_synchronized())
+ if(!is_synchronized() || m_no_sync)
{
LOG_DEBUG_CC(context, "Received new tx while syncing, ignored");
return 1;
@@ -2227,69 +2227,11 @@ skip:
template<class t_core>
bool t_cryptonote_protocol_handler<t_core>::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context)
{
- const bool hide_tx_broadcast =
- 1 < m_p2p->get_zone_count() && exclude_context.m_remote_address.get_zone() == epee::net_utils::zone::invalid;
-
- if (hide_tx_broadcast)
- MDEBUG("Attempting to conceal origin of tx via anonymity network connection(s)");
+ for(auto& tx_blob : arg.txs)
+ m_core.on_transaction_relayed(tx_blob);
// no check for success, so tell core they're relayed unconditionally
- const bool pad_transactions = m_core.pad_transactions() || hide_tx_broadcast;
- size_t bytes = pad_transactions ? 9 /* header */ + 4 /* 1 + 'txs' */ + tools::get_varint_data(arg.txs.size()).size() : 0;
- for(auto tx_blob_it = arg.txs.begin(); tx_blob_it!=arg.txs.end(); ++tx_blob_it)
- {
- m_core.on_transaction_relayed(*tx_blob_it);
- if (pad_transactions)
- bytes += tools::get_varint_data(tx_blob_it->size()).size() + tx_blob_it->size();
- }
-
- if (pad_transactions)
- {
- // stuff some dummy bytes in to stay safe from traffic volume analysis
- static constexpr size_t granularity = 1024;
- size_t padding = granularity - bytes % granularity;
- const size_t overhead = 2 /* 1 + '_' */ + tools::get_varint_data(padding).size();
- if (overhead > padding)
- padding = 0;
- else
- padding -= overhead;
- arg._ = std::string(padding, ' ');
-
- std::string arg_buff;
- epee::serialization::store_t_to_binary(arg, arg_buff);
-
- // we probably lowballed the payload size a bit, so added a but too much. Fix this now.
- size_t remove = arg_buff.size() % granularity;
- if (remove > arg._.size())
- arg._.clear();
- else
- arg._.resize(arg._.size() - remove);
- // if the size of _ moved enough, we might lose byte in size encoding, we don't care
- }
-
- std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections;
- m_p2p->for_each_connection([hide_tx_broadcast, &exclude_context, &connections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)
- {
- const epee::net_utils::zone current_zone = context.m_remote_address.get_zone();
- const bool broadcast_to_peer =
- peer_id &&
- (hide_tx_broadcast != bool(current_zone == epee::net_utils::zone::public_)) &&
- exclude_context.m_connection_id != context.m_connection_id;
-
- if (broadcast_to_peer)
- connections.push_back({current_zone, context.m_connection_id});
-
- return true;
- });
-
- if (connections.empty())
- MERROR("Transaction not relayed - no" << (hide_tx_broadcast ? " privacy": "") << " peers available");
- else
- {
- std::string fullBlob;
- epee::serialization::store_t_to_binary(arg, fullBlob);
- m_p2p->relay_notify_to_list(NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan<uint8_t>(fullBlob), std::move(connections));
- }
+ m_p2p->send_txs(std::move(arg.txs), exclude_context.m_remote_address.get_zone(), exclude_context.m_connection_id, m_core.pad_transactions());
return true;
}
//------------------------------------------------------------------------------------------------------------------------
diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp
new file mode 100644
index 000000000..26cd93b5a
--- /dev/null
+++ b/src/cryptonote_protocol/levin_notify.cpp
@@ -0,0 +1,574 @@
+// Copyright (c) 2019, 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.
+
+#include "levin_notify.h"
+
+#include <boost/asio/steady_timer.hpp>
+#include <boost/system/system_error.hpp>
+#include <chrono>
+#include <deque>
+#include <stdexcept>
+
+#include "common/expect.h"
+#include "common/varint.h"
+#include "cryptonote_config.h"
+#include "crypto/random.h"
+#include "cryptonote_basic/connection_context.h"
+#include "cryptonote_protocol/cryptonote_protocol_defs.h"
+#include "net/dandelionpp.h"
+#include "p2p/net_node.h"
+
+namespace cryptonote
+{
+namespace levin
+{
+ namespace
+ {
+ constexpr 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::seconds noise_min_delay{CRYPTONOTE_NOISE_MIN_DELAY};
+ constexpr const std::chrono::seconds noise_delay_range{CRYPTONOTE_NOISE_DELAY_RANGE};
+
+ /*! 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
+ function will select a duration from [0, 3] seconds, and the increments
+ for the selection will be determined by the `steady_clock` precision
+ (typically nanoseconds).
+
+ \return A randomized duration from 0 to `range`. */
+ std::chrono::steady_clock::duration random_duration(std::chrono::steady_clock::duration range)
+ {
+ using rep = std::chrono::steady_clock::rep;
+ return std::chrono::steady_clock::duration{crypto::rand_range(rep(0), range.count())};
+ }
+
+ //! \return All outgoing connections supporting fragments in `connections`.
+ std::vector<boost::uuids::uuid> get_out_connections(connections& p2p)
+ {
+ std::vector<boost::uuids::uuid> outs;
+ outs.reserve(connection_id_reserve_size);
+
+ /* The foreach call is serialized with a lock, but should be quick due to
+ the reserve call so a strand is not used. Investigate if there is lots
+ of waiting in here. */
+
+ p2p.foreach_connection([&outs] (detail::p2p_context& context) {
+ if (!context.m_is_income)
+ outs.emplace_back(context.m_connection_id);
+ return true;
+ });
+
+ return outs;
+ }
+
+ std::string make_tx_payload(std::vector<blobdata>&& txs, const bool pad)
+ {
+ NOTIFY_NEW_TRANSACTIONS::request request{};
+ request.txs = std::move(txs);
+
+ if (pad)
+ {
+ size_t bytes = 9 /* header */ + 4 /* 1 + 'txs' */ + tools::get_varint_data(request.txs.size()).size();
+ for(auto tx_blob_it = request.txs.begin(); tx_blob_it!=request.txs.end(); ++tx_blob_it)
+ bytes += tools::get_varint_data(tx_blob_it->size()).size() + tx_blob_it->size();
+
+ // stuff some dummy bytes in to stay safe from traffic volume analysis
+ static constexpr const size_t granularity = 1024;
+ size_t padding = granularity - bytes % granularity;
+ const size_t overhead = 2 /* 1 + '_' */ + tools::get_varint_data(padding).size();
+ if (overhead > padding)
+ padding = 0;
+ else
+ padding -= overhead;
+ request._ = std::string(padding, ' ');
+
+ std::string arg_buff;
+ epee::serialization::store_t_to_binary(request, arg_buff);
+
+ // we probably lowballed the payload size a bit, so added a but too much. Fix this now.
+ size_t remove = arg_buff.size() % granularity;
+ if (remove > request._.size())
+ request._.clear();
+ else
+ request._.resize(request._.size() - remove);
+ // if the size of _ moved enough, we might lose byte in size encoding, we don't care
+ }
+
+ std::string fullBlob;
+ if (!epee::serialization::store_t_to_binary(request, fullBlob))
+ throw std::runtime_error{"Failed to serialize to epee binary format"};
+
+ return fullBlob;
+ }
+
+ /* The current design uses `asio::strand`s. The documentation isn't as clear
+ as it should be - a `strand` has an internal `mutex` and `bool`. The
+ `mutex` synchronizes thread access and the `bool` is set when a thread is
+ executing something "in the strand". Therefore, if a callback has lots of
+ work to do in a `strand`, asio can switch to some other task instead of
+ blocking 1+ threads to wait for the original thread to complete the task
+ (as is the case when client code has a `mutex` inside the callback). The
+ downside is that asio _always_ allocates for the callback, even if it can
+ be immediately executed. So if all work in a strand is minimal, a lock
+ may be better.
+
+ This code uses a strand per "zone" and a strand per "channel in a zone".
+ `dispatch` is used heavily, which means "execute immediately in _this_
+ thread if the strand is not in use, otherwise queue the callback to be
+ executed immediately after the strand completes its current task".
+ `post` is used where deferred execution to an `asio::io_service::run`
+ thread is preferred.
+
+ The strand per "zone" is useful because the levin
+ `foreach_connection` is blocked with a mutex anyway. So this primarily
+ helps with reducing blocking of a thread attempting a "flood"
+ notification. Updating/merging the outgoing connections in the
+ Dandelion++ map is also somewhat expensive.
+
+ The strand per "channel" may need a re-visit. The most "expensive" code
+ is figuring out the noise/notification to send. If levin code is
+ optimized further, it might be better to just use standard locks per
+ channel. */
+
+ //! A queue of levin messages for a noise i2p/tor link
+ struct noise_channel
+ {
+ explicit noise_channel(boost::asio::io_service& io_service)
+ : active(nullptr),
+ queue(),
+ strand(io_service),
+ next_noise(io_service),
+ connection(boost::uuids::nil_uuid())
+ {}
+
+ // `asio::io_service::strand` cannot be copied or moved
+ noise_channel(const noise_channel&) = delete;
+ noise_channel& operator=(const noise_channel&) = delete;
+
+ // Only read/write these values "inside the strand"
+
+ epee::byte_slice active;
+ std::deque<epee::byte_slice> queue;
+ boost::asio::io_service::strand strand;
+ boost::asio::steady_timer next_noise;
+ boost::uuids::uuid connection;
+ };
+ } // anonymous
+
+ namespace detail
+ {
+ struct zone
+ {
+ explicit zone(boost::asio::io_service& io_service, std::shared_ptr<connections> p2p, epee::byte_slice noise_in)
+ : p2p(std::move(p2p)),
+ noise(std::move(noise_in)),
+ next_epoch(io_service),
+ strand(io_service),
+ map(),
+ channels(),
+ connection_count(0)
+ {
+ for (std::size_t count = 0; !noise.empty() && count < CRYPTONOTE_NOISE_CHANNELS; ++count)
+ channels.emplace_back(io_service);
+ }
+
+ const std::shared_ptr<connections> p2p;
+ const epee::byte_slice noise; //!< `!empty()` means zone is using noise channels
+ boost::asio::steady_timer next_epoch;
+ boost::asio::io_service::strand strand;
+ net::dandelionpp::connection_map map;//!< Tracks outgoing uuid's for noise channels or Dandelion++ stems
+ std::deque<noise_channel> channels; //!< Never touch after init; only update elements on `noise_channel.strand`
+ std::atomic<std::size_t> connection_count; //!< Only update in strand, can be read at any time
+ };
+ } // detail
+
+ namespace
+ {
+ //! Adds a message to the sending queue of the channel.
+ class queue_covert_notify
+ {
+ std::shared_ptr<detail::zone> zone_;
+ epee::byte_slice message_; // Requires manual copy constructor
+ const std::size_t destination_;
+
+ public:
+ queue_covert_notify(std::shared_ptr<detail::zone> zone, epee::byte_slice message, std::size_t destination)
+ : zone_(std::move(zone)), message_(std::move(message)), destination_(destination)
+ {}
+
+ queue_covert_notify(queue_covert_notify&&) = default;
+ queue_covert_notify(const queue_covert_notify& source)
+ : zone_(source.zone_), message_(source.message_.clone()), destination_(source.destination_)
+ {}
+
+ //! \pre Called within `zone_->channels[destionation_].strand`.
+ void operator()()
+ {
+ if (!zone_)
+ return;
+
+ noise_channel& channel = zone_->channels.at(destination_);
+ assert(channel.strand.running_in_this_thread());
+
+ if (!channel.connection.is_nil())
+ channel.queue.push_back(std::move(message_));
+ }
+ };
+
+ //! Sends a message to every active connection
+ class flood_notify
+ {
+ std::shared_ptr<detail::zone> zone_;
+ epee::byte_slice message_; // Requires manual copy
+ boost::uuids::uuid source_;
+
+ public:
+ explicit flood_notify(std::shared_ptr<detail::zone> zone, epee::byte_slice message, const boost::uuids::uuid& source)
+ : zone_(std::move(zone)), message_(message.clone()), source_(source)
+ {}
+
+ flood_notify(flood_notify&&) = default;
+ flood_notify(const flood_notify& source)
+ : zone_(source.zone_), message_(source.message_.clone()), source_(source.source_)
+ {}
+
+ void operator()() const
+ {
+ if (!zone_ || !zone_->p2p)
+ return;
+
+ assert(zone_->strand.running_in_this_thread());
+
+ /* The foreach should be quick, but then it iterates and acquires the
+ same lock for every connection. So do in a strand because two threads
+ will ping-pong each other with cacheline invalidations. Revisit if
+ algorithm changes or the locking strategy within the levin config
+ class changes. */
+
+ std::vector<boost::uuids::uuid> connections;
+ connections.reserve(connection_id_reserve_size);
+ zone_->p2p->foreach_connection([this, &connections] (detail::p2p_context& context) {
+ if (this->source_ != context.m_connection_id)
+ connections.emplace_back(context.m_connection_id);
+ return true;
+ });
+
+ for (const boost::uuids::uuid& connection : connections)
+ zone_->p2p->send(message_.clone(), connection);
+ }
+ };
+
+ //! Updates the connection for a channel.
+ struct update_channel
+ {
+ std::shared_ptr<detail::zone> zone_;
+ const std::size_t channel_;
+ const boost::uuids::uuid connection_;
+
+ //! \pre Called within `stem_.strand`.
+ void operator()() const
+ {
+ if (!zone_)
+ return;
+
+ noise_channel& channel = zone_->channels.at(channel_);
+ assert(channel.strand.running_in_this_thread());
+ static_assert(
+ CRYPTONOTE_MAX_FRAGMENTS <= (noise_min_epoch / (noise_min_delay + noise_delay_range)),
+ "Max fragments more than the max that can be sent in an epoch"
+ );
+
+ /* This clears the active message so that a message "in-flight" is
+ restarted. DO NOT try to send the remainder of the fragments, this
+ additional send time can leak that this node was sending out a real
+ notify (tx) instead of dummy noise. */
+
+ channel.connection = connection_;
+ channel.active = nullptr;
+
+ if (connection_.is_nil())
+ channel.queue.clear();
+ }
+ };
+
+ //! Merges `out_connections_` into the existing `zone_->map`.
+ struct update_channels
+ {
+ std::shared_ptr<detail::zone> zone_;
+ std::vector<boost::uuids::uuid> out_connections_;
+
+ //! \pre Called within `zone->strand`.
+ static void post(std::shared_ptr<detail::zone> zone)
+ {
+ if (!zone)
+ return;
+
+ assert(zone->strand.running_in_this_thread());
+
+ zone->connection_count = zone->map.size();
+ for (auto id = zone->map.begin(); id != zone->map.end(); ++id)
+ {
+ const std::size_t i = id - zone->map.begin();
+ zone->channels[i].strand.post(update_channel{zone, i, *id});
+ }
+ }
+
+ //! \pre Called within `zone_->strand`.
+ void operator()()
+ {
+ if (!zone_)
+ return;
+
+ assert(zone_->strand.running_in_this_thread());
+ if (zone_->map.update(std::move(out_connections_)))
+ post(std::move(zone_));
+ }
+ };
+
+ //! Swaps out noise channels entirely; new epoch start.
+ class change_channels
+ {
+ std::shared_ptr<detail::zone> zone_;
+ net::dandelionpp::connection_map map_; // Requires manual copy constructor
+
+ public:
+ explicit change_channels(std::shared_ptr<detail::zone> zone, net::dandelionpp::connection_map map)
+ : zone_(std::move(zone)), map_(std::move(map))
+ {}
+
+ change_channels(change_channels&&) = default;
+ change_channels(const change_channels& source)
+ : zone_(source.zone_), map_(source.map_.clone())
+ {}
+
+ //! \pre Called within `zone_->strand`.
+ void operator()()
+ {
+ if (!zone_)
+ return
+
+ assert(zone_->strand.running_in_this_thread());
+
+ zone_->map = std::move(map_);
+ update_channels::post(std::move(zone_));
+ }
+ };
+
+ //! Sends a noise packet or real notification and sets timer for next call.
+ struct send_noise
+ {
+ std::shared_ptr<detail::zone> zone_;
+ const std::size_t channel_;
+
+ static void wait(const std::chrono::steady_clock::time_point start, std::shared_ptr<detail::zone> zone, const std::size_t index)
+ {
+ if (!zone)
+ return;
+
+ noise_channel& channel = zone->channels.at(index);
+ channel.next_noise.expires_at(start + noise_min_delay + random_duration(noise_delay_range));
+ channel.next_noise.async_wait(
+ channel.strand.wrap(send_noise{std::move(zone), index})
+ );
+ }
+
+ //! \pre Called within `zone_->channels[channel_].strand`.
+ void operator()(boost::system::error_code error)
+ {
+ if (!zone_ || !zone_->p2p || zone_->noise.empty())
+ return;
+
+ if (error && error != boost::system::errc::operation_canceled)
+ throw boost::system::system_error{error, "send_noise timer failed"};
+
+ assert(zone_->channels.at(channel_).strand.running_in_this_thread());
+
+ const auto start = std::chrono::steady_clock::now();
+ noise_channel& channel = zone_->channels.at(channel_);
+
+ if (!channel.connection.is_nil())
+ {
+ epee::byte_slice message = nullptr;
+ if (!channel.active.empty())
+ message = channel.active.take_slice(zone_->noise.size());
+ else if (!channel.queue.empty())
+ {
+ channel.active = channel.queue.front().clone();
+ message = channel.active.take_slice(zone_->noise.size());
+ }
+ else
+ message = zone_->noise.clone();
+
+ if (zone_->p2p->send(std::move(message), channel.connection))
+ {
+ if (!channel.queue.empty() && channel.active.empty())
+ channel.queue.pop_front();
+ }
+ else
+ {
+ channel.active = nullptr;
+ channel.connection = boost::uuids::nil_uuid();
+ zone_->strand.post(
+ update_channels{zone_, get_out_connections(*zone_->p2p)}
+ );
+ }
+ }
+
+ wait(start, std::move(zone_), channel_);
+ }
+ };
+
+ //! Prepares connections for new channel epoch and sets timer for next epoch
+ struct start_epoch
+ {
+ // Variables allow for Dandelion++ extension
+ std::shared_ptr<detail::zone> zone_;
+ std::chrono::seconds min_epoch_;
+ std::chrono::seconds epoch_range_;
+ std::size_t count_;
+
+ //! \pre Should not be invoked within any strand to prevent blocking.
+ void operator()(const boost::system::error_code error = {})
+ {
+ if (!zone_ || !zone_->p2p)
+ return;
+
+ if (error && error != boost::system::errc::operation_canceled)
+ throw boost::system::system_error{error, "start_epoch timer failed"};
+
+ const auto start = std::chrono::steady_clock::now();
+ zone_->strand.dispatch(
+ change_channels{zone_, net::dandelionpp::connection_map{get_out_connections(*(zone_->p2p)), count_}}
+ );
+
+ detail::zone& alias = *zone_;
+ alias.next_epoch.expires_at(start + min_epoch_ + random_duration(epoch_range_));
+ alias.next_epoch.async_wait(start_epoch{std::move(*this)});
+ }
+ };
+ } // anonymous
+
+ notify::notify(boost::asio::io_service& service, std::shared_ptr<connections> p2p, epee::byte_slice noise)
+ : zone_(std::make_shared<detail::zone>(service, std::move(p2p), std::move(noise)))
+ {
+ if (!zone_->p2p)
+ throw std::logic_error{"cryptonote::levin::notify cannot have nullptr p2p argument"};
+
+ if (!zone_->noise.empty())
+ {
+ const auto now = std::chrono::steady_clock::now();
+ start_epoch{zone_, noise_min_epoch, noise_epoch_range, CRYPTONOTE_NOISE_CHANNELS}();
+ for (std::size_t channel = 0; channel < zone_->channels.size(); ++channel)
+ send_noise::wait(now, zone_, channel);
+ }
+ }
+
+ notify::~notify() noexcept
+ {}
+
+ notify::status notify::get_status() const noexcept
+ {
+ if (!zone_)
+ return {false, false};
+
+ return {!zone_->noise.empty(), CRYPTONOTE_NOISE_CHANNELS <= zone_->connection_count};
+ }
+
+ void notify::new_out_connection()
+ {
+ if (!zone_ || zone_->noise.empty() || CRYPTONOTE_NOISE_CHANNELS <= zone_->connection_count)
+ return;
+
+ zone_->strand.dispatch(
+ update_channels{zone_, get_out_connections(*(zone_->p2p))}
+ );
+ }
+
+ void notify::run_epoch()
+ {
+ if (!zone_)
+ return;
+ zone_->next_epoch.cancel();
+ }
+
+ void notify::run_stems()
+ {
+ if (!zone_)
+ return;
+
+ for (noise_channel& channel : zone_->channels)
+ channel.next_noise.cancel();
+ }
+
+ bool notify::send_txs(std::vector<cryptonote::blobdata> txs, const boost::uuids::uuid& source, const bool pad_txs)
+ {
+ if (!zone_)
+ return false;
+
+ if (!zone_->noise.empty() && !zone_->channels.empty())
+ {
+ // covert send in "noise" channel
+ static_assert(
+ 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);
+ epee::byte_slice message = epee::levin::make_fragmented_notify(
+ zone_->noise, NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan<std::uint8_t>(payload)
+ );
+ if (CRYPTONOTE_MAX_FRAGMENTS * zone_->noise.size() < message.size())
+ {
+ MERROR("notify::send_txs provided message exceeding covert fragment size");
+ return false;
+ }
+
+ for (std::size_t channel = 0; channel < zone_->channels.size(); ++channel)
+ {
+ zone_->channels[channel].strand.dispatch(
+ queue_covert_notify{zone_, message.clone(), channel}
+ );
+ }
+ }
+ else
+ {
+ const std::string payload = make_tx_payload(std::move(txs), pad_txs);
+ epee::byte_slice message =
+ epee::levin::make_notify(NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan<std::uint8_t>(payload));
+
+ // traditional monero send technique
+ zone_->strand.dispatch(flood_notify{zone_, std::move(message), source});
+ }
+
+ return true;
+ }
+} // levin
+} // net
diff --git a/src/cryptonote_protocol/levin_notify.h b/src/cryptonote_protocol/levin_notify.h
new file mode 100644
index 000000000..82d22680a
--- /dev/null
+++ b/src/cryptonote_protocol/levin_notify.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2019, 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 <boost/asio/io_service.hpp>
+#include <boost/uuid/uuid.hpp>
+#include <memory>
+#include <vector>
+
+#include "byte_slice.h"
+#include "cryptonote_basic/blobdatatype.h"
+#include "net/enums.h"
+#include "span.h"
+
+namespace epee
+{
+namespace levin
+{
+ template<typename> class async_protocol_handler_config;
+}
+}
+
+namespace nodetool
+{
+ template<typename> struct p2p_connection_context_t;
+}
+
+namespace cryptonote
+{
+ struct cryptonote_connection_context;
+}
+
+namespace cryptonote
+{
+namespace levin
+{
+ namespace detail
+ {
+ using p2p_context = nodetool::p2p_connection_context_t<cryptonote::cryptonote_connection_context>;
+ struct zone; //!< Internal data needed for zone notifications
+ } // detail
+
+ using connections = epee::levin::async_protocol_handler_config<detail::p2p_context>;
+
+ //! Provides tx notification privacy
+ class notify
+ {
+ std::shared_ptr<detail::zone> zone_;
+
+ public:
+ struct status
+ {
+ bool has_noise;
+ bool connections_filled;
+ };
+
+ //! Construct an instance that cannot notify.
+ notify() noexcept
+ : zone_(nullptr)
+ {}
+
+ //! Construct an instance with available notification `zones`.
+ explicit notify(boost::asio::io_service& service, std::shared_ptr<connections> p2p, epee::byte_slice noise);
+
+ notify(const notify&) = delete;
+ notify(notify&&) = default;
+
+ ~notify() noexcept;
+
+ notify& operator=(const notify&) = delete;
+ notify& operator=(notify&&) = default;
+
+ //! \return Status information for zone selection.
+ status get_status() const noexcept;
+
+ //! Probe for new outbound connection - skips if not needed.
+ void new_out_connection();
+
+ //! Run the logic for the next epoch immediately. Only use in testing.
+ void run_epoch();
+
+ //! Run the logic for the next stem timeout imemdiately. Only use in testing.
+ void run_stems();
+
+ /*! Send txs using `cryptonote_protocol_defs.h` payload format wrapped in a
+ levin header. The message will be sent in a "discreet" manner if
+ enabled - if `!noise.empty()` then the `command`/`payload` will be
+ queued to send at the next available noise interval. Otherwise, a
+ standard Monero flood notification will be used.
+
+ \note Eventually Dandelion++ stem sending will be used here when
+ enabled.
+
+ \param txs The transactions that need to be serialized and relayed.
+ \param source The source of the notification. `is_nil()` indicates this
+ node is the source. Dandelion++ will use this to map a source to a
+ particular stem.
+ \param pad_txs A request to pad txs to help conceal origin via
+ statistical analysis. Ignored if noise was enabled during
+ construction.
+
+ \return True iff the notification is queued for sending. */
+ bool send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source, bool pad_txs);
+ };
+} // levin
+} // net