diff --git a/src/block_template.cpp b/src/block_template.cpp index 4ac086e..e500a02 100644 --- a/src/block_template.cpp +++ b/src/block_template.cpp @@ -34,9 +34,6 @@ static constexpr char log_category_prefix[] = "BlockTemplate "; -// Max P2P message size (128 KB) minus BLOCK_RESPONSE header (5 bytes) -static constexpr size_t MAX_BLOCK_TEMPLATE_SIZE = 128 * 1024 - (1 + sizeof(uint32_t)); - namespace p2pool { BlockTemplate::BlockTemplate(SideChain* sidechain, RandomX_Hasher_Base* hasher) @@ -168,9 +165,7 @@ BlockTemplate& BlockTemplate::operator=(const BlockTemplate& b) static FORCEINLINE uint64_t get_base_reward(uint64_t already_generated_coins) { const uint64_t result = ~already_generated_coins >> 19; - - constexpr uint64_t min_reward = 600000000000ULL; - return (result < min_reward) ? min_reward : result; + return (result < BASE_BLOCK_REWARD) ? BASE_BLOCK_REWARD : result; } static FORCEINLINE uint64_t get_block_reward(uint64_t base_reward, uint64_t median_weight, uint64_t fees, uint64_t weight) @@ -353,14 +348,27 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const b->m_transactions.resize(1); b->m_outputs.clear(); - // Block template size without coinbase outputs and transactions (add 1+1 more bytes for output and tx count if they go above 128) - size_t k = b->serialize_mainchain_data().size() + b->serialize_sidechain_data().size() + 2; + // Block template size without coinbase outputs and transactions (minus 2 bytes for output and tx count dummy varints) + size_t k = b->serialize_mainchain_data().size() + b->serialize_sidechain_data().size() - 2; - // a rough estimation of outputs' size - // all outputs have <= 5 bytes for each output's reward, and up to 18 outputs can have 6 bytes for output's reward - k += m_shares.size() * (5 /* reward */ + 1 /* tx_type */ + HASH_SIZE /* stealth address */ + 1 /* viewtag */) + 18; + // Add output and tx count real varints + writeVarint(m_shares.size(), [&k](uint8_t) { ++k; }); + writeVarint(m_mempoolTxs.size(), [&k](uint8_t) { ++k; }); - const size_t max_transactions = (MAX_BLOCK_TEMPLATE_SIZE > k) ? ((MAX_BLOCK_TEMPLATE_SIZE - k) / HASH_SIZE) : 0; + // Add a rough upper bound estimation of outputs' size. All outputs have <= 5 bytes for each output's reward (< 0.034359738368 XMR per output) + k += m_shares.size() * (5 /* reward */ + 1 /* tx_type */ + HASH_SIZE /* stealth address */ + 1 /* viewtag */); + + // >= 0.034359738368 XMR is required for a 6 byte varint, add 1 byte per each potential 6-byte varint + { + uint64_t r = BASE_BLOCK_REWARD; + for (const auto& tx : m_mempoolTxs) { + r += tx.fee; + } + k += r / 34359738368ULL; + } + + const size_t max_transactions = (MAX_BLOCK_SIZE > k) ? ((MAX_BLOCK_SIZE - k) / HASH_SIZE) : 0; + LOGINFO(6, max_transactions << " transactions can be taken with current block size limit"); if (max_transactions == 0) { m_mempoolTxs.clear(); @@ -678,6 +686,7 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const m_fullDataBlob = m_blockTemplateBlob; m_fullDataBlob.insert(m_fullDataBlob.end(), sidechain_data.begin(), sidechain_data.end()); + LOGINFO(6, "blob size = " << m_fullDataBlob.size()); m_poolBlockTemplate->m_sidechainId = calc_sidechain_hash(0); @@ -705,9 +714,6 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const if (result != 0) { LOGERR(1, "pool block blob generation and/or parsing is broken, error " << result); } - else { - LOGINFO(6, "blob size = " << m_fullDataBlob.size()); - } } memset(m_minerTxKeccakState, 0, sizeof(m_minerTxKeccakState)); diff --git a/src/p2p_server.cpp b/src/p2p_server.cpp index 7865ddd..274bd7c 100644 --- a/src/p2p_server.cpp +++ b/src/p2p_server.cpp @@ -61,8 +61,7 @@ P2PServer::P2PServer(p2pool* pool) , m_fastestPeer(nullptr) { m_callbackBuf.resize(P2P_BUF_SIZE); - - m_blockDeserializeBuf.reserve(131072); + m_blockDeserializeBuf.reserve(MAX_BLOCK_SIZE); // Diffuse the initial state in case it has low quality m_rng.discard(10000); diff --git a/src/p2p_server.h b/src/p2p_server.h index ea72593..a773567 100644 --- a/src/p2p_server.h +++ b/src/p2p_server.h @@ -18,6 +18,7 @@ #pragma once #include "tcp_server.h" +#include "pool_block.h" #include namespace p2pool { @@ -26,7 +27,10 @@ class p2pool; struct PoolBlock; class BlockCache; -static constexpr size_t P2P_BUF_SIZE = 128 * 1024; +// Max block size plus BLOCK_RESPONSE header (5 bytes) +static constexpr uint64_t P2P_BUF_SIZE = MAX_BLOCK_SIZE + (1 + sizeof(uint32_t)); +static_assert((P2P_BUF_SIZE & (P2P_BUF_SIZE - 1)) == 0, "P2P_BUF_SIZE is not a power of 2, fix MAX_BLOCK_SIZE"); + static constexpr size_t PEER_LIST_RESPONSE_MAX_PEERS = 16; static constexpr int DEFAULT_P2P_PORT = 37889; static constexpr int DEFAULT_P2P_PORT_MINI = 37888; diff --git a/src/pool_block.h b/src/pool_block.h index 49f2b25..3c9b784 100644 --- a/src/pool_block.h +++ b/src/pool_block.h @@ -47,6 +47,12 @@ class SideChain; * Pool block's PoW hash is calculated from the Monero block template part using Monero's consensus rules */ +// 128 KB minus BLOCK_RESPONSE P2P protocol header (5 bytes) +static constexpr uint64_t MAX_BLOCK_SIZE = 128 * 1024 - 5; + +// 0.6 XMR +static constexpr uint64_t BASE_BLOCK_REWARD = 600000000000ULL; + struct DifficultyData { FORCEINLINE DifficultyData(uint64_t t, const difficulty_type& d) : m_timestamp(t), m_cumulativeDifficulty(d) {} diff --git a/src/pool_block_parser.inl b/src/pool_block_parser.inl index f91d39a..6deb041 100644 --- a/src/pool_block_parser.inl +++ b/src/pool_block_parser.inl @@ -27,7 +27,7 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si { try { // Sanity check - if (!data || (size > 128 * 1024)) { + if (!data || (size > MAX_BLOCK_SIZE)) { return __LINE__; } @@ -139,7 +139,7 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si READ_VARINT(tmp); // Sanity check - if ((tmp == 0) || (tmp > 128 * 1024)) { + if ((tmp == 0) || (tmp > MAX_BLOCK_SIZE)) { return __LINE__; } @@ -148,7 +148,7 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si // Technically some p2pool node could keep stuffing block with transactions until reward is less than 0.6 XMR // But default transaction picking algorithm never does that. It's better to just ban such nodes - if (total_reward < 600000000000ULL) { + if (total_reward < BASE_BLOCK_REWARD) { return __LINE__; } @@ -341,6 +341,11 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si hash check; const std::vector& consensus_id = sidechain.consensus_id(); const int data_size = static_cast((data_end - data_begin) + outputs_blob_size_diff + transactions_blob_size_diff); + + if (data_size > static_cast(MAX_BLOCK_SIZE)) { + return __LINE__; + } + keccak_custom( [nonce_offset, extra_nonce_offset, sidechain_hash_offset, data_begin, data_size, &consensus_id, &outputs_blob, outputs_blob_size_diff, outputs_offset, outputs_blob_size, transactions_blob, transactions_blob_size_diff, transactions_offset, transactions_blob_size](int offset) -> uint8_t {