Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a3bef15b9 | |||
| 44c93bb47a | |||
| c3fc96f0cf | |||
| 07f6ecd227 | |||
| 4985afddb3 | |||
| 4191dc8981 | |||
| 32581fb223 | |||
| f3dd25c0e0 | |||
| 33dc825d6f | |||
| 91f1be9ea2 | |||
| 35186b1b84 | |||
| bddd5653e1 | |||
| b36c4f354b | |||
| 1ba4c8dbc2 | |||
| 92d19c48da | |||
| 01606ee52e | |||
| 81d2389c24 | |||
| ef64c4e22f | |||
| 7fa01f342c | |||
| 4603ec9765 | |||
| 0be71bcea9 | |||
| 1a627e1f89 | |||
| c1229a4432 | |||
| 9f68111b69 | |||
| d1e79f2086 | |||
| 8965489acb | |||
| d6953406c9 | |||
| 5090cdc9f5 | |||
| df1061c87d | |||
| f55f3fe81c | |||
| 76cc82c292 | |||
| 81a23c73d2 | |||
| 4b86583492 | |||
| 30918a27ab | |||
| 940b042e28 | |||
| c028f4c4d8 | |||
| f9a9d2ee28 | |||
| 81aaa4ec7b | |||
| 6e594e53bb | |||
| e88b9170db | |||
| 39e30d5481 | |||
| 1120df3c53 | |||
| 42403c7acc | |||
| fa63d4b3f0 | |||
| 263579b217 | |||
| 58cde83fb0 |
@@ -134,8 +134,8 @@ Dates are provided in the format YYYY-MM-DD.
|
||||
| 1788000 | 2019-03-09 | v10 | v0.14.0.0 | v0.14.1.2 | New PoW based on Cryptonight-R, new block weight algorithm, slightly more efficient RingCT format
|
||||
| 1788720 | 2019-03-10 | v11 | v0.14.0.0 | v0.14.1.2 | forbid old RingCT transaction format
|
||||
| 1978433 | 2019-11-30* | v12 | v0.15.0.0 | v0.16.0.0 | New PoW based on RandomX, only allow >= 2 outputs, change to the block median used to calculate penalty, v1 coinbases are forbidden, rct sigs in coinbase forbidden, 10 block lock time for incoming outputs
|
||||
| 2210000 | 2020-10-17 | v13 | v0.17.0.0 | v0.17.1.0 | New CLSAG transaction format
|
||||
| 2210720 | 2020-10-18 | v14 | v0.17.0.0 | v0.17.1.0 | forbid old MLSAG transaction format
|
||||
| 2210000 | 2020-10-17 | v13 | v0.17.0.0 | v0.17.1.2 | New CLSAG transaction format
|
||||
| 2210720 | 2020-10-18 | v14 | v0.17.1.1 | v0.17.1.2 | forbid old MLSAG transaction format
|
||||
| XXXXXXX | XXX-XX-XX | XXX | vX.XX.X.X | vX.XX.X.X | XXX |
|
||||
|
||||
X's indicate that these details have not been determined as of commit date.
|
||||
@@ -295,7 +295,7 @@ Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch (
|
||||
```bash
|
||||
git clone https://github.com/monero-project/monero.git
|
||||
cd monero
|
||||
git checkout tags/v0.17.1.0
|
||||
git checkout tags/v0.17.1.2
|
||||
```
|
||||
|
||||
* Build:
|
||||
@@ -412,10 +412,10 @@ application.
|
||||
cd monero
|
||||
```
|
||||
|
||||
* If you would like a specific [version/tag](https://github.com/monero-project/monero/tags), do a git checkout for that version. eg. 'v0.17.1.0'. If you don't care about the version and just want binaries from master, skip this step:
|
||||
* If you would like a specific [version/tag](https://github.com/monero-project/monero/tags), do a git checkout for that version. eg. 'v0.17.1.2'. If you don't care about the version and just want binaries from master, skip this step:
|
||||
|
||||
```bash
|
||||
git checkout v0.17.1.0
|
||||
git checkout v0.17.1.2
|
||||
```
|
||||
|
||||
* If you are on a 64-bit system, run:
|
||||
|
||||
@@ -126,7 +126,7 @@ Setup for LXC:
|
||||
|
||||
```bash
|
||||
GH_USER=fluffypony
|
||||
VERSION=v0.17.1.0
|
||||
VERSION=v0.17.1.2
|
||||
|
||||
./gitian-build.py --setup $GH_USER $VERSION
|
||||
```
|
||||
@@ -182,7 +182,7 @@ If you chose to do detached signing using `--detach-sign` above (recommended), y
|
||||
|
||||
```bash
|
||||
GH_USER=fluffypony
|
||||
VERSION=v0.17.1.0
|
||||
VERSION=v0.17.1.2
|
||||
|
||||
gpg --detach-sign ${VERSION}-linux/${GH_USER}/monero-linux-*-build.assert
|
||||
gpg --detach-sign ${VERSION}-win/${GH_USER}/monero-win-*-build.assert
|
||||
|
||||
@@ -47,6 +47,7 @@ int main(int argc, char* argv[])
|
||||
epee::string_tools::set_module_name_and_folder(argv[0]);
|
||||
|
||||
uint32_t log_level = 0;
|
||||
uint64_t block_start = 0;
|
||||
uint64_t block_stop = 0;
|
||||
bool blocks_dat = false;
|
||||
|
||||
@@ -58,6 +59,7 @@ int main(int argc, char* argv[])
|
||||
po::options_description desc_cmd_sett("Command line options and settings options");
|
||||
const command_line::arg_descriptor<std::string> arg_output_file = {"output-file", "Specify output file", "", true};
|
||||
const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""};
|
||||
const command_line::arg_descriptor<uint64_t> arg_block_start = {"block-start", "Start at block number", block_start};
|
||||
const command_line::arg_descriptor<uint64_t> arg_block_stop = {"block-stop", "Stop at block number", block_stop};
|
||||
const command_line::arg_descriptor<bool> arg_blocks_dat = {"blocksdat", "Output in blocks.dat format", blocks_dat};
|
||||
|
||||
@@ -67,6 +69,7 @@ int main(int argc, char* argv[])
|
||||
command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on);
|
||||
command_line::add_arg(desc_cmd_sett, cryptonote::arg_stagenet_on);
|
||||
command_line::add_arg(desc_cmd_sett, arg_log_level);
|
||||
command_line::add_arg(desc_cmd_sett, arg_block_start);
|
||||
command_line::add_arg(desc_cmd_sett, arg_block_stop);
|
||||
command_line::add_arg(desc_cmd_sett, arg_blocks_dat);
|
||||
|
||||
@@ -97,6 +100,7 @@ int main(int argc, char* argv[])
|
||||
mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str());
|
||||
else
|
||||
mlog_set_log(std::string(std::to_string(log_level) + ",bcutil:INFO").c_str());
|
||||
block_start = command_line::get_arg(vm, arg_block_start);
|
||||
block_stop = command_line::get_arg(vm, arg_block_stop);
|
||||
|
||||
LOG_PRINT_L0("Starting...");
|
||||
@@ -178,7 +182,7 @@ int main(int argc, char* argv[])
|
||||
else
|
||||
{
|
||||
BootstrapFile bootstrap;
|
||||
r = bootstrap.store_blockchain_raw(core_storage, NULL, output_file_path, block_stop);
|
||||
r = bootstrap.store_blockchain_raw(core_storage, NULL, output_file_path, block_start, block_stop);
|
||||
}
|
||||
CHECK_AND_ASSERT_MES(r, 1, "Failed to export blockchain raw data");
|
||||
LOG_PRINT_L0("Blockchain raw data exported OK");
|
||||
|
||||
@@ -227,6 +227,7 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t block_first, block_last;
|
||||
uint64_t start_height = 1, seek_height;
|
||||
if (opt_resume)
|
||||
start_height = core.get_blockchain_storage().get_current_blockchain_height();
|
||||
@@ -235,10 +236,10 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path
|
||||
BootstrapFile bootstrap;
|
||||
std::streampos pos;
|
||||
// BootstrapFile bootstrap(import_file_path);
|
||||
uint64_t total_source_blocks = bootstrap.count_blocks(import_file_path, pos, seek_height);
|
||||
MINFO("bootstrap file last block number: " << total_source_blocks-1 << " (zero-based height) total blocks: " << total_source_blocks);
|
||||
uint64_t total_source_blocks = bootstrap.count_blocks(import_file_path, pos, seek_height, block_first);
|
||||
MINFO("bootstrap file last block number: " << total_source_blocks+block_first-1 << " (zero-based height) total blocks: " << total_source_blocks);
|
||||
|
||||
if (total_source_blocks-1 <= start_height)
|
||||
if (total_source_blocks+block_first-1 <= start_height)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -260,7 +261,8 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path
|
||||
|
||||
// 4 byte magic + (currently) 1024 byte header structures
|
||||
uint8_t major_version, minor_version;
|
||||
bootstrap.seek_to_first_chunk(import_file, major_version, minor_version);
|
||||
uint64_t dummy;
|
||||
bootstrap.seek_to_first_chunk(import_file, major_version, minor_version, dummy, dummy);
|
||||
|
||||
std::string str1;
|
||||
char buffer1[1024];
|
||||
@@ -275,7 +277,7 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path
|
||||
|
||||
if (! block_stop)
|
||||
{
|
||||
block_stop = total_source_blocks - 1;
|
||||
block_stop = total_source_blocks+block_first - 1;
|
||||
}
|
||||
|
||||
// These are what we'll try to use, and they don't have to be a determination
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace
|
||||
|
||||
|
||||
|
||||
bool BootstrapFile::open_writer(const boost::filesystem::path& file_path)
|
||||
bool BootstrapFile::open_writer(const boost::filesystem::path& file_path, uint64_t start_block, uint64_t stop_block)
|
||||
{
|
||||
const boost::filesystem::path dir_path = file_path.parent_path();
|
||||
if (!dir_path.empty())
|
||||
@@ -78,7 +78,7 @@ bool BootstrapFile::open_writer(const boost::filesystem::path& file_path)
|
||||
m_raw_data_file = new std::ofstream();
|
||||
|
||||
bool do_initialize_file = false;
|
||||
uint64_t num_blocks = 0;
|
||||
uint64_t num_blocks = 0, block_first = 0;
|
||||
|
||||
if (! boost::filesystem::exists(file_path))
|
||||
{
|
||||
@@ -88,10 +88,12 @@ bool BootstrapFile::open_writer(const boost::filesystem::path& file_path)
|
||||
}
|
||||
else
|
||||
{
|
||||
num_blocks = count_blocks(file_path.string());
|
||||
MDEBUG("appending to existing file with height: " << num_blocks-1 << " total blocks: " << num_blocks);
|
||||
std::streampos dummy_pos;
|
||||
uint64_t dummy_height = 0;
|
||||
num_blocks = count_blocks(file_path.string(), dummy_pos, dummy_height, block_first);
|
||||
MDEBUG("appending to existing file with height: " << num_blocks+block_first-1 << " total blocks: " << num_blocks);
|
||||
}
|
||||
m_height = num_blocks;
|
||||
m_height = num_blocks+block_first;
|
||||
|
||||
if (do_initialize_file)
|
||||
m_raw_data_file->open(file_path.string(), std::ios_base::binary | std::ios_base::out | std::ios::trunc);
|
||||
@@ -106,13 +108,12 @@ bool BootstrapFile::open_writer(const boost::filesystem::path& file_path)
|
||||
return false;
|
||||
|
||||
if (do_initialize_file)
|
||||
initialize_file();
|
||||
initialize_file(start_block, stop_block);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool BootstrapFile::initialize_file()
|
||||
bool BootstrapFile::initialize_file(uint64_t first_block, uint64_t last_block)
|
||||
{
|
||||
const uint32_t file_magic = blockchain_raw_magic;
|
||||
|
||||
@@ -129,8 +130,8 @@ bool BootstrapFile::initialize_file()
|
||||
bfi.header_size = header_size;
|
||||
|
||||
bootstrap::blocks_info bbi;
|
||||
bbi.block_first = 0;
|
||||
bbi.block_last = 0;
|
||||
bbi.block_first = first_block;
|
||||
bbi.block_last = last_block;
|
||||
bbi.block_last_pos = 0;
|
||||
|
||||
buffer_type buffer2;
|
||||
@@ -261,7 +262,7 @@ bool BootstrapFile::close()
|
||||
}
|
||||
|
||||
|
||||
bool BootstrapFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_memory_pool* _tx_pool, boost::filesystem::path& output_file, uint64_t requested_block_stop)
|
||||
bool BootstrapFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_memory_pool* _tx_pool, boost::filesystem::path& output_file, uint64_t start_block, uint64_t requested_block_stop)
|
||||
{
|
||||
uint64_t num_blocks_written = 0;
|
||||
m_max_chunk = 0;
|
||||
@@ -269,17 +270,11 @@ bool BootstrapFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_mem
|
||||
m_tx_pool = _tx_pool;
|
||||
uint64_t progress_interval = 100;
|
||||
MINFO("Storing blocks raw data...");
|
||||
if (!BootstrapFile::open_writer(output_file))
|
||||
{
|
||||
MFATAL("failed to open raw file for write");
|
||||
return false;
|
||||
}
|
||||
block b;
|
||||
|
||||
// block_start, block_stop use 0-based height. m_height uses 1-based height. So to resume export
|
||||
// from last exported block, block_start doesn't need to add 1 here, as it's already at the next
|
||||
// height.
|
||||
uint64_t block_start = m_height;
|
||||
uint64_t block_stop = 0;
|
||||
MINFO("source blockchain height: " << m_blockchain_storage->get_current_blockchain_height()-1);
|
||||
if ((requested_block_stop > 0) && (requested_block_stop < m_blockchain_storage->get_current_blockchain_height()))
|
||||
@@ -292,6 +287,13 @@ bool BootstrapFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_mem
|
||||
block_stop = m_blockchain_storage->get_current_blockchain_height() - 1;
|
||||
MINFO("Using block height of source blockchain: " << block_stop);
|
||||
}
|
||||
if (!BootstrapFile::open_writer(output_file, start_block, block_stop))
|
||||
{
|
||||
MFATAL("failed to open raw file for write");
|
||||
return false;
|
||||
}
|
||||
uint64_t block_start = m_height ? m_height : start_block;
|
||||
MINFO("Starting block height: " << block_start);
|
||||
for (m_cur_height = block_start; m_cur_height <= block_stop; ++m_cur_height)
|
||||
{
|
||||
// this method's height refers to 0-based height (genesis block = height 0)
|
||||
@@ -323,7 +325,8 @@ bool BootstrapFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_mem
|
||||
return BootstrapFile::close();
|
||||
}
|
||||
|
||||
uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file, uint8_t &major_version, uint8_t &minor_version)
|
||||
uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file, uint8_t &major_version, uint8_t &minor_version,
|
||||
uint64_t &block_first, uint64_t &block_last)
|
||||
{
|
||||
uint32_t file_magic;
|
||||
|
||||
@@ -368,11 +371,35 @@ uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file, uint8_t
|
||||
MINFO("bootstrap magic size: " << sizeof(file_magic));
|
||||
MINFO("bootstrap header size: " << bfi.header_size);
|
||||
|
||||
uint32_t buflen_blocks_info;
|
||||
|
||||
import_file.read(buf1, sizeof(buflen_blocks_info));
|
||||
str1.assign(buf1, sizeof(buflen_blocks_info));
|
||||
if (! import_file)
|
||||
throw std::runtime_error("Error reading expected number of bytes");
|
||||
if (! ::serialization::parse_binary(str1, buflen_blocks_info))
|
||||
throw std::runtime_error("Error in deserialization of buflen_blocks_info");
|
||||
MINFO("bootstrap::blocks_info size: " << buflen_blocks_info);
|
||||
|
||||
if (buflen_blocks_info > sizeof(buf1))
|
||||
throw std::runtime_error("Error: bootstrap::blocks_info size exceeds buffer size");
|
||||
import_file.read(buf1, buflen_blocks_info);
|
||||
if (! import_file)
|
||||
throw std::runtime_error("Error reading expected number of bytes");
|
||||
str1.assign(buf1, buflen_blocks_info);
|
||||
bootstrap::blocks_info bbi;
|
||||
if (! ::serialization::parse_binary(str1, bbi))
|
||||
throw std::runtime_error("Error in deserialization of bootstrap::blocks_info");
|
||||
MINFO("bootstrap first block:" << bbi.block_first);
|
||||
MINFO("bootstrap last block:" << bbi.block_last);
|
||||
|
||||
uint64_t full_header_size = sizeof(file_magic) + bfi.header_size;
|
||||
import_file.seekg(full_header_size);
|
||||
|
||||
major_version = bfi.major_version;
|
||||
minor_version = bfi.minor_version;
|
||||
block_first = bbi.block_first;
|
||||
block_last = bbi.block_last;
|
||||
return full_header_size;
|
||||
}
|
||||
|
||||
@@ -436,13 +463,14 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path)
|
||||
{
|
||||
std::streampos dummy_pos;
|
||||
uint64_t dummy_height = 0;
|
||||
return count_blocks(import_file_path, dummy_pos, dummy_height);
|
||||
return count_blocks(import_file_path, dummy_pos, dummy_height, dummy_height);
|
||||
}
|
||||
|
||||
// If seek_height is non-zero on entry, return a stream position <= this height when finished.
|
||||
// And return the actual height corresponding to this position. Allows the caller to locate its
|
||||
// starting position without having to reread the entire file again.
|
||||
uint64_t BootstrapFile::count_blocks(const std::string& import_file_path, std::streampos &start_pos, uint64_t& seek_height)
|
||||
uint64_t BootstrapFile::count_blocks(const std::string& import_file_path, std::streampos &start_pos,
|
||||
uint64_t& seek_height, uint64_t &block_first)
|
||||
{
|
||||
boost::filesystem::path raw_file_path(import_file_path);
|
||||
boost::system::error_code ec;
|
||||
@@ -464,7 +492,8 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path, std::s
|
||||
|
||||
uint64_t full_header_size; // 4 byte magic + length of header structures
|
||||
uint8_t major_version, minor_version;
|
||||
full_header_size = seek_to_first_chunk(import_file, major_version, minor_version);
|
||||
uint64_t block_last;
|
||||
full_header_size = seek_to_first_chunk(import_file, major_version, minor_version, block_first, block_last);
|
||||
|
||||
MINFO("Scanning blockchain from bootstrap file...");
|
||||
bool quit = false;
|
||||
@@ -473,11 +502,11 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path, std::s
|
||||
|
||||
while (! quit)
|
||||
{
|
||||
if (start_height && h + progress_interval >= start_height - 1)
|
||||
if (start_height && h + block_first + progress_interval >= start_height - 1)
|
||||
{
|
||||
start_height = 0;
|
||||
start_pos = import_file.tellg();
|
||||
seek_height = h;
|
||||
seek_height = h + block_first;
|
||||
}
|
||||
bytes_read += count_bytes(import_file, progress_interval, blocks, quit);
|
||||
h += blocks;
|
||||
|
||||
@@ -57,12 +57,12 @@ class BootstrapFile
|
||||
public:
|
||||
|
||||
uint64_t count_bytes(std::ifstream& import_file, uint64_t blocks, uint64_t& h, bool& quit);
|
||||
uint64_t count_blocks(const std::string& dir_path, std::streampos& start_pos, uint64_t& seek_height);
|
||||
uint64_t count_blocks(const std::string& dir_path, std::streampos& start_pos, uint64_t& seek_height, uint64_t& block_first);
|
||||
uint64_t count_blocks(const std::string& dir_path);
|
||||
uint64_t seek_to_first_chunk(std::ifstream& import_file, uint8_t &major_version, uint8_t &minor_version);
|
||||
uint64_t seek_to_first_chunk(std::ifstream& import_file, uint8_t &major_version, uint8_t &minor_version, uint64_t &block_first, uint64_t &block_last);
|
||||
|
||||
bool store_blockchain_raw(cryptonote::Blockchain* cs, cryptonote::tx_memory_pool* txp,
|
||||
boost::filesystem::path& output_file, uint64_t use_block_height=0);
|
||||
boost::filesystem::path& output_file, uint64_t start_block=0, uint64_t stop_block=0);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -75,8 +75,8 @@ protected:
|
||||
boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>* m_output_stream;
|
||||
|
||||
// open export file for write
|
||||
bool open_writer(const boost::filesystem::path& file_path);
|
||||
bool initialize_file();
|
||||
bool open_writer(const boost::filesystem::path& file_path, uint64_t start_block, uint64_t stop_block);
|
||||
bool initialize_file(uint64_t start_block, uint64_t stop_block);
|
||||
bool close();
|
||||
void write_block(block& block);
|
||||
void flush_chunk();
|
||||
|
||||
Binary file not shown.
@@ -239,6 +239,8 @@ namespace cryptonote
|
||||
ADD_CHECKPOINT2(2182500, "0d22b5f81982eff21d094af9e821dc2007e6342069e3b1a37b15d97646353124", "0xead4a874083492");
|
||||
ADD_CHECKPOINT2(2193000, "6e91b917a40309f89f75f2c8d7be5a6d1a3c425634f07f7d1867bd32d2e602ed", "0xf085140f17389d");
|
||||
ADD_CHECKPOINT2(2206500, "dd3c8590f33eaa546a4ce69d02f27fd58a2f115cd32d733bd9426f8278f0cb8a", "0xf8725275799f0d");
|
||||
ADD_CHECKPOINT2(2210500, "ed17259215ac6aabe6e8252b5b5eff613d2e69cc2111173e567109aa78301911", "0xfbcb50a9a6a433");
|
||||
ADD_CHECKPOINT2(2224000, "e34bc71301600df96d08aaa6b0bde932cba1b06a3dd57076e3f664a078810a80", "0x1052fc256fceb6f");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
#define P2P_LOCAL_WHITE_PEERLIST_LIMIT 1000
|
||||
#define P2P_LOCAL_GRAY_PEERLIST_LIMIT 5000
|
||||
|
||||
#define P2P_DEFAULT_CONNECTIONS_COUNT 8
|
||||
#define P2P_DEFAULT_CONNECTIONS_COUNT 12
|
||||
#define P2P_DEFAULT_HANDSHAKE_INTERVAL 60 //secondes
|
||||
#define P2P_DEFAULT_PACKET_MAX_SIZE 50000000 //50000000 bytes maximum packet size
|
||||
#define P2P_DEFAULT_PEERS_IN_HANDSHAKE 250
|
||||
|
||||
@@ -3068,9 +3068,21 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context
|
||||
if (tx.version >= 2) {
|
||||
if (tx.rct_signatures.type <= rct::RCTTypeBulletproof2)
|
||||
{
|
||||
MERROR_VER("Ringct type " << (unsigned)tx.rct_signatures.type << " is not allowed from v" << (HF_VERSION_CLSAG + 1));
|
||||
tvc.m_invalid_output = true;
|
||||
return false;
|
||||
// two MLSAG txes went in due to a bug with txes that went into the txpool before the fork, grandfather them in
|
||||
static const char * grandfathered[2] = { "c5151944f0583097ba0c88cd0f43e7fabb3881278aa2f73b3b0a007c5d34e910", "6f2f117cde6fbcf8d4a6ef8974fcac744726574ac38cf25d3322c996b21edd4c" };
|
||||
crypto::hash h0, h1;
|
||||
epee::string_tools::hex_to_pod(grandfathered[0], h0);
|
||||
epee::string_tools::hex_to_pod(grandfathered[1], h1);
|
||||
if (cryptonote::get_transaction_hash(tx) == h0 || cryptonote::get_transaction_hash(tx) == h1)
|
||||
{
|
||||
MDEBUG("Grandfathering cryptonote::get_transaction_hash(tx) in");
|
||||
}
|
||||
else
|
||||
{
|
||||
MERROR_VER("Ringct type " << (unsigned)tx.rct_signatures.type << " is not allowed from v" << (HF_VERSION_CLSAG + 1));
|
||||
tvc.m_invalid_output = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5364,7 +5376,7 @@ void Blockchain::cancel()
|
||||
}
|
||||
|
||||
#if defined(PER_BLOCK_CHECKPOINT)
|
||||
static const char expected_block_hashes_hash[] = "49db4105aadc7c296ffbf5d56de1785361684671544489f3c5e56cb40ef92a28";
|
||||
static const char expected_block_hashes_hash[] = "1d166275537bfbb5841d98da298c65089f3006d751776b2dc8a4f0275ef0ce58";
|
||||
void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints)
|
||||
{
|
||||
if (get_checkpoints == nullptr || !m_fast_sync)
|
||||
|
||||
@@ -1091,6 +1091,13 @@ namespace cryptonote
|
||||
|
||||
//epee::net_utils::network_throttle_manager::get_global_throttle_inreq().logger_handle_net("log/dr-monero/net/req-all.data", sec, get_avg_block_size());
|
||||
|
||||
if(arg.blocks.empty())
|
||||
{
|
||||
LOG_ERROR_CCONTEXT("sent wrong NOTIFY_HAVE_OBJECTS: no blocks");
|
||||
drop_connection(context, true, false);
|
||||
++m_sync_bad_spans_downloaded;
|
||||
return 1;
|
||||
}
|
||||
if(context.m_last_response_height > arg.current_blockchain_height)
|
||||
{
|
||||
LOG_ERROR_CCONTEXT("sent wrong NOTIFY_HAVE_OBJECTS: arg.m_current_blockchain_height=" << arg.current_blockchain_height
|
||||
|
||||
@@ -241,8 +241,8 @@ namespace levin
|
||||
strand(io_service),
|
||||
map(),
|
||||
channels(),
|
||||
flush_time(std::chrono::steady_clock::time_point::max()),
|
||||
connection_count(0),
|
||||
flush_callbacks(0),
|
||||
is_public(is_public),
|
||||
pad_txs(pad_txs),
|
||||
fluffing(false)
|
||||
@@ -258,8 +258,8 @@ namespace levin
|
||||
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::chrono::steady_clock::time_point flush_time; //!< Next expected Dandelion++ fluff flush
|
||||
std::atomic<std::size_t> connection_count; //!< Only update in strand, can be read at any time
|
||||
std::uint32_t flush_callbacks; //!< Number of active fluff flush callbacks queued
|
||||
const bool is_public; //!< Zone is public ipv4/ipv6 connections
|
||||
const bool pad_txs; //!< Pad txs to the next boundary for privacy
|
||||
bool fluffing; //!< Zone is in Dandelion++ fluff epoch
|
||||
@@ -305,7 +305,6 @@ namespace levin
|
||||
struct fluff_flush
|
||||
{
|
||||
std::shared_ptr<detail::zone> zone_;
|
||||
std::chrono::steady_clock::time_point flush_time_;
|
||||
|
||||
static void queue(std::shared_ptr<detail::zone> zone, const std::chrono::steady_clock::time_point flush_time)
|
||||
{
|
||||
@@ -313,28 +312,21 @@ namespace levin
|
||||
assert(zone->strand.running_in_this_thread());
|
||||
|
||||
detail::zone& this_zone = *zone;
|
||||
this_zone.flush_time = flush_time;
|
||||
++this_zone.flush_callbacks;
|
||||
this_zone.flush_txs.expires_at(flush_time);
|
||||
this_zone.flush_txs.async_wait(this_zone.strand.wrap(fluff_flush{std::move(zone), flush_time}));
|
||||
this_zone.flush_txs.async_wait(this_zone.strand.wrap(fluff_flush{std::move(zone)}));
|
||||
}
|
||||
|
||||
void operator()(const boost::system::error_code error)
|
||||
{
|
||||
if (!zone_ || !zone_->p2p)
|
||||
if (!zone_ || !zone_->flush_callbacks || --zone_->flush_callbacks || !zone_->p2p)
|
||||
return;
|
||||
|
||||
assert(zone_->strand.running_in_this_thread());
|
||||
|
||||
const bool timer_error = bool(error);
|
||||
if (timer_error)
|
||||
{
|
||||
if (error != boost::system::errc::operation_canceled)
|
||||
throw boost::system::system_error{error, "fluff_flush timer failed"};
|
||||
|
||||
// new timer canceled this one set in future
|
||||
if (zone_->flush_time < flush_time_)
|
||||
return;
|
||||
}
|
||||
if (timer_error && error != boost::system::errc::operation_canceled)
|
||||
throw boost::system::system_error{error, "fluff_flush timer failed"};
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
auto next_flush = std::chrono::steady_clock::time_point::max();
|
||||
@@ -370,8 +362,6 @@ namespace levin
|
||||
|
||||
if (next_flush != std::chrono::steady_clock::time_point::max())
|
||||
fluff_flush::queue(std::move(zone_), next_flush);
|
||||
else
|
||||
zone_->flush_time = next_flush; // signal that no timer is set
|
||||
}
|
||||
};
|
||||
|
||||
@@ -406,13 +396,11 @@ namespace levin
|
||||
|
||||
MDEBUG("Queueing " << txs.size() << " transaction(s) for Dandelion++ fluffing");
|
||||
|
||||
bool available = false;
|
||||
zone->p2p->foreach_connection([txs, now, &zone, &source, &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] (detail::p2p_context& context)
|
||||
{
|
||||
// When i2p/tor, only fluff to outbound connections
|
||||
if (source != context.m_connection_id && (zone->is_public || !context.m_is_income))
|
||||
{
|
||||
available = true;
|
||||
if (context.fluff_txs.empty())
|
||||
context.flush_time = now + (context.m_is_income ? in_duration() : out_duration());
|
||||
|
||||
@@ -424,10 +412,9 @@ namespace levin
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!available)
|
||||
if (next_flush == std::chrono::steady_clock::time_point::max())
|
||||
MWARNING("Unable to send transaction(s), no available connections");
|
||||
|
||||
if (next_flush < zone->flush_time)
|
||||
else if (!zone->flush_callbacks || next_flush < zone->flush_txs.expires_at())
|
||||
fluff_flush::queue(std::move(zone), next_flush);
|
||||
}
|
||||
};
|
||||
@@ -524,12 +511,7 @@ namespace levin
|
||||
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
|
||||
if (!zone_->fluffing)
|
||||
{
|
||||
core_->on_transactions_relayed(epee::to_span(txs_), relay_method::stem);
|
||||
for (int tries = 2; 0 < tries; tries--)
|
||||
@@ -549,6 +531,9 @@ namespace levin
|
||||
|
||||
MERROR("Unable to send transaction(s) via Dandelion++ stem");
|
||||
}
|
||||
|
||||
core_->on_transactions_relayed(epee::to_span(txs_), relay_method::fluff);
|
||||
fluff_notify::run(std::move(zone_), epee::to_span(txs_), source_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ namespace nodetool
|
||||
const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_seed_node = {"seed-node", "Connect to a node to retrieve peer addresses, and disconnect"};
|
||||
const command_line::arg_descriptor<std::vector<std::string> > arg_tx_proxy = {"tx-proxy", "Send local txes through proxy: <network-type>,<socks-ip:port>[,max_connections][,disable_noise] i.e. \"tor,127.0.0.1:9050,100,disable_noise\""};
|
||||
const command_line::arg_descriptor<std::vector<std::string> > arg_anonymous_inbound = {"anonymous-inbound", "<hidden-service-address>,<[bind-ip:]port>[,max_connections] i.e. \"x.onion,127.0.0.1:18083,100\""};
|
||||
const command_line::arg_descriptor<std::string> arg_ban_list = {"ban-list", "Specify ban list file, one IP address per line"};
|
||||
const command_line::arg_descriptor<bool> arg_p2p_hide_my_port = {"hide-my-port", "Do not announce yourself as peerlist candidate", false, true};
|
||||
const command_line::arg_descriptor<bool> arg_no_sync = {"no-sync", "Don't synchronize the blockchain with other peers", false};
|
||||
|
||||
|
||||
+16
-8
@@ -164,6 +164,7 @@ namespace nodetool
|
||||
network_zone()
|
||||
: m_connect(nullptr),
|
||||
m_net_server(epee::net_utils::e_connection_type_P2P),
|
||||
m_seed_nodes(),
|
||||
m_bind_ip(),
|
||||
m_bind_ipv6_address(),
|
||||
m_port(),
|
||||
@@ -175,7 +176,9 @@ namespace nodetool
|
||||
m_proxy_address(),
|
||||
m_current_number_of_out_peers(0),
|
||||
m_current_number_of_in_peers(0),
|
||||
m_can_pingback(false)
|
||||
m_seed_nodes_lock(),
|
||||
m_can_pingback(false),
|
||||
m_seed_nodes_initialized(false)
|
||||
{
|
||||
set_config_defaults();
|
||||
}
|
||||
@@ -183,6 +186,7 @@ namespace nodetool
|
||||
network_zone(boost::asio::io_service& public_service)
|
||||
: m_connect(nullptr),
|
||||
m_net_server(public_service, epee::net_utils::e_connection_type_P2P),
|
||||
m_seed_nodes(),
|
||||
m_bind_ip(),
|
||||
m_bind_ipv6_address(),
|
||||
m_port(),
|
||||
@@ -194,13 +198,16 @@ namespace nodetool
|
||||
m_proxy_address(),
|
||||
m_current_number_of_out_peers(0),
|
||||
m_current_number_of_in_peers(0),
|
||||
m_can_pingback(false)
|
||||
m_seed_nodes_lock(),
|
||||
m_can_pingback(false),
|
||||
m_seed_nodes_initialized(false)
|
||||
{
|
||||
set_config_defaults();
|
||||
}
|
||||
|
||||
connect_func* m_connect;
|
||||
net_server m_net_server;
|
||||
std::vector<epee::net_utils::network_address> m_seed_nodes;
|
||||
std::string m_bind_ip;
|
||||
std::string m_bind_ipv6_address;
|
||||
std::string m_port;
|
||||
@@ -212,7 +219,9 @@ namespace nodetool
|
||||
boost::asio::ip::tcp::endpoint m_proxy_address;
|
||||
std::atomic<unsigned int> m_current_number_of_out_peers;
|
||||
std::atomic<unsigned int> m_current_number_of_in_peers;
|
||||
boost::shared_mutex m_seed_nodes_lock;
|
||||
bool m_can_pingback;
|
||||
bool m_seed_nodes_initialized;
|
||||
|
||||
private:
|
||||
void set_config_defaults() noexcept
|
||||
@@ -383,9 +392,10 @@ namespace nodetool
|
||||
void record_addr_failed(const epee::net_utils::network_address& addr);
|
||||
bool is_addr_recently_failed(const epee::net_utils::network_address& addr);
|
||||
bool is_priority_node(const epee::net_utils::network_address& na);
|
||||
std::set<std::string> get_seed_nodes(cryptonote::network_type nettype) const;
|
||||
std::set<std::string> get_seed_nodes();
|
||||
bool connect_to_seed();
|
||||
std::set<std::string> get_ip_seed_nodes() const;
|
||||
std::set<std::string> get_dns_seed_nodes();
|
||||
std::set<std::string> get_seed_nodes(epee::net_utils::zone);
|
||||
bool connect_to_seed(epee::net_utils::zone);
|
||||
|
||||
template <class Container>
|
||||
bool connect_to_peerlist(const Container& peers);
|
||||
@@ -467,9 +477,6 @@ namespace nodetool
|
||||
|
||||
std::list<epee::net_utils::network_address> m_priority_peers;
|
||||
std::vector<epee::net_utils::network_address> m_exclusive_peers;
|
||||
std::vector<epee::net_utils::network_address> m_seed_nodes;
|
||||
bool m_seed_nodes_initialized = false;
|
||||
boost::shared_mutex m_seed_nodes_lock;
|
||||
std::atomic_flag m_fallback_seed_nodes_added;
|
||||
std::vector<nodetool::peerlist_entry> m_command_line_peers;
|
||||
uint64_t m_peer_livetime;
|
||||
@@ -523,6 +530,7 @@ namespace nodetool
|
||||
extern const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_seed_node;
|
||||
extern const command_line::arg_descriptor<std::vector<std::string> > arg_tx_proxy;
|
||||
extern const command_line::arg_descriptor<std::vector<std::string> > arg_anonymous_inbound;
|
||||
extern const command_line::arg_descriptor<std::string> arg_ban_list;
|
||||
extern const command_line::arg_descriptor<bool> arg_p2p_hide_my_port;
|
||||
extern const command_line::arg_descriptor<bool> arg_no_sync;
|
||||
|
||||
|
||||
+177
-52
@@ -71,6 +71,17 @@
|
||||
|
||||
#define MIN_WANTED_SEED_NODES 12
|
||||
|
||||
static inline boost::asio::ip::address_v4 make_address_v4_from_v6(const boost::asio::ip::address_v6& a)
|
||||
{
|
||||
const auto &bytes = a.to_bytes();
|
||||
uint32_t v4 = 0;
|
||||
v4 = (v4 << 8) | bytes[12];
|
||||
v4 = (v4 << 8) | bytes[13];
|
||||
v4 = (v4 << 8) | bytes[14];
|
||||
v4 = (v4 << 8) | bytes[15];
|
||||
return boost::asio::ip::address_v4(v4);
|
||||
}
|
||||
|
||||
namespace nodetool
|
||||
{
|
||||
template<class t_payload_net_handler>
|
||||
@@ -106,6 +117,7 @@ namespace nodetool
|
||||
command_line::add_arg(desc, arg_p2p_seed_node);
|
||||
command_line::add_arg(desc, arg_tx_proxy);
|
||||
command_line::add_arg(desc, arg_anonymous_inbound);
|
||||
command_line::add_arg(desc, arg_ban_list);
|
||||
command_line::add_arg(desc, arg_p2p_hide_my_port);
|
||||
command_line::add_arg(desc, arg_no_sync);
|
||||
command_line::add_arg(desc, arg_no_igd);
|
||||
@@ -245,6 +257,10 @@ namespace nodetool
|
||||
zone.second.m_net_server.get_config_object().close(c);
|
||||
|
||||
conns.clear();
|
||||
|
||||
peerlist_entry pe{};
|
||||
pe.adr = addr;
|
||||
zone.second.m_peerlist.remove_from_peer_white(pe);
|
||||
}
|
||||
|
||||
MCLOG_CYAN(el::Level::Info, "global", "Host " << addr.host_str() << " blocked.");
|
||||
@@ -435,12 +451,42 @@ namespace nodetool
|
||||
|
||||
if (command_line::has_arg(vm, arg_p2p_seed_node))
|
||||
{
|
||||
boost::unique_lock<boost::shared_mutex> lock(m_seed_nodes_lock);
|
||||
boost::unique_lock<boost::shared_mutex> lock(public_zone.m_seed_nodes_lock);
|
||||
|
||||
if (!parse_peers_and_add_to_container(vm, arg_p2p_seed_node, m_seed_nodes))
|
||||
if (!parse_peers_and_add_to_container(vm, arg_p2p_seed_node, public_zone.m_seed_nodes))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!command_line::is_arg_defaulted(vm, arg_ban_list))
|
||||
{
|
||||
const std::string ban_list = command_line::get_arg(vm, arg_ban_list);
|
||||
|
||||
const boost::filesystem::path ban_list_path(ban_list);
|
||||
boost::system::error_code ec;
|
||||
if (!boost::filesystem::exists(ban_list_path, ec))
|
||||
{
|
||||
throw std::runtime_error("Can't find ban list file " + ban_list + " - " + ec.message());
|
||||
}
|
||||
|
||||
std::string banned_ips;
|
||||
if (!epee::file_io_utils::load_file_to_string(ban_list_path.string(), banned_ips))
|
||||
{
|
||||
throw std::runtime_error("Failed to read ban list file " + ban_list);
|
||||
}
|
||||
|
||||
std::istringstream iss(banned_ips);
|
||||
for (std::string line; std::getline(iss, line); )
|
||||
{
|
||||
const expect<epee::net_utils::network_address> parsed_addr = net::get_network_address(line, 0);
|
||||
if (!parsed_addr)
|
||||
{
|
||||
MERROR("Invalid IP address: " << line << " - " << parsed_addr.error());
|
||||
continue;
|
||||
}
|
||||
block_host(*parsed_addr, std::numeric_limits<time_t>::max());
|
||||
}
|
||||
}
|
||||
|
||||
if(command_line::has_arg(vm, arg_p2p_hide_my_port))
|
||||
m_hide_my_port = true;
|
||||
|
||||
@@ -598,21 +644,21 @@ namespace nodetool
|
||||
|
||||
//-----------------------------------------------------------------------------------
|
||||
template<class t_payload_net_handler>
|
||||
std::set<std::string> node_server<t_payload_net_handler>::get_seed_nodes(cryptonote::network_type nettype) const
|
||||
std::set<std::string> node_server<t_payload_net_handler>::get_ip_seed_nodes() const
|
||||
{
|
||||
std::set<std::string> full_addrs;
|
||||
if (nettype == cryptonote::TESTNET)
|
||||
if (m_nettype == cryptonote::TESTNET)
|
||||
{
|
||||
full_addrs.insert("212.83.175.67:28080");
|
||||
full_addrs.insert("212.83.172.165:28080");
|
||||
full_addrs.insert("192.110.160.146:28080");
|
||||
}
|
||||
else if (nettype == cryptonote::STAGENET)
|
||||
else if (m_nettype == cryptonote::STAGENET)
|
||||
{
|
||||
full_addrs.insert("162.210.173.150:38080");
|
||||
full_addrs.insert("192.110.160.146:38080");
|
||||
}
|
||||
else if (nettype == cryptonote::FAKECHAIN)
|
||||
else if (m_nettype == cryptonote::FAKECHAIN)
|
||||
{
|
||||
}
|
||||
else
|
||||
@@ -625,12 +671,17 @@ namespace nodetool
|
||||
full_addrs.insert("209.250.243.248:18080");
|
||||
full_addrs.insert("104.238.221.81:18080");
|
||||
full_addrs.insert("66.85.74.134:18080");
|
||||
full_addrs.insert("xwvz3ekocr3dkyxfkmgm2hvbpzx2ysqmaxgter7znnqrhoicygkfswid.onion:18083");
|
||||
full_addrs.insert("4pixvbejrvihnkxmduo2agsnmc3rrulrqc7s3cbwwrep6h6hrzsibeqd.onion:18083");
|
||||
full_addrs.insert("s3l6ke4ed3df466khuebb4poienoingwof7oxtbo6j4n56sghe3a.b32.i2p:18080");
|
||||
full_addrs.insert("sel36x6fibfzujwvt4hf5gxolz6kd3jpvbjqg6o3ud2xtionyl2q.b32.i2p:18080");
|
||||
full_addrs.insert("zbjkbsxc5munw3qusl7j2hpcmikhqocdf4pqhnhtpzw5nt5jrmofptid.onion:18083");
|
||||
}
|
||||
return full_addrs;
|
||||
}
|
||||
//-----------------------------------------------------------------------------------
|
||||
template<class t_payload_net_handler>
|
||||
std::set<std::string> node_server<t_payload_net_handler>::get_seed_nodes()
|
||||
std::set<std::string> node_server<t_payload_net_handler>::get_dns_seed_nodes()
|
||||
{
|
||||
if (!m_exclusive_peers.empty() || m_offline)
|
||||
{
|
||||
@@ -638,11 +689,11 @@ namespace nodetool
|
||||
}
|
||||
if (m_nettype == cryptonote::TESTNET)
|
||||
{
|
||||
return get_seed_nodes(cryptonote::TESTNET);
|
||||
return get_ip_seed_nodes();
|
||||
}
|
||||
if (m_nettype == cryptonote::STAGENET)
|
||||
{
|
||||
return get_seed_nodes(cryptonote::STAGENET);
|
||||
return get_ip_seed_nodes();
|
||||
}
|
||||
|
||||
std::set<std::string> full_addrs;
|
||||
@@ -730,7 +781,7 @@ namespace nodetool
|
||||
else
|
||||
MINFO("Not enough DNS seed nodes found, using fallback defaults too");
|
||||
|
||||
for (const auto &peer: get_seed_nodes(cryptonote::MAINNET))
|
||||
for (const auto &peer: get_ip_seed_nodes())
|
||||
full_addrs.insert(peer);
|
||||
m_fallback_seed_nodes_added.test_and_set();
|
||||
}
|
||||
@@ -739,6 +790,23 @@ namespace nodetool
|
||||
}
|
||||
//-----------------------------------------------------------------------------------
|
||||
template<class t_payload_net_handler>
|
||||
std::set<std::string> node_server<t_payload_net_handler>::get_seed_nodes(epee::net_utils::zone zone)
|
||||
{
|
||||
switch (zone)
|
||||
{
|
||||
case epee::net_utils::zone::public_:
|
||||
return get_dns_seed_nodes();
|
||||
case epee::net_utils::zone::tor:
|
||||
return {};
|
||||
case epee::net_utils::zone::i2p:
|
||||
return {};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw std::logic_error{"Bad zone given to get_seed_nodes"};
|
||||
}
|
||||
//-----------------------------------------------------------------------------------
|
||||
template<class t_payload_net_handler>
|
||||
typename node_server<t_payload_net_handler>::network_zone& node_server<t_payload_net_handler>::add_zone(const epee::net_utils::zone zone)
|
||||
{
|
||||
const auto zone_ = m_network_zones.lower_bound(zone);
|
||||
@@ -1232,7 +1300,10 @@ namespace nodetool
|
||||
template<class t_payload_net_handler>
|
||||
bool node_server<t_payload_net_handler>::try_to_connect_and_handshake_with_new_peer(const epee::net_utils::network_address& na, bool just_take_peerlist, uint64_t last_seen_stamp, PeerType peer_type, uint64_t first_seen_stamp)
|
||||
{
|
||||
network_zone& zone = m_network_zones.at(na.get_zone());
|
||||
const auto i = m_network_zones.find(na.get_zone());
|
||||
if (i == m_network_zones.end())
|
||||
return false;
|
||||
network_zone& zone = i->second;
|
||||
if (zone.m_connect == nullptr) // outgoing connections in zone not possible
|
||||
return false;
|
||||
|
||||
@@ -1432,17 +1503,44 @@ namespace nodetool
|
||||
const uint32_t actual_ip = na.as<const epee::net_utils::ipv4_network_address>().ip();
|
||||
classB.insert(actual_ip & 0x0000ffff);
|
||||
}
|
||||
else if (cntxt.m_remote_address.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id())
|
||||
{
|
||||
const epee::net_utils::network_address na = cntxt.m_remote_address;
|
||||
const boost::asio::ip::address_v6 &actual_ip = na.as<const epee::net_utils::ipv6_network_address>().ip();
|
||||
if (actual_ip.is_v4_mapped())
|
||||
{
|
||||
boost::asio::ip::address_v4 v4ip = make_address_v4_from_v6(actual_ip);
|
||||
uint32_t actual_ipv4;
|
||||
memcpy(&actual_ipv4, v4ip.to_bytes().data(), sizeof(actual_ipv4));
|
||||
classB.insert(actual_ipv4 & ntohl(0xffff0000));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
auto get_host_string = [](const epee::net_utils::network_address &address) {
|
||||
if (address.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id())
|
||||
{
|
||||
boost::asio::ip::address_v6 actual_ip = address.as<const epee::net_utils::ipv6_network_address>().ip();
|
||||
if (actual_ip.is_v4_mapped())
|
||||
{
|
||||
boost::asio::ip::address_v4 v4ip = make_address_v4_from_v6(actual_ip);
|
||||
uint32_t actual_ipv4;
|
||||
memcpy(&actual_ipv4, v4ip.to_bytes().data(), sizeof(actual_ipv4));
|
||||
return epee::net_utils::ipv4_network_address(actual_ipv4, 0).host_str();
|
||||
}
|
||||
}
|
||||
return address.host_str();
|
||||
};
|
||||
std::unordered_set<std::string> hosts_added;
|
||||
std::deque<size_t> filtered;
|
||||
const size_t limit = use_white_list ? 20 : std::numeric_limits<size_t>::max();
|
||||
for (int step = 0; step < 2; ++step)
|
||||
{
|
||||
bool skip_duplicate_class_B = step == 0;
|
||||
size_t idx = 0, skipped = 0;
|
||||
zone.m_peerlist.foreach (use_white_list, [&classB, &filtered, &idx, &skipped, skip_duplicate_class_B, limit, next_needed_pruning_stripe](const peerlist_entry &pe){
|
||||
zone.m_peerlist.foreach (use_white_list, [&classB, &filtered, &idx, &skipped, skip_duplicate_class_B, limit, next_needed_pruning_stripe, &hosts_added, &get_host_string](const peerlist_entry &pe){
|
||||
if (filtered.size() >= limit)
|
||||
return false;
|
||||
bool skip = false;
|
||||
@@ -1452,6 +1550,27 @@ namespace nodetool
|
||||
uint32_t actual_ip = na.as<const epee::net_utils::ipv4_network_address>().ip();
|
||||
skip = classB.find(actual_ip & 0x0000ffff) != classB.end();
|
||||
}
|
||||
else if (skip_duplicate_class_B && pe.adr.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id())
|
||||
{
|
||||
const epee::net_utils::network_address na = pe.adr;
|
||||
const boost::asio::ip::address_v6 &actual_ip = na.as<const epee::net_utils::ipv6_network_address>().ip();
|
||||
if (actual_ip.is_v4_mapped())
|
||||
{
|
||||
boost::asio::ip::address_v4 v4ip = make_address_v4_from_v6(actual_ip);
|
||||
uint32_t actual_ipv4;
|
||||
memcpy(&actual_ipv4, v4ip.to_bytes().data(), sizeof(actual_ipv4));
|
||||
skip = classB.find(actual_ipv4 & ntohl(0xffff0000)) != classB.end();
|
||||
}
|
||||
}
|
||||
|
||||
// consider each host once, to avoid giving undue inflence to hosts running several nodes
|
||||
if (!skip)
|
||||
{
|
||||
const auto i = hosts_added.find(get_host_string(pe.adr));
|
||||
if (i != hosts_added.end())
|
||||
skip = true;
|
||||
}
|
||||
|
||||
if (skip)
|
||||
++skipped;
|
||||
else if (next_needed_pruning_stripe == 0 || pe.pruning_seed == 0)
|
||||
@@ -1459,16 +1578,17 @@ namespace nodetool
|
||||
else if (next_needed_pruning_stripe == tools::get_pruning_stripe(pe.pruning_seed))
|
||||
filtered.push_front(idx);
|
||||
++idx;
|
||||
hosts_added.insert(get_host_string(pe.adr));
|
||||
return true;
|
||||
});
|
||||
if (skipped == 0 || !filtered.empty())
|
||||
break;
|
||||
if (skipped)
|
||||
MINFO("Skipping " << skipped << " possible peers as they share a class B with existing peers");
|
||||
MDEBUG("Skipping " << skipped << " possible peers as they share a class B with existing peers");
|
||||
}
|
||||
if (filtered.empty())
|
||||
{
|
||||
MDEBUG("No available peer in " << (use_white_list ? "white" : "gray") << " list filtered by " << next_needed_pruning_stripe);
|
||||
MINFO("No available peer in " << (use_white_list ? "white" : "gray") << " list filtered by " << next_needed_pruning_stripe);
|
||||
return false;
|
||||
}
|
||||
if (use_white_list)
|
||||
@@ -1541,56 +1661,59 @@ namespace nodetool
|
||||
}
|
||||
//-----------------------------------------------------------------------------------
|
||||
template<class t_payload_net_handler>
|
||||
bool node_server<t_payload_net_handler>::connect_to_seed()
|
||||
bool node_server<t_payload_net_handler>::connect_to_seed(epee::net_utils::zone zone)
|
||||
{
|
||||
boost::upgrade_lock<boost::shared_mutex> seed_nodes_upgrade_lock(m_seed_nodes_lock);
|
||||
network_zone& server = m_network_zones.at(zone);
|
||||
boost::upgrade_lock<boost::shared_mutex> seed_nodes_upgrade_lock(server.m_seed_nodes_lock);
|
||||
|
||||
if (!m_seed_nodes_initialized)
|
||||
if (!server.m_seed_nodes_initialized)
|
||||
{
|
||||
const std::uint16_t default_port = cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT;
|
||||
boost::upgrade_to_unique_lock<boost::shared_mutex> seed_nodes_lock(seed_nodes_upgrade_lock);
|
||||
m_seed_nodes_initialized = true;
|
||||
for (const auto& full_addr : get_seed_nodes())
|
||||
server.m_seed_nodes_initialized = true;
|
||||
for (const auto& full_addr : get_seed_nodes(zone))
|
||||
{
|
||||
// seeds should have hostname converted to IP already
|
||||
MDEBUG("Seed node: " << full_addr);
|
||||
append_net_address(m_seed_nodes, full_addr, cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT);
|
||||
server.m_seed_nodes.push_back(MONERO_UNWRAP(net::get_network_address(full_addr, default_port)));
|
||||
}
|
||||
MDEBUG("Number of seed nodes: " << m_seed_nodes.size());
|
||||
MDEBUG("Number of seed nodes: " << server.m_seed_nodes.size());
|
||||
}
|
||||
|
||||
if (m_seed_nodes.empty() || m_offline || !m_exclusive_peers.empty())
|
||||
if (server.m_seed_nodes.empty() || m_offline || !m_exclusive_peers.empty())
|
||||
return true;
|
||||
|
||||
size_t try_count = 0;
|
||||
bool is_connected_to_at_least_one_seed_node = false;
|
||||
size_t current_index = crypto::rand_idx(m_seed_nodes.size());
|
||||
const net_server& server = m_network_zones.at(epee::net_utils::zone::public_).m_net_server;
|
||||
size_t current_index = crypto::rand_idx(server.m_seed_nodes.size());
|
||||
while(true)
|
||||
{
|
||||
if(server.is_stop_signal_sent())
|
||||
if(server.m_net_server.is_stop_signal_sent())
|
||||
return false;
|
||||
|
||||
peerlist_entry pe_seed{};
|
||||
pe_seed.adr = m_seed_nodes[current_index];
|
||||
pe_seed.adr = server.m_seed_nodes[current_index];
|
||||
if (is_peer_used(pe_seed))
|
||||
is_connected_to_at_least_one_seed_node = true;
|
||||
else if (try_to_connect_and_handshake_with_new_peer(m_seed_nodes[current_index], true))
|
||||
else if (try_to_connect_and_handshake_with_new_peer(server.m_seed_nodes[current_index], true))
|
||||
break;
|
||||
if(++try_count > m_seed_nodes.size())
|
||||
if(++try_count > server.m_seed_nodes.size())
|
||||
{
|
||||
if (!m_fallback_seed_nodes_added.test_and_set())
|
||||
// only IP zone has fallback (to direct IP) seeds
|
||||
if (zone == epee::net_utils::zone::public_ && !m_fallback_seed_nodes_added.test_and_set())
|
||||
{
|
||||
MWARNING("Failed to connect to any of seed peers, trying fallback seeds");
|
||||
current_index = m_seed_nodes.size() - 1;
|
||||
current_index = server.m_seed_nodes.size() - 1;
|
||||
{
|
||||
boost::upgrade_to_unique_lock<boost::shared_mutex> seed_nodes_lock(seed_nodes_upgrade_lock);
|
||||
|
||||
for (const auto &peer: get_seed_nodes(m_nettype))
|
||||
for (const auto &peer: get_ip_seed_nodes())
|
||||
{
|
||||
MDEBUG("Fallback seed node: " << peer);
|
||||
append_net_address(m_seed_nodes, peer, cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT);
|
||||
append_net_address(server.m_seed_nodes, peer, cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT);
|
||||
}
|
||||
}
|
||||
if (current_index == m_seed_nodes.size() - 1)
|
||||
if (current_index == server.m_seed_nodes.size() - 1)
|
||||
{
|
||||
MWARNING("No fallback seeds, continuing without seeds");
|
||||
break;
|
||||
@@ -1604,7 +1727,7 @@ namespace nodetool
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(++current_index >= m_seed_nodes.size())
|
||||
if(++current_index >= server.m_seed_nodes.size())
|
||||
current_index = 0;
|
||||
}
|
||||
return true;
|
||||
@@ -1620,20 +1743,21 @@ namespace nodetool
|
||||
|
||||
if (!m_exclusive_peers.empty()) return true;
|
||||
|
||||
// Only have seeds in the public zone right now.
|
||||
|
||||
size_t start_conn_count = get_public_outgoing_connections_count();
|
||||
if(!get_public_white_peers_count() && !connect_to_seed())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!connect_to_peerlist(m_priority_peers)) return false;
|
||||
|
||||
bool one_succeeded = false;
|
||||
for(auto& zone : m_network_zones)
|
||||
{
|
||||
size_t start_conn_count = get_outgoing_connections_count(zone.second);
|
||||
if(!zone.second.m_peerlist.get_white_peers_count() && !connect_to_seed(zone.first))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (zone.first == zone_type::public_ && !connect_to_peerlist(m_priority_peers)) continue;
|
||||
|
||||
size_t base_expected_white_connections = (zone.second.m_config.m_net_config.max_out_connection_count*P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT)/100;
|
||||
|
||||
// carefully avoid `continue` in nested loop
|
||||
|
||||
size_t conn_count = get_outgoing_connections_count(zone.second);
|
||||
while(conn_count < zone.second.m_config.m_net_config.max_out_connection_count)
|
||||
{
|
||||
@@ -1670,16 +1794,17 @@ namespace nodetool
|
||||
}
|
||||
conn_count = new_conn_count;
|
||||
}
|
||||
|
||||
if (start_conn_count == get_outgoing_connections_count(zone.second) && start_conn_count < zone.second.m_config.m_net_config.max_out_connection_count)
|
||||
{
|
||||
MINFO("Failed to connect to any, trying seeds");
|
||||
if (!connect_to_seed(zone.first))
|
||||
continue;
|
||||
}
|
||||
one_succeeded = true;
|
||||
}
|
||||
|
||||
if (start_conn_count == get_public_outgoing_connections_count() && start_conn_count < m_network_zones.at(zone_type::public_).m_config.m_net_config.max_out_connection_count)
|
||||
{
|
||||
MINFO("Failed to connect to any, trying seeds");
|
||||
if (!connect_to_seed())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return one_succeeded;
|
||||
}
|
||||
//-----------------------------------------------------------------------------------
|
||||
template<class t_payload_net_handler>
|
||||
|
||||
@@ -1131,6 +1131,11 @@ namespace cryptonote
|
||||
{
|
||||
RPC_TRACKER(send_raw_tx);
|
||||
|
||||
{
|
||||
bool ok;
|
||||
use_bootstrap_daemon_if_necessary<COMMAND_RPC_SEND_RAW_TX>(invoke_http_mode::JON, "/sendrawtransaction", req, res, ok);
|
||||
}
|
||||
|
||||
const bool restricted = m_restricted && ctx;
|
||||
|
||||
bool skip_validation = false;
|
||||
@@ -2827,6 +2832,8 @@ namespace cryptonote
|
||||
RPC_TRACKER(relay_tx);
|
||||
CHECK_PAYMENT_MIN1(req, res, req.txids.size() * COST_PER_TX_RELAY, false);
|
||||
|
||||
const bool restricted = m_restricted && ctx;
|
||||
|
||||
bool failed = false;
|
||||
res.status = "";
|
||||
for (const auto &str: req.txids)
|
||||
@@ -2840,12 +2847,16 @@ namespace cryptonote
|
||||
continue;
|
||||
}
|
||||
|
||||
//TODO: The get_pool_transaction could have an optional meta parameter
|
||||
bool broadcasted = false;
|
||||
cryptonote::blobdata txblob;
|
||||
if (m_core.get_pool_transaction(txid, txblob, relay_category::legacy))
|
||||
if ((broadcasted = m_core.get_pool_transaction(txid, txblob, relay_category::broadcasted)) || (!restricted && m_core.get_pool_transaction(txid, txblob, relay_category::all)))
|
||||
{
|
||||
// The settings below always choose i2p/tor if enabled. Otherwise, do fluff iff previously relayed else dandelion++ stem.
|
||||
NOTIFY_NEW_TRANSACTIONS::request r;
|
||||
r.txs.push_back(std::move(txblob));
|
||||
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid, relay_method::local);
|
||||
const auto tx_relay = broadcasted ? relay_method::fluff : relay_method::local;
|
||||
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid, tx_relay);
|
||||
//TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes
|
||||
}
|
||||
else
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#define DEF_MONERO_VERSION_TAG "@VERSIONTAG@"
|
||||
#define DEF_MONERO_VERSION "0.17.1.0"
|
||||
#define DEF_MONERO_VERSION "0.17.1.2"
|
||||
#define DEF_MONERO_RELEASE_NAME "Oxygen Orion"
|
||||
#define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG
|
||||
#define DEF_MONERO_VERSION_IS_RELEASE @VERSION_IS_RELEASE@
|
||||
|
||||
@@ -910,6 +910,11 @@ std::string WalletImpl::path() const
|
||||
return m_wallet->path();
|
||||
}
|
||||
|
||||
void WalletImpl::stop()
|
||||
{
|
||||
m_wallet->stop();
|
||||
}
|
||||
|
||||
bool WalletImpl::store(const std::string &path)
|
||||
{
|
||||
clearStatus();
|
||||
|
||||
@@ -99,6 +99,7 @@ public:
|
||||
std::string publicSpendKey() const override;
|
||||
std::string publicMultisigSignerKey() const override;
|
||||
std::string path() const override;
|
||||
void stop() override;
|
||||
bool store(const std::string &path) override;
|
||||
std::string filename() const override;
|
||||
std::string keysFilename() const override;
|
||||
|
||||
@@ -506,6 +506,11 @@ struct Wallet
|
||||
*/
|
||||
virtual std::string publicMultisigSignerKey() const = 0;
|
||||
|
||||
/*!
|
||||
* \brief stop - interrupts wallet refresh() loop once (doesn't stop background refresh thread)
|
||||
*/
|
||||
virtual void stop() = 0;
|
||||
|
||||
/*!
|
||||
* \brief store - stores wallet to file.
|
||||
* \param path - main filename to store wallet to. additionally stores address file and keys file.
|
||||
|
||||
@@ -2961,6 +2961,8 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction,
|
||||
MTRACE("update_pool_state got pool");
|
||||
|
||||
// remove any pending tx that's not in the pool
|
||||
constexpr const std::chrono::seconds tx_propagation_timeout{CRYPTONOTE_DANDELIONPP_EMBARGO_AVERAGE * 3 / 2};
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin();
|
||||
while (it != m_unconfirmed_txs.end())
|
||||
{
|
||||
@@ -2988,9 +2990,11 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction,
|
||||
LOG_PRINT_L1("Pending txid " << txid << " not in pool, marking as not in pool");
|
||||
pit->second.m_state = wallet2::unconfirmed_transfer_details::pending_not_in_pool;
|
||||
}
|
||||
else if (pit->second.m_state == wallet2::unconfirmed_transfer_details::pending_not_in_pool && refreshed)
|
||||
else if (pit->second.m_state == wallet2::unconfirmed_transfer_details::pending_not_in_pool && refreshed &&
|
||||
now > std::chrono::system_clock::from_time_t(pit->second.m_sent_time) + tx_propagation_timeout)
|
||||
{
|
||||
LOG_PRINT_L1("Pending txid " << txid << " not in pool, marking as failed");
|
||||
LOG_PRINT_L1("Pending txid " << txid << " not in pool after " << tx_propagation_timeout.count() <<
|
||||
" seconds, marking as failed");
|
||||
pit->second.m_state = wallet2::unconfirmed_transfer_details::failed;
|
||||
|
||||
// the inputs aren't spent anymore, since the tx failed
|
||||
|
||||
@@ -613,6 +613,61 @@ TEST_F(levin_notify, stem_without_padding)
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(levin_notify, stem_no_outs_without_padding)
|
||||
{
|
||||
cryptonote::levin::notify notifier = make_notifier(0, true, false);
|
||||
|
||||
for (unsigned count = 0; count < 10; ++count)
|
||||
add_connection(true);
|
||||
|
||||
{
|
||||
const auto status = notifier.get_status();
|
||||
EXPECT_FALSE(status.has_noise);
|
||||
EXPECT_FALSE(status.connections_filled);
|
||||
}
|
||||
notifier.new_out_connection();
|
||||
io_service_.poll();
|
||||
|
||||
std::vector<cryptonote::blobdata> txs(2);
|
||||
txs[0].resize(100, 'f');
|
||||
txs[1].resize(200, 'e');
|
||||
|
||||
std::vector<cryptonote::blobdata> sorted_txs = txs;
|
||||
std::sort(sorted_txs.begin(), sorted_txs.end());
|
||||
|
||||
ASSERT_EQ(10u, contexts_.size());
|
||||
|
||||
auto context = contexts_.begin();
|
||||
EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), cryptonote::relay_method::stem));
|
||||
|
||||
io_service_.reset();
|
||||
ASSERT_LT(0u, io_service_.poll());
|
||||
EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::fluff));
|
||||
if (events_.has_stem_txes())
|
||||
EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::stem));
|
||||
|
||||
|
||||
notifier.run_fluff();
|
||||
ASSERT_LT(0u, io_service_.poll());
|
||||
|
||||
std::size_t send_count = 0;
|
||||
EXPECT_EQ(0u, context->process_send_queue());
|
||||
for (++context; context != contexts_.end(); ++context)
|
||||
{
|
||||
send_count += context->process_send_queue();
|
||||
}
|
||||
|
||||
EXPECT_EQ(9u, send_count);
|
||||
ASSERT_EQ(9u, receiver_.notified_size());
|
||||
for (unsigned count = 0; count < 9u; ++count)
|
||||
{
|
||||
auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
|
||||
EXPECT_EQ(sorted_txs, notification.txs);
|
||||
EXPECT_TRUE(notification._.empty());
|
||||
EXPECT_TRUE(notification.dandelionpp_fluff);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(levin_notify, local_without_padding)
|
||||
{
|
||||
cryptonote::levin::notify notifier = make_notifier(0, true, false);
|
||||
@@ -928,6 +983,60 @@ TEST_F(levin_notify, stem_with_padding)
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(levin_notify, stem_no_outs_with_padding)
|
||||
{
|
||||
cryptonote::levin::notify notifier = make_notifier(0, true, true);
|
||||
|
||||
for (unsigned count = 0; count < 10; ++count)
|
||||
add_connection(true);
|
||||
|
||||
{
|
||||
const auto status = notifier.get_status();
|
||||
EXPECT_FALSE(status.has_noise);
|
||||
EXPECT_FALSE(status.connections_filled);
|
||||
}
|
||||
notifier.new_out_connection();
|
||||
io_service_.poll();
|
||||
|
||||
std::vector<cryptonote::blobdata> txs(2);
|
||||
txs[0].resize(100, 'f');
|
||||
txs[1].resize(200, 'e');
|
||||
|
||||
std::vector<cryptonote::blobdata> sorted_txs = txs;
|
||||
std::sort(sorted_txs.begin(), sorted_txs.end());
|
||||
|
||||
ASSERT_EQ(10u, contexts_.size());
|
||||
|
||||
auto context = contexts_.begin();
|
||||
EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), cryptonote::relay_method::stem));
|
||||
|
||||
io_service_.reset();
|
||||
ASSERT_LT(0u, io_service_.poll());
|
||||
EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::fluff));
|
||||
if (events_.has_stem_txes())
|
||||
EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::stem));
|
||||
|
||||
notifier.run_fluff();
|
||||
ASSERT_LT(0u, io_service_.poll());
|
||||
|
||||
std::size_t send_count = 0;
|
||||
EXPECT_EQ(0u, context->process_send_queue());
|
||||
for (++context; context != contexts_.end(); ++context)
|
||||
{
|
||||
send_count += context->process_send_queue();
|
||||
}
|
||||
|
||||
EXPECT_EQ(9u, send_count);
|
||||
ASSERT_EQ(9u, receiver_.notified_size());
|
||||
for (unsigned count = 0; count < 9u; ++count)
|
||||
{
|
||||
auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
|
||||
EXPECT_EQ(sorted_txs, notification.txs);
|
||||
EXPECT_FALSE(notification._.empty());
|
||||
EXPECT_TRUE(notification.dandelionpp_fluff);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(levin_notify, local_with_padding)
|
||||
{
|
||||
cryptonote::levin::notify notifier = make_notifier(0, true, true);
|
||||
|
||||
Reference in New Issue
Block a user