Compare commits

...

21 Commits

Author SHA1 Message Date
SChernykh e44baad73c Update version 2021-11-05 15:39:20 +01:00
SChernykh bb7a5b4d6d Cleanup 2021-11-02 17:18:02 +01:00
SChernykh 64116643c2 Fixed block broadcasts 2021-11-01 19:36:11 +01:00
SChernykh 1e4c6ef5f4 Added block verification test 2021-11-01 18:53:34 +01:00
SChernykh 61edc6ed70 Reduce cache flush frequency to once per minute 2021-11-01 11:29:59 +01:00
SChernykh d283f91eb0 Update README.md 2021-11-01 11:07:00 +01:00
SChernykh dff2413cae TCPServer: make write buffers global
Reduced memory usage per connection.
2021-10-31 20:29:35 +01:00
SChernykh 998c2ba72f Fixed msys2 build crash 2021-10-31 20:29:35 +01:00
SChernykh b56d027dd5 Include P2P peer list into miner count 2021-10-31 20:29:35 +01:00
SChernykh eaed654012 Check for missing data from monerod on each new block
It helps in these cases:
- monerod was down/unavailable for a while and then restarted
- monerod was out of sync and then jumped several block heights ahead
2021-10-31 12:20:29 +01:00
SChernykh 981b161cc9 SideChain: faster processing of repeated broadcasts 2021-10-31 11:06:00 +01:00
SChernykh f59e9171e8 SideChain: use a separate lock for m_seenBlocks 2021-10-31 10:26:13 +01:00
SChernykh b45540ca08 TCPServer: clean up old IP bans 2021-10-29 14:54:36 +02:00
SChernykh 806e1ca0a3 SideChain: faster check for low-diff spam blocks 2021-10-29 11:39:15 +02:00
SChernykh 113e39817d Cleanup old Monero blocks data 2021-10-29 11:14:28 +02:00
SChernykh 0869326f39 SideChain: count miners in the last 48 hours 2021-10-29 10:37:12 +02:00
SChernykh 75d62c0303 SideChain: cleanup m_seenBlocks when pruning old blocks 2021-10-29 10:34:53 +02:00
SChernykh da4686b88f ZMQReader: start after initial RPC calls are finished 2021-10-28 20:05:40 +02:00
SChernykh a008eac8c6 ZMQReader: more reliable connect() 2021-10-28 19:28:33 +02:00
SChernykh 3d60ae8c32 Fix: don't start until monerod is fully synchronized 2021-10-28 18:47:28 +02:00
SChernykh 88f346414a Fixed race condition when using --no-color 2021-10-28 10:36:50 +02:00
22 changed files with 361 additions and 163 deletions
+12 -11
View File
@@ -2,7 +2,7 @@
Decentralized pool for Monero mining.
Mining on the mainnet has started! You can check the pool status at https://p2pool.io/
Pool status and monitoring pages can be found at https://p2pool.io/ and https://p2pool.observer/
### Build status
@@ -68,9 +68,9 @@ make -j$(nproc)
monerod binary compatible with p2pool:
```
sudo apt update && sudo apt install git build-essential cmake pkg-config libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libldns-dev libexpat1-dev libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-locale-dev libboost-program-options-dev libboost-regex-dev libboost-serialization-dev libboost-system-dev libboost-thread-dev ccache doxygen graphviz
git clone --recursive https://github.com/SChernykh/monero
git clone --recursive https://github.com/monero-project/monero/
cd monero
git checkout p2pool-api-v0.17
git checkout release-v0.17
git submodule sync && git submodule update --init --force --recursive
make release-static -j$(nproc)
```
@@ -113,9 +113,9 @@ make -j$(sysctl -n hw.logicalcpu)
monerod binary compatible with p2pool:
```
git clone --recursive https://github.com/SChernykh/monero
git clone --recursive https://github.com/monero-project/monero/
cd monero
git checkout p2pool-api-v0.17
git checkout release-v0.17
git submodule sync && git submodule update --init --force --recursive
brew update && brew bundle --file=contrib/brew/Brewfile
make -j$(sysctl -n hw.logicalcpu)
@@ -138,9 +138,9 @@ Alternatively, you can select "Clone a repository" within the GUI, then select "
monerod binary compatible with p2pool:
```
git clone --recursive https://github.com/SChernykh/monero
git clone --recursive https://github.com/monero-project/monero/
cd monero
git checkout p2pool-api-v0.17
git checkout release-v0.17
git submodule sync && git submodule update --init --force --recursive
```
then follow the instructions from https://github.com/monero-project/monero/#on-windows
@@ -171,10 +171,11 @@ Step-by-step guide:
- Prepare enough huge pages (each of monerod/p2pool/xmrig needs them): `sudo sysctl vm.nr_hugepages=3072`
- Get xmrig (linux-static-x64) binary from https://github.com/xmrig/xmrig/releases/latest
- Check that ports 18080 (Monero p2p port) and 37889 (p2pool p2p port) are open in your firewall to ensure better connectivity
- Use the `monerod` binary bundled with p2pool (official binaries don't have p2pool support yet)
- Run `./monerod --zmq-pub tcp://127.0.0.1:18083 --disable-dns-checkpoints --enable-dns-blocklist` **don't forget --zmq-pub parameter in the command line**
- Double check that it shows **Monero 'Oxygen Orion' (v0.17.2.3-7dbb0d1fc)** on startup. Wait until it's synchronized.
- Double check that it shows **Monero 'Oxygen Orion' (v0.17.2.3-fce167d7c)** on startup. Wait until it's synchronized.
- Run `./p2pool --host 127.0.0.1 --wallet YOUR_WALLET_ADDRESS`
- p2pool has _very_ verbose logging by default, it will spam a lot, no I mean A LOT in both console and in p2pool.log. Logs help testing immensely!
- p2pool has verbose logging by default, you can reduce it by using "loglevel N" command where N is between 0 and 6. Default loglevel is 3.
- You can use `logrotate` with a config like this to control logfile growth:
```
<path-to-logfile>
@@ -197,7 +198,7 @@ Step-by-step guide:
*NOTE: Windows SmartScreen may block incoming connections by files that are "Downloaded from the Internet". You can allow 'p2pool.exe' and 'monerod.exe' by double-clicking them, clicking "More Info", then click "Run Anyway" and then closing them immediately so you can run them from the command line. Advanced users can use the PowerShell cmdlet `Unblock-File` to remove this flag.*
- Download p2pool binaries from https://github.com/SChernykh/p2pool/releases/latest
- Download xmrig binary from https://github.com/xmrig/xmrig/releases/latest *(xmrig-6.15.0-gcc-win64.zip is the current version)*
- Download xmrig binary from https://github.com/xmrig/xmrig/releases/latest *(xmrig-6.15.2-gcc-win64.zip is the current version)*
- Expand the p2pool binaries into an appropriate location (`%USERPROFILE%/bin` or `C:/bin/` are good options)
- Expand xmrig binary into appropriate location (same folder as p2pool is fine)
- Prepare huge pages (each of monerod/p2pool/xmrig needs them):
@@ -206,7 +207,7 @@ Step-by-step guide:
- Open a command prompt and navigate to the folder where you extracted p2pool.
- *When running these commands, Windows Firewall may prompt to allow connections, click "Allow"*
- Run `.\Monero\monerod.exe --zmq-pub tcp://127.0.0.1:18083 --disable-dns-checkpoints --enable-dns-blocklist` *NOTE: don't forget --zmq-pub parameter in the command line*
- Double check that it shows **Monero 'Oxygen Orion' (v0.17.2.3-7dbb0d1fc)** on startup. Wait until it's synchronized.
- Double check that it shows **Monero 'Oxygen Orion' (v0.17.2.3-fce167d7c)** on startup. Wait until it's synchronized.
- Run `.\p2pool.exe --host 127.0.0.1 --wallet YOUR_WALLET_ADDRESS`
- Wait until initial p2pool sync is finished, it shouldn't take more than 5-10 minutes, once completed xmrig should be able to connect to the stratum server on port 3333.
- Run `.\xmrig.exe -o 127.0.0.1:3333`. Note that you don't need to specify wallet address for xmrig. **Wallet address set in xmrig config will be ignored!**
+28
View File
@@ -279,6 +279,34 @@ enum class NetworkType {
Stagenet,
};
struct raw_ip
{
alignas(8) uint8_t data[16];
FORCEINLINE bool operator<(const raw_ip& other) const
{
const uint64_t* a = reinterpret_cast<const uint64_t*>(data);
const uint64_t* b = reinterpret_cast<const uint64_t*>(other.data);
if (a[1] < b[1]) return true;
if (a[1] > b[1]) return false;
return a[0] < b[0];
}
FORCEINLINE bool operator==(const raw_ip& other) const
{
const uint64_t* a = reinterpret_cast<const uint64_t*>(data);
const uint64_t* b = reinterpret_cast<const uint64_t*>(other.data);
return (a[0] == b[0]) && (a[1] == b[1]);
}
FORCEINLINE bool operator!=(const raw_ip& other) const { return !operator==(other); }
};
static_assert(sizeof(raw_ip) == 16, "struct raw_ip has invalid size");
void* malloc_hook(size_t n) noexcept;
void* realloc_hook(void* ptr, size_t size) noexcept;
void* calloc_hook(size_t count, size_t size) noexcept;
+6 -3
View File
@@ -54,7 +54,7 @@ public:
: m_writePos(0)
, m_readPos(0)
{
is_main_thread = true;
set_main_thread();
m_logFile.open(log_file_name, std::ios::app | std::ios::binary);
@@ -160,7 +160,10 @@ private:
p += 3;
size -= 3;
if (!CONSOLE_COLORS) {
// Read CONSOLE_COLORS only once because its value can be changed in another thread
const bool c = CONSOLE_COLORS;
if (!c) {
strip_colors(p, size);
}
@@ -181,7 +184,7 @@ private:
}
if (m_logFile.is_open()) {
if (CONSOLE_COLORS) {
if (c) {
strip_colors(p, size);
}
+1 -1
View File
@@ -740,7 +740,7 @@ void P2PServer::on_timer()
void P2PServer::flush_cache()
{
if (!m_cache) {
if (!m_cache || ((m_timerCounter % 30) != 2)) {
return;
}
+2 -1
View File
@@ -128,6 +128,7 @@ public:
void print_status() override;
void show_peers();
size_t peer_list_size() const { return m_peerList.size(); }
private:
p2pool* m_pool;
@@ -192,7 +193,7 @@ private:
std::vector<Broadcast*> m_broadcastQueue;
uv_mutex_t m_missingBlockRequestsLock;
std::set<std::pair<uint64_t, uint64_t>> m_missingBlockRequests;
unordered_set<std::pair<uint64_t, uint64_t>> m_missingBlockRequests;
static void on_broadcast(uv_async_t* handle) { reinterpret_cast<P2PServer*>(handle->data)->on_broadcast(); }
void on_broadcast();
+75 -8
View File
@@ -203,6 +203,8 @@ void p2pool::handle_miner_data(MinerData& data)
c.reward = 0;
m_mainchainByHash[c.id] = c;
cleanup_mainchain_data(data.height);
}
data.tx_backlog.clear();
@@ -224,7 +226,7 @@ void p2pool::handle_miner_data(MinerData& data)
"\n---------------------------------------------------------------------------------------------------------------"
);
if (!is_main_thread) {
if (!is_main_thread()) {
update_block_template_async();
}
else {
@@ -232,6 +234,41 @@ void p2pool::handle_miner_data(MinerData& data)
}
m_zmqLastActive = time(nullptr);
if (m_serversStarted.load()) {
std::vector<uint64_t> missing_heights;
{
WriteLock lock(m_mainchainLock);
for (uint64_t h = data.height; h && (h + BLOCK_HEADERS_REQUIRED > data.height); --h) {
if (m_mainchainByHeight.find(h) == m_mainchainByHeight.end()) {
LOGWARN(3, "Mainchain data for height " << h << " is missing, requesting it from monerod again");
missing_heights.push_back(h);
}
}
}
for (uint64_t h : missing_heights) {
char buf[log::Stream::BUF_SIZE + 1];
log::Stream s(buf);
s << "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"get_block_header_by_height\",\"params\":{\"height\":" << h << "}}\0";
JSONRPCRequest::call(m_params->m_host.c_str(), m_params->m_rpcPort, buf,
[this, h](const char* data, size_t size)
{
ChainMain block;
if (!parse_block_header(data, size, block)) {
LOGERR(1, "couldn't download block header for height " << h);
}
},
[h](const char* data, size_t size)
{
if (size > 0) {
LOGERR(1, "couldn't download block header for height " << h << ", error " << log::const_buf(data, size));
}
});
}
}
}
const char* BLOCK_FOUND = "\n\
@@ -533,6 +570,7 @@ void p2pool::download_block_headers(uint64_t current_height)
if (parse_block_headers_range(data, size) == BLOCK_HEADERS_REQUIRED) {
update_median_timestamp();
if (m_serversStarted.exchange(1) == 0) {
m_ZMQReader = new ZMQReader(m_params->m_host.c_str(), m_params->m_zmqPort, this);
m_stratumServer = new StratumServer(this);
m_p2pServer = new P2PServer(this);
api_update_network_stats();
@@ -688,18 +726,22 @@ void p2pool::parse_get_info_rpc(const char* data, size_t size)
const auto& result = doc["result"];
struct {
bool busy_syncing, mainnet, testnet, stagenet;
bool busy_syncing, synchronized, mainnet, testnet, stagenet;
} info;
if (!PARSE(result, info, busy_syncing) || !PARSE(result, info, mainnet) || !PARSE(result, info, testnet) || !PARSE(result, info, stagenet)) {
if (!PARSE(result, info, busy_syncing) ||
!PARSE(result, info, synchronized) ||
!PARSE(result, info, mainnet) ||
!PARSE(result, info, testnet) ||
!PARSE(result, info, stagenet)) {
LOGWARN(1, "get_info RPC response is invalid, trying again in 1 second");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
get_info();
return;
}
if (info.busy_syncing) {
LOGINFO(1, "monerod is busy syncing, trying again in 1 second");
if (info.busy_syncing || !info.synchronized) {
LOGINFO(1, "monerod is " << (info.busy_syncing ? "busy syncing" : "not synchronized") << ", trying again in 1 second");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
get_info();
return;
@@ -971,7 +1013,7 @@ void p2pool::api_update_pool_stats()
uint64_t t;
const difficulty_type& diff = m_sideChain->difficulty();
const uint64_t hashrate = udiv128(diff.hi, diff.lo, m_sideChain->block_time(), &t);
const uint64_t miners = m_sideChain->miner_count();
const uint64_t miners = std::max<uint64_t>(m_sideChain->miner_count(), m_p2pServer ? m_p2pServer->peer_list_size() : 0U);
const difficulty_type total_hashes = m_sideChain->total_hashes();
time_t last_block_found_time = 0;
@@ -1034,7 +1076,7 @@ void p2pool::api_update_stats_mod()
s << last_block_found_hash << '\0';
memcpy(last_block_found_buf + 4, "...", 4);
const uint64_t miners = m_sideChain->miner_count();
const uint64_t miners = std::max<uint64_t>(m_sideChain->miner_count(), m_p2pServer ? m_p2pServer->peer_list_size() : 0U);
uint64_t t;
const difficulty_type& diff = m_sideChain->difficulty();
@@ -1063,6 +1105,31 @@ void p2pool::api_update_stats_mod()
});
}
void p2pool::cleanup_mainchain_data(uint64_t height)
{
// Expects m_mainchainLock to be already locked here
// Deletes everything older than 720 blocks, except for the 3 latest RandomX seed heights
constexpr uint64_t PRUNE_DISTANCE = BLOCK_HEADERS_REQUIRED;
const uint64_t seed_height = get_seed_height(height);
const std::array<uint64_t, 3> seed_heights{ seed_height, seed_height - SEEDHASH_EPOCH_BLOCKS, seed_height - SEEDHASH_EPOCH_BLOCKS * 2 };
for (auto it = m_mainchainByHeight.begin(); it != m_mainchainByHeight.end();) {
const uint64_t h = it->first;
if (h + PRUNE_DISTANCE >= height) {
break;
}
if (std::find(seed_heights.begin(), seed_heights.end(), h) == seed_heights.end()) {
m_mainchainByHash.erase(it->second.id);
it = m_mainchainByHeight.erase(it);
}
else {
++it;
}
}
}
void p2pool::api_update_block_found(const ChainMain* data)
{
clear_crypto_cache();
@@ -1218,11 +1285,11 @@ int p2pool::run()
}
try {
ZMQReader z(m_params->m_host.c_str(), m_params->m_zmqPort, this);
get_info();
load_found_blocks();
const int rc = uv_run(uv_default_loop_checked(), UV_RUN_DEFAULT);
LOGINFO(1, "uv_run exited, result = " << rc);
delete m_ZMQReader;
}
catch (const std::exception& e) {
const char* s = e.what();
+5
View File
@@ -31,6 +31,7 @@ class StratumServer;
class P2PServer;
class ConsoleCommands;
class p2pool_api;
class ZMQReader;
class p2pool : public MinerCallbackHandler
{
@@ -129,6 +130,8 @@ private:
void api_update_pool_stats();
void api_update_stats_mod();
void cleanup_mainchain_data(uint64_t height);
struct FoundBlock
{
FORCEINLINE FoundBlock(time_t _t, uint64_t _h, const hash& _id, const difficulty_type& _block_diff, const difficulty_type& _total_hashes)
@@ -172,6 +175,8 @@ private:
time_t m_zmqLastActive;
time_t m_startTime;
ZMQReader* m_ZMQReader = nullptr;
};
} // namespace p2pool
+5 -7
View File
@@ -60,7 +60,6 @@ PoolBlock::PoolBlock()
m_mainChainData.reserve(48 * 1024);
m_outputs.reserve(2048);
m_transactions.reserve(256);
m_tmpInts.reserve(m_transactions.capacity() * HASH_SIZE);
m_sideChainData.reserve(512);
m_uncles.reserve(8);
m_tmpTxExtra.reserve(80);
@@ -110,7 +109,6 @@ PoolBlock& PoolBlock::operator=(const PoolBlock& b)
m_cumulativeDifficulty = b.m_cumulativeDifficulty;
m_sidechainId = b.m_sidechainId;
m_tmpTxExtra = b.m_tmpTxExtra;
m_tmpInts = b.m_tmpInts;
m_depth = b.m_depth;
m_verified = b.m_verified;
m_invalid = b.m_invalid;
@@ -276,21 +274,21 @@ bool PoolBlock::get_pow_hash(RandomX_Hasher* hasher, const hash& seed_hash, hash
cnt >>= 1;
m_tmpInts.resize(cnt * HASH_SIZE);
memcpy(m_tmpInts.data(), h, (cnt * 2 - count) * HASH_SIZE);
std::vector<uint8_t> tmp_ints(cnt * HASH_SIZE);
memcpy(tmp_ints.data(), h, (cnt * 2 - count) * HASH_SIZE);
for (i = cnt * 2 - count, j = cnt * 2 - count; j < cnt; i += 2, ++j) {
keccak(h + i * HASH_SIZE, HASH_SIZE * 2, m_tmpInts.data() + j * HASH_SIZE, HASH_SIZE);
keccak(h + i * HASH_SIZE, HASH_SIZE * 2, tmp_ints.data() + j * HASH_SIZE, HASH_SIZE);
}
while (cnt > 2) {
cnt >>= 1;
for (i = 0, j = 0; j < cnt; i += 2, ++j) {
keccak(m_tmpInts.data() + i * HASH_SIZE, HASH_SIZE * 2, m_tmpInts.data() + j * HASH_SIZE, HASH_SIZE);
keccak(tmp_ints.data() + i * HASH_SIZE, HASH_SIZE * 2, tmp_ints.data() + j * HASH_SIZE, HASH_SIZE);
}
}
keccak(m_tmpInts.data(), HASH_SIZE * 2, blob + blob_size, HASH_SIZE);
keccak(tmp_ints.data(), HASH_SIZE * 2, blob + blob_size, HASH_SIZE);
}
}
blob_size += HASH_SIZE;
-1
View File
@@ -121,7 +121,6 @@ struct PoolBlock
// Just temporary stuff, not a part of the block
std::vector<uint8_t> m_tmpTxExtra;
std::vector<uint8_t> m_tmpInts;
uint64_t m_depth;
-1
View File
@@ -341,7 +341,6 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, SideChain& sidechai
// Defaults for off-chain variables
m_tmpTxExtra.clear();
m_tmpInts.clear();
m_depth = 0;
+70 -41
View File
@@ -77,8 +77,11 @@ SideChain::SideChain(p2pool* pool, NetworkType type, const char* pool_name)
}
uv_mutex_init_checked(&m_sidechainLock);
uv_mutex_init_checked(&m_seenBlocksLock);
m_difficultyData.reserve(m_chainWindowSize);
m_tmpShares.reserve(m_chainWindowSize * 2);
m_tmpRewards.reserve(m_chainWindowSize * 2);
LOGINFO(1, "generating consensus ID");
@@ -147,6 +150,7 @@ SideChain::SideChain(p2pool* pool, NetworkType type, const char* pool_name)
SideChain::~SideChain()
{
uv_mutex_destroy(&m_sidechainLock);
uv_mutex_destroy(&m_seenBlocksLock);
for (auto& it : m_blocksById) {
delete it.second;
}
@@ -259,6 +263,11 @@ void SideChain::fill_sidechain_data(PoolBlock& block, Wallet* w, const hash& txk
get_shares(&block, shares);
}
P2PServer* SideChain::p2pServer() const
{
return m_pool ? m_pool->p2p_server() : nullptr;
}
bool SideChain::get_shares(PoolBlock* tip, std::vector<MinerShare>& shares) const
{
shares.clear();
@@ -343,21 +352,21 @@ bool SideChain::get_shares(PoolBlock* tip, std::vector<MinerShare>& shares) cons
bool SideChain::block_seen(const PoolBlock& block)
{
MutexLock lock(m_sidechainLock);
// Check if it's some old block
if (m_chainTip && m_chainTip->m_sidechainHeight > block.m_sidechainHeight + m_chainWindowSize * 2 &&
block.m_cumulativeDifficulty < m_chainTip->m_cumulativeDifficulty) {
const PoolBlock* tip = m_chainTip;
if (tip && tip->m_sidechainHeight > block.m_sidechainHeight + m_chainWindowSize * 2 &&
block.m_cumulativeDifficulty < tip->m_cumulativeDifficulty) {
return true;
}
// Check if it was received before
MutexLock lock(m_seenBlocksLock);
return !m_seenBlocks.insert(block.m_sidechainId).second;
}
void SideChain::unsee_block(const PoolBlock& block)
{
MutexLock lock(m_sidechainLock);
MutexLock lock(m_seenBlocksLock);
m_seenBlocks.erase(block.m_sidechainId);
}
@@ -370,7 +379,7 @@ bool SideChain::add_external_block(PoolBlock& block, std::vector<hash>& missing_
return false;
}
difficulty_type min_accepted_diff;
bool too_low_diff = (block.m_difficulty < m_curDifficulty);
{
MutexLock lock(m_sidechainLock);
if (m_blocksById.find(block.m_sidechainId) != m_blocksById.end()) {
@@ -378,23 +387,25 @@ bool SideChain::add_external_block(PoolBlock& block, std::vector<hash>& missing_
return true;
}
// Find the minimum difficulty in the current PPLNS window
min_accepted_diff = m_curDifficulty;
for (PoolBlock* tmp = m_chainTip; tmp && (tmp->m_sidechainHeight + m_chainWindowSize > m_chainTip->m_sidechainHeight); tmp = get_parent(tmp)) {
if (tmp->m_difficulty < min_accepted_diff) {
min_accepted_diff = tmp->m_difficulty;
// This is mainly an anti-spam measure, not an actual verification step
if (too_low_diff) {
// Reduce required diff by 50% (by doubling this block's diff) to account for alternative chains
difficulty_type diff2 = block.m_difficulty;
diff2 += block.m_difficulty;
for (PoolBlock* tmp = m_chainTip; tmp && (tmp->m_sidechainHeight + m_chainWindowSize > m_chainTip->m_sidechainHeight); tmp = get_parent(tmp)) {
if (diff2 >= tmp->m_difficulty) {
too_low_diff = false;
break;
}
}
}
}
LOGINFO(4, "add_external_block: height = " << block.m_sidechainHeight << ", id = " << block.m_sidechainId << ", mainchain height = " << block.m_txinGenHeight);
// Reduce it by 50% to account for alternative chains. This is mainly an anti-spam measure, not an actual verification step
min_accepted_diff.lo = (min_accepted_diff.lo >> 1) | (min_accepted_diff.hi << 63);
min_accepted_diff.hi >>= 1;
if (block.m_difficulty < min_accepted_diff) {
LOGWARN(4, "add_external_block: block has too low difficulty " << block.m_difficulty << ", expected >= " << min_accepted_diff << ". Ignoring it.");
if (too_low_diff) {
LOGWARN(4, "add_external_block: block has too low difficulty " << block.m_difficulty << ", expected >= ~" << m_curDifficulty << ". Ignoring it.");
return true;
}
@@ -486,8 +497,8 @@ void SideChain::add_block(const PoolBlock& block)
);
// Save it for faster syncing on the next p2pool start
if (m_pool->p2p_server()) {
m_pool->p2p_server()->store_in_cache(block);
if (p2pServer()) {
p2pServer()->store_in_cache(block);
}
PoolBlock* new_block = new PoolBlock(block);
@@ -564,21 +575,34 @@ bool SideChain::get_block_blob(const hash& id, std::vector<uint8_t>& blob)
bool SideChain::get_outputs_blob(PoolBlock* block, uint64_t total_reward, std::vector<uint8_t>& blob)
{
std::vector<MinerShare> shares;
std::vector<uint64_t> rewards;
shares.reserve(m_chainWindowSize * 2);
rewards.reserve(m_chainWindowSize * 2);
blob.clear();
MutexLock lock(m_sidechainLock);
if (!get_shares(block, shares) || !split_reward(total_reward, shares, rewards) || (rewards.size() != shares.size())) {
auto it = m_blocksById.find(block->m_sidechainId);
if (it != m_blocksById.end()) {
PoolBlock* b = it->second;
const size_t n = b->m_outputs.size();
blob.reserve(n * 38 + 64);
writeVarint(n, blob);
for (const PoolBlock::TxOutput& output : b->m_outputs) {
writeVarint(output.m_reward, blob);
blob.emplace_back(TXOUT_TO_KEY);
blob.insert(blob.end(), output.m_ephPublicKey.h, output.m_ephPublicKey.h + HASH_SIZE);
}
block->m_outputs = b->m_outputs;
return true;
}
if (!get_shares(block, m_tmpShares) || !split_reward(total_reward, m_tmpShares, m_tmpRewards) || (m_tmpRewards.size() != m_tmpShares.size())) {
return false;
}
const size_t n = shares.size();
const size_t n = m_tmpShares.size();
blob.clear();
blob.reserve(n * 38 + 64);
writeVarint(n, blob);
@@ -588,16 +612,16 @@ bool SideChain::get_outputs_blob(PoolBlock* block, uint64_t total_reward, std::v
hash eph_public_key;
for (size_t i = 0; i < n; ++i) {
writeVarint(rewards[i], blob);
writeVarint(m_tmpRewards[i], blob);
blob.emplace_back(TXOUT_TO_KEY);
if (!shares[i].m_wallet->get_eph_public_key(block->m_txkeySec, i, eph_public_key)) {
if (!m_tmpShares[i].m_wallet->get_eph_public_key(block->m_txkeySec, i, eph_public_key)) {
LOGWARN(6, "get_eph_public_key failed at index " << i);
}
blob.insert(blob.end(), eph_public_key.h, eph_public_key.h + HASH_SIZE);
block->m_outputs.emplace_back(rewards[i], eph_public_key);
block->m_outputs.emplace_back(m_tmpRewards[i], eph_public_key);
}
return true;
@@ -738,9 +762,9 @@ uint64_t SideChain::miner_count()
MutexLock lock(m_sidechainLock);
// Delete wallets that weren't seen for more than 24 hours and return how many remain
// Delete wallets that weren't seen for more than 72 hours and return how many remain
for (auto it = m_seenWallets.begin(); it != m_seenWallets.end();) {
if (it->second + 24 * 60 * 60 <= cur_time) {
if (it->second + 72 * 60 * 60 <= cur_time) {
it = m_seenWallets.erase(it);
}
else {
@@ -954,14 +978,14 @@ void SideChain::verify_loop(PoolBlock* block)
// If it came through a broadcast, send it to our peers
if (block->m_wantBroadcast && !block->m_broadcasted) {
block->m_broadcasted = true;
if (m_pool->p2p_server() && (block->m_depth < UNCLE_BLOCK_DEPTH)) {
m_pool->p2p_server()->broadcast(*block);
if (p2pServer() && (block->m_depth < UNCLE_BLOCK_DEPTH)) {
p2pServer()->broadcast(*block);
}
}
// Save it for faster syncing on the next p2pool start
if (m_pool->p2p_server()) {
m_pool->p2p_server()->store_in_cache(*block);
if (p2pServer()) {
p2pServer()->store_in_cache(*block);
}
// Try to verify blocks on top of this one
@@ -1297,7 +1321,9 @@ void SideChain::update_chain_tip(PoolBlock* block)
", main chain height = " << log::Gray() << m_chainTip->m_txinGenHeight);
block->m_wantBroadcast = true;
m_pool->update_block_template_async();
if (m_pool) {
m_pool->update_block_template_async();
}
prune_old_blocks();
}
}
@@ -1313,7 +1339,7 @@ void SideChain::update_chain_tip(PoolBlock* block)
m_pool->update_block_template_async();
}
if (m_pool->p2p_server() && block->m_wantBroadcast && !block->m_broadcasted) {
if (p2pServer() && block->m_wantBroadcast && !block->m_broadcasted) {
block->m_broadcasted = true;
#ifdef DEBUG_BROADCAST_DELAY_MS
struct Work
@@ -1324,7 +1350,7 @@ void SideChain::update_chain_tip(PoolBlock* block)
};
Work* work = new Work{};
work->req.data = work;
work->server = m_pool->p2p_server();
work->server = p2pServer();
work->block = block;
const int err = uv_queue_work(uv_default_loop(), &work->req,
[](uv_work_t*)
@@ -1343,7 +1369,7 @@ void SideChain::update_chain_tip(PoolBlock* block)
LOGERR(1, "update_chain_tip: uv_queue_work failed, error " << uv_err_name(err));
}
#else
m_pool->p2p_server()->broadcast(*block);
p2pServer()->broadcast(*block);
#endif
}
}
@@ -1539,6 +1565,7 @@ void SideChain::prune_old_blocks()
auto it2 = m_blocksById.find(block->m_sidechainId);
if (it2 != m_blocksById.end()) {
m_blocksById.erase(it2);
unsee_block(*block);
delete block;
++num_blocks_pruned;
}
@@ -1563,7 +1590,9 @@ void SideChain::prune_old_blocks()
// If side-chain started pruning blocks it means the initial sync is complete
// It's now safe to delete cached blocks
m_pool->p2p_server()->clear_cached_blocks();
if (p2pServer()) {
p2pServer()->clear_cached_blocks();
}
}
}
+9 -1
View File
@@ -23,6 +23,7 @@
namespace p2pool {
class p2pool;
class P2PServer;
struct DifficultyData;
struct PoolBlock;
class Wallet;
@@ -71,10 +72,13 @@ public:
time_t last_updated() const;
bool is_default() const;
const PoolBlock* chainTip() const { return m_chainTip; }
static bool split_reward(uint64_t reward, const std::vector<MinerShare>& shares, std::vector<uint64_t>& rewards);
private:
p2pool* m_pool;
P2PServer* p2pServer() const;
NetworkType m_networkType;
private:
@@ -97,8 +101,12 @@ private:
PoolBlock* m_chainTip;
std::map<uint64_t, std::vector<PoolBlock*>> m_blocksByHeight;
unordered_map<hash, PoolBlock*> m_blocksById;
unordered_set<hash> m_seenBlocks;
unordered_map<hash, time_t> m_seenWallets;
std::vector<MinerShare> m_tmpShares;
std::vector<uint64_t> m_tmpRewards;
uv_mutex_t m_seenBlocksLock;
unordered_set<hash> m_seenBlocks;
std::vector<DifficultyData> m_difficultyData;
+12 -44
View File
@@ -18,8 +18,6 @@
#pragma once
#include "uv_util.h"
#include <map>
#include <set>
namespace p2pool {
@@ -46,36 +44,6 @@ public:
int listen_port() const { return m_listenPort; }
struct raw_ip
{
alignas(8) uint8_t data[16];
FORCEINLINE bool operator<(const raw_ip& other) const
{
const uint64_t* a = reinterpret_cast<const uint64_t*>(data);
const uint64_t* b = reinterpret_cast<const uint64_t*>(other.data);
if (a[1] < b[1]) return true;
if (a[1] > b[1]) return false;
return a[0] < b[0];
}
FORCEINLINE bool operator==(const raw_ip& other) const
{
const uint64_t* a = reinterpret_cast<const uint64_t*>(data);
const uint64_t* b = reinterpret_cast<const uint64_t*>(other.data);
return (a[0] == b[0]) && (a[1] == b[1]);
}
FORCEINLINE bool operator!=(const raw_ip& other) const { return !operator==(other); }
};
static_assert(sizeof(raw_ip) == 16, "struct raw_ip has invalid size");
static_assert(sizeof(in6_addr) == 16, "struct in6_addr has invalid size");
static_assert(sizeof(in_addr) == 4, "struct in_addr has invalid size");
bool connect_to_peer(bool is_v6, const raw_ip& ip, int port);
virtual void on_connect_failed(bool is_v6, const raw_ip& ip, int port);
@@ -121,21 +89,21 @@ public:
char m_readBuf[READ_BUF_SIZE];
uint32_t m_numRead;
struct WriteBuf
{
Client* m_client;
uv_write_t m_write;
char m_data[WRITE_BUF_SIZE];
};
uv_mutex_t m_writeBuffersLock;
std::vector<WriteBuf*> m_writeBuffers;
std::atomic<uint32_t> m_resetCounter{ 0 };
uv_mutex_t m_sendLock;
};
struct WriteBuf
{
Client* m_client;
uv_write_t m_write;
char m_data[WRITE_BUF_SIZE];
};
uv_mutex_t m_writeBuffersLock;
std::vector<WriteBuf*> m_writeBuffers;
struct SendCallbackBase
{
virtual ~SendCallbackBase() {}
@@ -193,12 +161,12 @@ protected:
uint32_t m_numIncomingConnections;
uv_mutex_t m_bansLock;
std::map<raw_ip, time_t> m_bans;
unordered_map<raw_ip, time_t> m_bans;
bool is_banned(const raw_ip& ip);
uv_mutex_t m_pendingConnectionsLock;
std::set<raw_ip> m_pendingConnections;
unordered_set<raw_ip> m_pendingConnections;
uv_async_t m_dropConnectionsAsync;
static void on_drop_connections(uv_async_t* async) { reinterpret_cast<TCPServer*>(async->data)->close_sockets(false); }
+35 -29
View File
@@ -46,6 +46,12 @@ TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::TCPServer(allocate_client_callback all
uv_mutex_init_checked(&m_clientsListLock);
uv_mutex_init_checked(&m_bansLock);
uv_mutex_init_checked(&m_pendingConnectionsLock);
uv_mutex_init_checked(&m_writeBuffersLock);
m_writeBuffers.resize(DEFAULT_BACKLOG);
for (size_t i = 0; i < m_writeBuffers.size(); ++i) {
m_writeBuffers[i] = new WriteBuf();
}
m_preallocatedClients.reserve(DEFAULT_BACKLOG);
for (int i = 0; i < DEFAULT_BACKLOG; ++i) {
@@ -319,8 +325,12 @@ bool TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::is_banned(const raw_ip& ip)
MutexLock lock(m_bansLock);
auto it = m_bans.find(ip);
if ((it != m_bans.end()) && (time(nullptr) < it->second)) {
return true;
if (it != m_bans.end()) {
const bool banned = (time(nullptr) < it->second);
if (!banned) {
m_bans.erase(it);
}
return banned;
}
return false;
@@ -461,6 +471,14 @@ void TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::shutdown_tcp()
uv_mutex_destroy(&m_bansLock);
uv_mutex_destroy(&m_pendingConnectionsLock);
{
MutexLock lock(m_writeBuffersLock);
for (WriteBuf* buf : m_writeBuffers) {
delete buf;
}
}
uv_mutex_destroy(&m_writeBuffersLock);
LOGINFO(1, "stopped");
}
@@ -488,18 +506,18 @@ bool TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::send_internal(Client* client, Sen
MutexLock lock0(client->m_sendLock);
typename Client::WriteBuf* buf = nullptr;
WriteBuf* buf = nullptr;
{
MutexLock lock(client->m_writeBuffersLock);
if (!client->m_writeBuffers.empty()) {
buf = client->m_writeBuffers.back();
client->m_writeBuffers.pop_back();
MutexLock lock(m_writeBuffersLock);
if (!m_writeBuffers.empty()) {
buf = m_writeBuffers.back();
m_writeBuffers.pop_back();
}
}
if (!buf) {
buf = new typename Client::WriteBuf();
buf = new WriteBuf();
}
const size_t bytes_written = callback(buf->m_data);
@@ -512,8 +530,8 @@ bool TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::send_internal(Client* client, Sen
if (bytes_written == 0) {
LOGWARN(1, "send callback wrote 0 bytes, nothing to do");
{
MutexLock lock(client->m_writeBuffersLock);
client->m_writeBuffers.push_back(buf);
MutexLock lock(m_writeBuffersLock);
m_writeBuffers.push_back(buf);
}
return true;
}
@@ -528,8 +546,8 @@ bool TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::send_internal(Client* client, Sen
const int err = uv_write(&buf->m_write, reinterpret_cast<uv_stream_t*>(&client->m_socket), bufs, 1, Client::on_write);
if (err) {
{
MutexLock lock(client->m_writeBuffersLock);
client->m_writeBuffers.push_back(buf);
MutexLock lock(m_writeBuffersLock);
m_writeBuffers.push_back(buf);
}
LOGWARN(1, "failed to start writing data to client connection " << static_cast<const char*>(client->m_addrString) << ", error " << uv_err_name(err));
return false;
@@ -760,27 +778,14 @@ TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::Client::Client()
{
Client::reset();
uv_mutex_init_checked(&m_writeBuffersLock);
uv_mutex_init_checked(&m_sendLock);
m_readBuf[0] = '\0';
m_writeBuffers.resize(2);
for (size_t i = 0; i < m_writeBuffers.size(); ++i) {
m_writeBuffers[i] = new WriteBuf();
}
}
template<size_t READ_BUF_SIZE, size_t WRITE_BUF_SIZE>
TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::Client::~Client()
{
{
MutexLock lock(m_writeBuffersLock);
for (WriteBuf* buf : m_writeBuffers) {
delete buf;
}
}
uv_mutex_destroy(&m_writeBuffersLock);
uv_mutex_destroy(&m_sendLock);
}
@@ -857,12 +862,13 @@ void TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::Client::on_read(uv_stream_t* stre
template<size_t READ_BUF_SIZE, size_t WRITE_BUF_SIZE>
void TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::Client::on_write(uv_write_t* req, int status)
{
Client::WriteBuf* buf = static_cast<Client::WriteBuf*>(req->data);
WriteBuf* buf = static_cast<WriteBuf*>(req->data);
Client* client = buf->m_client;
TCPServer* server = client->m_owner;
{
MutexLock lock(client->m_writeBuffersLock);
client->m_writeBuffers.push_back(buf);
if (server) {
MutexLock lock(server->m_writeBuffersLock);
server->m_writeBuffers.push_back(buf);
}
if (status != 0) {
+6 -3
View File
@@ -32,7 +32,7 @@ namespace p2pool {
#define STR2(X) STR(X)
#define STR(X) #X
const char* VERSION = "v1.3 (built"
const char* VERSION = "v1.3.1 (built"
#if defined(__clang__)
" with clang/" __clang_version__
#elif defined(__GNUC__)
@@ -225,7 +225,7 @@ void uv_rwlock_init_checked(uv_rwlock_t* lock)
uv_loop_t* uv_default_loop_checked()
{
if (!is_main_thread) {
if (!is_main_thread()) {
LOGERR(1, "uv_default_loop() can only be used by the main thread. Fix the code!");
#ifdef _WIN32
if (IsDebuggerPresent()) {
@@ -339,7 +339,10 @@ void BackgroundJobTracker::print_status()
}
BackgroundJobTracker bkg_jobs_tracker;
thread_local bool is_main_thread = false;
static thread_local bool main_thread = false;
void set_main_thread() { main_thread = true; }
bool is_main_thread() { return main_thread; }
bool resolve_host(std::string& host, bool& is_v6)
{
+23 -2
View File
@@ -19,7 +19,7 @@
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 5027)
#pragma warning(disable : 4623 5026 5027)
#endif
#define ROBIN_HOOD_MALLOC(size) p2pool::malloc_hook(size)
@@ -133,7 +133,9 @@ private:
};
extern BackgroundJobTracker bkg_jobs_tracker;
extern thread_local bool is_main_thread;
void set_main_thread();
bool is_main_thread();
bool resolve_host(std::string& host, bool& is_v6);
@@ -165,4 +167,23 @@ struct hash<std::array<uint8_t, N>>
}
};
template<>
struct hash<p2pool::raw_ip>
{
FORCEINLINE size_t operator()(const p2pool::raw_ip& value) const noexcept
{
return hash_bytes(value.data, sizeof(value.data));
}
};
template<>
struct hash<std::pair<uint64_t, uint64_t>>
{
FORCEINLINE size_t operator()(const std::pair<uint64_t, uint64_t>& value) const noexcept
{
static_assert(sizeof(value) == sizeof(uint64_t) * 2, "Invalid std::pair<uint64_t, uint64_t> size");
return hash_bytes(&value, sizeof(value));
}
};
} // namespace robin_hood
+3
View File
@@ -19,6 +19,9 @@
#include <uv.h>
static_assert(sizeof(in6_addr) == 16, "struct in6_addr has invalid size");
static_assert(sizeof(in_addr) == 4, "struct in_addr has invalid size");
namespace p2pool {
struct MutexLock : public nocopy_nomove
+27 -8
View File
@@ -19,6 +19,7 @@
#include "zmq_reader.h"
#include "json_parsers.h"
#include <rapidjson/document.h>
#include <random>
static constexpr char log_category_prefix[] = "ZMQReader ";
@@ -88,10 +89,14 @@ void ZMQReader::run()
char addr[32];
snprintf(addr, sizeof(addr), "tcp://%s:%u", m_address, m_zmqPort);
while (!connect(addr, m_zmqPort)) { if (m_finished.load()) return; }
if (!connect(addr)) {
return;
}
snprintf(addr, sizeof(addr), "tcp://127.0.0.1:%u", m_publisherPort);
while (!connect(addr, m_publisherPort)) { if (m_finished.load()) return; }
if (!connect(addr)) {
return;
}
m_subscriber.set(zmq::sockopt::subscribe, "json-full-chain_main");
m_subscriber.set(zmq::sockopt::subscribe, "json-full-miner_data");
@@ -126,7 +131,7 @@ void ZMQReader::run()
}
}
bool ZMQReader::connect(const char* address, uint32_t id)
bool ZMQReader::connect(const char* address)
{
struct ConnectMonitor : public zmq::monitor_t
{
@@ -139,19 +144,33 @@ bool ZMQReader::connect(const char* address, uint32_t id)
bool connected = false;
} monitor;
char buf[32];
snprintf(buf, sizeof(buf), "inproc://connect-mon-%u", id);
static uint64_t id = 0;
if (!id) {
std::random_device rd;
id = (static_cast<uint64_t>(rd()) << 32) | static_cast<uint32_t>(rd());
}
char buf[log::Stream::BUF_SIZE + 1];
log::Stream s(buf);
s << "inproc://p2pool-connect-mon-" << id << '\0';
++id;
monitor.init(m_subscriber, buf);
m_subscriber.connect(address);
using namespace std::chrono;
const system_clock::time_point start_time = system_clock::now();
system_clock::time_point start_time = system_clock::now();
while (!monitor.connected && monitor.check_event(-1)) {
const int64_t elapsed_time = duration_cast<milliseconds>(system_clock::now() - start_time).count();
const system_clock::time_point cur_time = system_clock::now();
const int64_t elapsed_time = duration_cast<milliseconds>(cur_time - start_time).count();
if (elapsed_time >= 3000) {
LOGERR(1, "failed to connect to " << address);
return false;
if (m_finished.load()) {
return false;
}
start_time = cur_time;
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ public:
private:
static void run_wrapper(void* arg);
void run();
bool connect(const char* address, uint32_t id);
bool connect(const char* address);
void parse(char* data, size_t size);
+1
View File
@@ -152,3 +152,4 @@ add_executable(${CMAKE_PROJECT_NAME} ${HEADERS} ${SOURCES})
target_link_libraries(${CMAKE_PROJECT_NAME} debug ${ZMQ_LIBRARY_DEBUG} debug ${UV_LIBRARY_DEBUG} optimized ${ZMQ_LIBRARY} optimized ${UV_LIBRARY} ${LIBS})
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/src/crypto_tests.txt" $<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>)
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/src/mainnet_test2_block.dat" $<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>)
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/src/sidechain_dump.dat" $<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>)
+40 -1
View File
@@ -16,6 +16,7 @@
*/
#include "common.h"
#include "crypto.h"
#include "pool_block.h"
#include "pow_hash.h"
#include "side_chain.h"
@@ -72,7 +73,6 @@ TEST(pool_block, deserialize)
ASSERT_EQ(b.m_cumulativeDifficulty.lo, 12544665764606ull);
ASSERT_EQ(b.m_cumulativeDifficulty.hi, 0);
ASSERT_EQ(b.m_tmpTxExtra.size(), 0);
ASSERT_EQ(b.m_tmpInts.size(), 0);
ASSERT_EQ(b.m_depth, 0);
ASSERT_EQ(b.m_verified, false);
ASSERT_EQ(b.m_invalid, false);
@@ -100,4 +100,43 @@ TEST(pool_block, deserialize)
ASSERT_EQ(b.m_difficulty.check_pow(pow_hash), true);
}
TEST(pool_block, verify)
{
init_crypto_cache();
PoolBlock b;
SideChain sidechain(nullptr, NetworkType::Mainnet);
std::ifstream f("sidechain_dump.dat", std::ios::binary | std::ios::ate);
ASSERT_EQ(f.good() && f.is_open(), true);
std::vector<uint8_t> buf(f.tellg());
f.seekg(0);
f.read(reinterpret_cast<char*>(buf.data()), buf.size());
ASSERT_EQ(f.good(), true);
for (const uint8_t *p = buf.data(), *e = buf.data() + buf.size(); p < e;) {
ASSERT_TRUE(p + sizeof(uint32_t) <= e);
const uint32_t n = *reinterpret_cast<const uint32_t*>(p);
p += sizeof(uint32_t);
ASSERT_TRUE(p + n <= e);
ASSERT_EQ(b.deserialize(p, n, sidechain), 0);
p += n;
sidechain.add_block(b);
ASSERT_TRUE(sidechain.has_block(b.m_sidechainId));
}
const PoolBlock* tip = sidechain.chainTip();
ASSERT_TRUE(tip != nullptr);
ASSERT_TRUE(tip->m_verified);
ASSERT_FALSE(tip->m_invalid);
ASSERT_EQ(tip->m_txinGenHeight, 2483901);
ASSERT_EQ(tip->m_sidechainHeight, 522805);
destroy_crypto_cache();
}
}
Binary file not shown.