De-duplicate tx hashes and pub keys to save memory (off by default) (#382)

P2Pool-main: 8.2 MB saved
P2Pool-mini: 66 MB saved
P2Pool-nano: 25.2 MB saved

The feature is available only when building from source and is intended for use on low-memory systems (for example, a VPS server with < 1 GB RAM).

It only makes sense to use with `--no-cache --no-randomx` in the command line because cache and RandomX hasher take much more memory.
This commit is contained in:
SChernykh
2025-10-18 12:21:16 +02:00
committed by GitHub
parent 52bcbda381
commit e2f0ec7c69
25 changed files with 640 additions and 103 deletions
+2 -2
View File
@@ -429,8 +429,8 @@ jobs:
strategy:
matrix:
config:
- {c: "gcc", cxx: "g++", flags: "-ffunction-sections -Wno-error=maybe-uninitialized -Wno-error=attributes -Wno-unknown-attributes"}
- {c: "clang", cxx: "clang++", flags: "-fuse-ld=lld -Wno-unused-command-line-argument -Wno-nan-infinity-disabled -Wno-unknown-attributes"}
- {c: "gcc", cxx: "g++", flags: "-ffunction-sections -Wno-error=maybe-uninitialized -Wno-error=attributes -Wno-attributes"}
- {c: "clang", cxx: "clang++", flags: "-fuse-ld=lld -Wno-unused-command-line-argument -Wno-nan-infinity-disabled -Wno-attributes"}
defaults:
run:
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
run: |
mkdir build
cd build
cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_C_COMPILER=clang-21 -DCMAKE_CXX_COMPILER=clang++-21 -DDEV_CLANG_TIDY=ON -DSTATIC_LIBS=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_C_COMPILER=clang-21 -DCMAKE_CXX_COMPILER=clang++-21 -DDEV_CLANG_TIDY=ON -DSTATIC_LIBS=ON -DWITH_INDEXED_HASHES=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
- name: Run clang-tidy
run: |
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
cd tests
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DDEV_DEBUG=ON -DWITH_COVERAGE=ON -DCMAKE_C_COMPILER=clang-21 -DCMAKE_CXX_COMPILER=clang++-21 -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
cmake .. -DCMAKE_BUILD_TYPE=Release -DDEV_DEBUG=ON -DWITH_INDEXED_HASHES=ON -DWITH_COVERAGE=ON -DCMAKE_C_COMPILER=clang-21 -DCMAKE_CXX_COMPILER=clang++-21 -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
make -j$(nproc) p2pool_tests
- name: Run tests
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
run: |
mkdir build
cd build
cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DSTATIC_LIBS=ON -DWITH_GRPC=OFF -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DSTATIC_LIBS=ON -DWITH_GRPC=OFF -DWITH_INDEXED_HASHES=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
python ../cppcheck/remove_external.py compile_commands.json
- name: Run cppcheck
+7 -7
View File
@@ -57,7 +57,7 @@ jobs:
run: |
mkdir build
cd build
cmake .. -DDEV_TEST_SYNC=ON -DDEV_WITH_TSAN=ON -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_FLAGS='-fsanitize=thread -Og -fno-omit-frame-pointer -g' -DCMAKE_CXX_FLAGS='-fsanitize=thread -Og -fno-omit-frame-pointer -g' -DWITH_LTO=OFF -DSTATIC_LIBS=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
cmake .. -DDEV_TEST_SYNC=ON -DDEV_WITH_TSAN=ON -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_FLAGS='-fsanitize=thread -Og -fno-omit-frame-pointer -g' -DCMAKE_CXX_FLAGS='-fsanitize=thread -Og -fno-omit-frame-pointer -g' -DWITH_LTO=OFF -DSTATIC_LIBS=ON -DWITH_INDEXED_HASHES=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
make -j$(nproc) p2pool
- name: Run p2pool
@@ -148,7 +148,7 @@ jobs:
run: |
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-21 -DCMAKE_CXX_COMPILER=clang++-21 -DCMAKE_C_FLAGS='-fsanitize=memory -fsanitize-recover -fsanitize-memory-track-origins -fno-omit-frame-pointer' -DCMAKE_CXX_FLAGS='-nostdinc++ -nostdlib++ -fsanitize=memory -fsanitize-recover -fsanitize-memory-track-origins -isystem /tmp/libcxx_msan/include/c++/v1 -L/tmp/libcxx_msan/lib -Wl,-rpath /tmp/libcxx_msan/lib -lc++ -lc++abi -Wno-unused-command-line-argument -fuse-ld=lld-21 -fno-omit-frame-pointer' -DDEV_TEST_SYNC=ON -DDEV_WITH_MSAN=ON -DWITH_LTO=OFF -DSTATIC_LIBS=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-21 -DCMAKE_CXX_COMPILER=clang++-21 -DCMAKE_C_FLAGS='-fsanitize=memory -fsanitize-recover -fsanitize-memory-track-origins -fno-omit-frame-pointer' -DCMAKE_CXX_FLAGS='-nostdinc++ -nostdlib++ -fsanitize=memory -fsanitize-recover -fsanitize-memory-track-origins -isystem /tmp/libcxx_msan/include/c++/v1 -L/tmp/libcxx_msan/lib -Wl,-rpath /tmp/libcxx_msan/lib -lc++ -lc++abi -Wno-unused-command-line-argument -fuse-ld=lld-21 -fno-omit-frame-pointer' -DDEV_TEST_SYNC=ON -DDEV_WITH_MSAN=ON -DWITH_LTO=OFF -DSTATIC_LIBS=ON -DWITH_INDEXED_HASHES=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
make -j$(nproc) p2pool
- name: Run p2pool
@@ -201,7 +201,7 @@ jobs:
run: |
mkdir build
cd build
cmake .. -DDEV_TEST_SYNC=ON -DDEV_WITH_UBSAN=ON -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DWITH_LTO=OFF -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
cmake .. -DDEV_TEST_SYNC=ON -DDEV_WITH_UBSAN=ON -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DWITH_LTO=OFF -DWITH_INDEXED_HASHES=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
make -j$(nproc) p2pool
- name: Run p2pool
@@ -254,7 +254,7 @@ jobs:
run: |
mkdir build
cd build
cmake .. -DDEV_TEST_SYNC=ON -DDEV_DEBUG=ON -DDEV_WITH_ASAN=ON -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_FLAGS="-fno-omit-frame-pointer -fsanitize=address -Og -g" -DCMAKE_CXX_FLAGS="-fno-omit-frame-pointer -fsanitize=address -Og -g" -DWITH_LTO=OFF -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
cmake .. -DDEV_TEST_SYNC=ON -DDEV_DEBUG=ON -DDEV_WITH_ASAN=ON -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_FLAGS="-fno-omit-frame-pointer -fsanitize=address -Og -g" -DCMAKE_CXX_FLAGS="-fno-omit-frame-pointer -fsanitize=address -Og -g" -DWITH_LTO=OFF -DWITH_INDEXED_HASHES=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
make -j$(nproc) p2pool
- name: Run p2pool
@@ -329,7 +329,7 @@ jobs:
run: |
mkdir build
cd build
cmake .. -DCMAKE_C_COMPILER="$(brew --prefix llvm@18)/bin/clang" -DCMAKE_CXX_COMPILER="$(brew --prefix llvm@18)/bin/clang++" -DCMAKE_AR="$(brew --prefix llvm@18)/bin/llvm-ar" -DCMAKE_RANLIB="$(brew --prefix llvm@18)/bin/llvm-ranlib" -DCMAKE_C_FLAGS='${{ matrix.config.flags }}' -DCMAKE_CXX_FLAGS='${{ matrix.config.flags }}' -DWITH_LTO=OFF -DSTATIC_LIBS=ON -DDEV_TEST_SYNC=ON -DDEV_DEBUG=ON
cmake .. -DCMAKE_C_COMPILER="$(brew --prefix llvm@18)/bin/clang" -DCMAKE_CXX_COMPILER="$(brew --prefix llvm@18)/bin/clang++" -DCMAKE_AR="$(brew --prefix llvm@18)/bin/llvm-ar" -DCMAKE_RANLIB="$(brew --prefix llvm@18)/bin/llvm-ranlib" -DCMAKE_C_FLAGS='${{ matrix.config.flags }}' -DCMAKE_CXX_FLAGS='${{ matrix.config.flags }}' -DWITH_LTO=OFF -DSTATIC_LIBS=ON -DDEV_TEST_SYNC=ON -DDEV_DEBUG=ON -DWITH_INDEXED_HASHES=ON
make -j4 p2pool
- name: Run p2pool
@@ -378,7 +378,7 @@ jobs:
run: |
mkdir build
cd build
cmake .. -G "Visual Studio 17 2022" -DCMAKE_SYSTEM_VERSION="10.0" -DDEV_TEST_SYNC=ON -DDEV_WITH_ASAN=ON -DDEV_DEBUG=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
cmake .. -G "Visual Studio 17 2022" -DCMAKE_SYSTEM_VERSION="10.0" -DDEV_TEST_SYNC=ON -DDEV_WITH_ASAN=ON -DDEV_DEBUG=ON -DWITH_INDEXED_HASHES=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
& "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Msbuild\\Current\\Bin\\amd64\\msbuild" -v:m /m /p:Configuration=Debug p2pool.vcxproj
- name: Run p2pool
@@ -424,7 +424,7 @@ jobs:
run: |
mkdir build
cd build
cmake .. -G "Visual Studio 17 2022" -DCMAKE_SYSTEM_VERSION="10.0" -DDEV_TEST_SYNC=ON -DDEV_DEBUG=ON -DDEV_TRACK_MEMORY=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
cmake .. -G "Visual Studio 17 2022" -DCMAKE_SYSTEM_VERSION="10.0" -DDEV_TEST_SYNC=ON -DDEV_DEBUG=ON -DDEV_TRACK_MEMORY=ON -DWITH_INDEXED_HASHES=ON -DCMAKE_POLICY_VERSION_MINIMUM="3.5"
& "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Msbuild\\Current\\Bin\\amd64\\msbuild" -v:m /m /p:Configuration=RelWithDebInfo p2pool.vcxproj
- name: Run p2pool
+6
View File
@@ -18,6 +18,7 @@ option(WITH_LTO "Use link-time compiler optimization (if linking fails for you,
option(WITH_UPNP "Include UPnP support. If this is turned off, p2pool will not be able to configure port forwarding on UPnP-enabled routers." ON)
option(WITH_GRPC "Include gRPC support. If this is turned off, p2pool will not be able to merge mine with Tari." ON)
option(WITH_TLS "Include TLS support. If this is turned off, p2pool will not support Stratum TLS connections, and lack many other security features. It's recommended to keep it ON!" ON)
option(WITH_INDEXED_HASHES "Save memory used for storing transaction hashes and public keys (a bit slower block verification - use it only if you compile for a very low memory system)" OFF)
option(WITH_MERGE_MINING_DONATION "Merge mine donations to the author. This doesn't affect your hashrate or payouts in any way - only unused merge mining capacity will be utilised. If you merge mine yourself, your settings will take priority." ON)
@@ -190,6 +191,11 @@ if (WITH_TLS)
set(SOURCES ${SOURCES} src/tls.cpp)
endif()
if (WITH_INDEXED_HASHES)
add_compile_definitions(WITH_INDEXED_HASHES)
set(SOURCES ${SOURCES} src/indexed_hash.cpp)
endif()
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Header Files" FILES ${HEADERS})
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" FILES ${SOURCES})
+14 -7
View File
@@ -395,7 +395,8 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
LOGERR(1, "Added transaction " << tx.id << " twice. Fix the code!");
continue;
}
m_transactionHashes.insert(m_transactionHashes.end(), tx.id.h, tx.id.h + HASH_SIZE);
const hash h = tx.id;
m_transactionHashes.insert(m_transactionHashes.end(), h.h, h.h + HASH_SIZE);
final_fees += tx.fee;
final_weight += tx.weight;
}
@@ -476,7 +477,8 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
LOGERR(1, "Added transaction " << tx.id << " twice. Fix the code!");
continue;
}
m_transactionHashes.insert(m_transactionHashes.end(), tx.id.h, tx.id.h + HASH_SIZE);
const hash h = tx.id;
m_transactionHashes.insert(m_transactionHashes.end(), h.h, h.h + HASH_SIZE);
final_fees += tx.fee;
final_weight += tx.weight;
}
@@ -728,7 +730,7 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
LOGINFO(3, "final reward = " << log::Gray() << log::XMRAmount(final_reward) << log::NoColor() <<
", weight = " << log::Gray() << final_weight << log::NoColor() <<
", outputs = " << log::Gray() << m_poolBlockTemplate->m_outputs.size() << log::NoColor() <<
", outputs = " << log::Gray() << m_poolBlockTemplate->m_outputAmounts.size() << log::NoColor() <<
", " << log::Gray() << m_numTransactionHashes << log::NoColor() <<
" of " << log::Gray() << m_mempoolTxs.size() << log::NoColor() << " transactions included");
@@ -847,7 +849,8 @@ void BlockTemplate::select_mempool_transactions(const Mempool& mempool)
PoolBlock* b = m_poolBlockTemplate;
b->m_transactions.clear();
b->m_transactions.resize(1);
b->m_outputs.clear();
b->m_ephPublicKeys.clear();
b->m_outputAmounts.clear();
// 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;
@@ -909,8 +912,11 @@ int BlockTemplate::create_miner_tx(const MinerData& data, const std::vector<Mine
// Number of outputs (1 output per miner)
writeVarint(num_outputs, m_minerTx);
m_poolBlockTemplate->m_outputs.clear();
m_poolBlockTemplate->m_outputs.reserve(num_outputs);
m_poolBlockTemplate->m_ephPublicKeys.clear();
m_poolBlockTemplate->m_outputAmounts.clear();
m_poolBlockTemplate->m_ephPublicKeys.reserve(num_outputs);
m_poolBlockTemplate->m_outputAmounts.reserve(num_outputs);
uint64_t reward_amounts_weight = 0;
for (size_t i = 0; i < num_outputs; ++i) {
@@ -932,7 +938,8 @@ int BlockTemplate::create_miner_tx(const MinerData& data, const std::vector<Mine
LOGERR(1, "get_eph_public_key failed at index " << i);
}
m_minerTx.insert(m_minerTx.end(), eph_public_key.h, eph_public_key.h + HASH_SIZE);
m_poolBlockTemplate->m_outputs.emplace_back(m_rewards[i], eph_public_key, view_tag);
m_poolBlockTemplate->m_ephPublicKeys.emplace_back(eph_public_key);
m_poolBlockTemplate->m_outputAmounts.emplace_back(m_rewards[i], view_tag);
}
m_minerTx.emplace_back(view_tag);
+4
View File
@@ -107,7 +107,11 @@ private:
PoolBlock* m_poolBlockTemplate;
#ifdef P2POOL_UNIT_TESTS
BlockTemplate* m_oldTemplates[1] = {};
#else
BlockTemplate* m_oldTemplates[4] = {};
#endif
std::atomic<uint64_t> m_finalReward;
+92 -1
View File
@@ -377,6 +377,85 @@ FORCEINLINE difficulty_type operator/(const difficulty_type& a, const T& b)
return result;
}
#ifdef WITH_INDEXED_HASHES
struct indexed_hash
{
enum {
BUCKET_BITS = 12,
BUCKET_SHIFT = 32 - BUCKET_BITS,
};
static_assert((BUCKET_BITS > 0) && (BUCKET_BITS < 32), "Invalid bucket bit size");
FORCEINLINE indexed_hash() : m_index(std::numeric_limits<uint32_t>::max()) {}
explicit indexed_hash(const hash& h);
~indexed_hash();
indexed_hash(const indexed_hash& h);
FORCEINLINE indexed_hash(indexed_hash&& h) : m_index(h.m_index) { h.m_index = std::numeric_limits<uint32_t>::max(); }
indexed_hash& operator=(const indexed_hash& h);
indexed_hash& operator=(indexed_hash&& h);
FORCEINLINE indexed_hash& operator=(const hash& h)
{
if (*this == h) {
return *this;
}
return operator=(indexed_hash(h));
}
FORCEINLINE bool operator==(const indexed_hash& h) const { return m_index == h.m_index; }
FORCEINLINE bool operator==(const hash& h) const
{
const uint32_t i = m_index;
if (i == std::numeric_limits<uint32_t>::max()) {
return h.empty();
}
const uint32_t bucket = i >> BUCKET_SHIFT;
if (bucket != get_bucket(h)) {
return false;
}
return is_same(i, bucket, h);
}
FORCEINLINE operator hash() const
{
const uint32_t i = m_index;
if (i == std::numeric_limits<uint32_t>::max()) {
return {};
}
return get(i);
}
FORCEINLINE uint32_t get_bucket() const { return m_index >> BUCKET_SHIFT; }
static void cleanup_storage();
static void print_status();
private:
uint32_t m_index;
static FORCEINLINE uint32_t get_bucket(const hash& h) { return h.u64()[3] >> (64 - BUCKET_BITS); }
static hash get(uint32_t index);
static bool is_same(uint32_t index, uint32_t bucket, const hash& h);
};
static_assert(sizeof(indexed_hash) == 4, "indexed_hash has wrong size");
#else
typedef hash indexed_hash;
#endif
struct TxMempoolData
{
FORCEINLINE TxMempoolData() : id(), blob_size(0), weight(0), fee(0), time_received(0) {}
@@ -395,10 +474,22 @@ struct TxMempoolData
if (weight > tx.weight) return false;
// If two transactions have exactly the same fee and weight, just order them by id
#ifdef WITH_INDEXED_HASHES
const uint32_t id_bucket = id.get_bucket();
const uint32_t tx_id_bucket = tx.id.get_bucket();
if (id_bucket < tx_id_bucket) return true;
if (id_bucket > tx_id_bucket) return false;
const hash h1 = id;
const hash h2 = tx.id;
return h1 < h2;
#else
return id < tx.id;
#endif
}
hash id;
indexed_hash id;
uint64_t blob_size;
uint64_t weight;
uint64_t fee;
+4
View File
@@ -224,6 +224,10 @@ static void do_status(p2pool *m_pool, const char * /* args */)
m_pool->print_merge_mining_status();
#ifdef WITH_INDEXED_HASHES
indexed_hash::print_status();
#endif
bkg_jobs_tracker->print_status();
if (p2p) {
+9 -8
View File
@@ -218,7 +218,7 @@ public:
{
WriteLock lock(derivations_lock);
auto entry = derivations->emplace(index, DerivationEntry{ derivation, { 0xFFFFFFFFUL, 0xFFFFFFFFUL }, {}, t }).first;
auto entry = derivations->emplace(index, DerivationEntry{ derivation, { 0xFFFFFFFFUL, 0xFFFFFFFFUL }, {}, static_cast<uint32_t>(t) }).first;
entry->second.add_view_tag(static_cast<uint32_t>(output_index << 8) | view_tag);
}
@@ -262,7 +262,7 @@ public:
const uint64_t t = seconds_since_epoch();
{
WriteLock lock(public_keys_lock);
public_keys->emplace(index, PublicKeyEntry{ derived_key, t });
public_keys->emplace(index, PublicKeyEntry{ static_cast<indexed_hash>(derived_key), static_cast<uint32_t>(t) });
}
return true;
@@ -297,7 +297,7 @@ public:
const uint64_t t = seconds_since_epoch();
{
WriteLock lock(tx_keys_lock);
tx_keys->emplace(index, TxKeyEntry{ pub, sec, t });
tx_keys->emplace(index, TxKeyEntry{ pub, sec, static_cast<uint32_t>(t) });
}
}
@@ -306,7 +306,8 @@ public:
if (timestamp) {
auto clean_old = [timestamp](auto* table) {
for (auto it = table->begin(); it != table->end();) {
if (it->second.m_timestamp < timestamp) {
// Wraparound-safe way of checking "it->second.m_timestamp < timestamp"
if (((it->second.m_timestamp - static_cast<uint32_t>(timestamp)) & 0x80000000UL) != 0) {
it = table->erase(it);
}
else {
@@ -357,7 +358,7 @@ private:
uint32_t m_viewTags1[2] = { 0xFFFFFFFFUL, 0xFFFFFFFFUL };
std::vector<uint32_t> m_viewTags2;
// cppcheck-suppress unusedStructMember
uint64_t m_timestamp;
uint32_t m_timestamp;
FORCEINLINE bool find_view_tag(size_t output_index, uint8_t& view_tag) const
{
@@ -407,9 +408,9 @@ private:
struct PublicKeyEntry
{
hash m_key;
indexed_hash m_key;
// cppcheck-suppress unusedStructMember
uint64_t m_timestamp;
uint32_t m_timestamp;
};
struct TxKeyEntry
@@ -417,7 +418,7 @@ private:
hash m_pub;
hash m_sec;
// cppcheck-suppress unusedStructMember
uint64_t m_timestamp;
uint32_t m_timestamp;
};
typedef unordered_map<std::array<uint8_t, HASH_SIZE * 2>, DerivationEntry> DerivationsMap;
+291
View File
@@ -0,0 +1,291 @@
/*
* This file is part of the Monero P2Pool <https://github.com/SChernykh/p2pool>
* Copyright (c) 2021-2025 SChernykh <https://github.com/SChernykh>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "common.h"
#include "util.h"
#include "uv_util.h"
LOG_CATEGORY(indexed_hash)
namespace p2pool {
static constexpr uint32_t BUCKET_COUNT = 1U << indexed_hash::BUCKET_BITS;
static constexpr uint32_t INDEX_MASK = (1U << indexed_hash::BUCKET_SHIFT) - 1U;
// Stores hashes only
static std::vector<hash> storage1[BUCKET_COUNT];
// Stores first 4 bytes of each hash (for better cache locality during search) and reference counters
static std::vector<std::pair<uint32_t, uint32_t>> storage2[BUCKET_COUNT];
static constexpr uint32_t LOCK_COUNT = 64;
static ReadWriteLock locks[LOCK_COUNT];
static FORCEINLINE void decref(uint32_t index)
{
if (index == std::numeric_limits<uint32_t>::max()) {
return;
}
const uint32_t bucket = index >> indexed_hash::BUCKET_SHIFT;
index &= INDEX_MASK;
auto& d2 = storage2[bucket];
WriteLock lock(locks[bucket % LOCK_COUNT]);
auto& p2 = d2[index];
#ifdef P2POOL_DEBUGGING
if (p2.second == 0) {
LOGERR(0, "fatal error: reference counter is 0 when it shouldn't be 0");
PANIC_STOP();
}
#endif
--p2.second;
}
static FORCEINLINE void incref(uint32_t index)
{
if (index == std::numeric_limits<uint32_t>::max()) {
return;
}
const uint32_t bucket = index >> indexed_hash::BUCKET_SHIFT;
index &= INDEX_MASK;
auto& d2 = storage2[bucket];
WriteLock lock(locks[bucket % LOCK_COUNT]);
++d2[index].second;
}
indexed_hash::indexed_hash(const hash& h)
{
if (h.empty()) {
m_index = std::numeric_limits<uint32_t>::max();
return;
}
const uint32_t bucket = get_bucket(h);
auto& d1 = storage1[bucket];
auto& d2 = storage2[bucket];
const uint32_t h32 = static_cast<uint32_t>(h.u64()[0]);
uint32_t first_free_slot = std::numeric_limits<uint32_t>::max();
WriteLock lock(locks[bucket % LOCK_COUNT]);
const uint32_t n = static_cast<uint32_t>(d1.size());
for (uint32_t i = 0; i < n; ++i) {
auto& p2 = d2[i];
if ((p2.first == h32) && (d1[i] == h)) {
m_index = (bucket << BUCKET_SHIFT) | i;
++p2.second;
return;
}
if ((p2.second == 0) && (first_free_slot == std::numeric_limits<uint32_t>::max())) {
first_free_slot = i;
}
}
if (first_free_slot != std::numeric_limits<uint32_t>::max()) {
m_index = (bucket << BUCKET_SHIFT) | first_free_slot;
d1[first_free_slot] = h;
auto& p2 = d2[first_free_slot];
p2.first = h32;
p2.second = 1;
}
else {
if (n > INDEX_MASK) {
LOGERR(0, "fatal error: storage overflow");
PANIC_STOP();
}
m_index = (bucket << BUCKET_SHIFT) | n;
if (n == d1.capacity()) {
const uint32_t new_capacity = static_cast<uint32_t>(std::min<size_t>(n + ((n + 3) / 4), INDEX_MASK + 1));
d1.reserve(new_capacity);
d2.reserve(new_capacity);
}
d1.emplace_back(h);
d2.emplace_back(h32, 1);
}
}
indexed_hash::~indexed_hash()
{
decref(m_index);
}
indexed_hash::indexed_hash(const indexed_hash& h)
{
const uint32_t i = h.m_index;
m_index = i;
incref(i);
}
indexed_hash& indexed_hash::operator=(const indexed_hash& h)
{
if (this == &h) {
return *this;
}
const uint32_t i = m_index;
const uint32_t j = h.m_index;
if (i == j) {
return *this;
}
decref(i);
m_index = j;
incref(j);
return *this;
}
indexed_hash& indexed_hash::operator=(indexed_hash&& h)
{
if (this == &h) {
return *this;
}
const uint32_t i = m_index;
const uint32_t j = h.m_index;
// Empty h because we're moving it
h.m_index = std::numeric_limits<uint32_t>::max();
if (i == j) {
// If h had the same index, decrease h's reference counter because now we have 1 instead of 2 objects using this index
decref(j);
return *this;
}
// If h had a different index, decrease our reference counter because our index will be overwritten by the index from h
decref(i);
// No need to change the reference counter anymore because now we are referencing the hash that was referenced by h
m_index = j;
return *this;
}
hash indexed_hash::get(uint32_t index)
{
const uint32_t bucket = index >> BUCKET_SHIFT;
const auto& d1 = storage1[bucket];
index &= INDEX_MASK;
ReadLock lock(locks[bucket % LOCK_COUNT]);
#ifdef P2POOL_DEBUGGING
if (storage2[bucket][index].second == 0) {
LOGERR(0, "fatal error: reference counter is 0 when it shouldn't be 0");
PANIC_STOP();
}
#endif
return d1[index];
}
bool indexed_hash::is_same(uint32_t index, uint32_t bucket, const hash& h)
{
const auto& d1 = storage1[bucket];
index &= INDEX_MASK;
ReadLock lock(locks[bucket % LOCK_COUNT]);
#ifdef P2POOL_DEBUGGING
if (storage2[bucket][index].second == 0) {
LOGERR(0, "fatal error: reference counter is 0 when it shouldn't be 0");
PANIC_STOP();
}
#endif
return d1[index] == h;
}
void indexed_hash::cleanup_storage()
{
for (auto& d1 : storage1) {
d1.clear();
d1.shrink_to_fit();
}
for (auto& d2 : storage2) {
#ifdef P2POOL_DEBUGGING
for (const auto& p2 : d2) {
if (p2.second) {
LOGERR(0, "storage is not empty at exit. Fix the code!");
PANIC_STOP();
}
}
#endif
d2.clear();
d2.shrink_to_fit();
}
}
void indexed_hash::print_status()
{
size_t total_hashes_stored = 0;
size_t total_refcount = 0;
size_t memory_used = 0;
const auto& s1 = storage1;
const auto& s2 = storage2;
for (uint32_t i = 0; i < LOCK_COUNT; ++i) {
ReadLock lock(locks[i]);
for (uint32_t j = i; j < BUCKET_COUNT; j += LOCK_COUNT) {
memory_used += s1[j].capacity() * HASH_SIZE + s2[j].capacity() * sizeof(uint32_t) * 2;
for (const auto& p2 : storage2[j]) {
if (p2.second) {
++total_hashes_stored;
total_refcount += p2.second;
}
}
}
}
memory_used += total_refcount * sizeof(indexed_hash);
LOGINFO(4, "status" <<
"\nTotal hashes stored = " << total_hashes_stored <<
"\nTotal refcount = " << total_refcount <<
"\nMemory used = " << static_cast<double>(memory_used) / 1048576.0 << " MB (reduced from " << static_cast<double>(total_refcount * HASH_SIZE) / 1048576.0 << " MB)"
);
}
} // namespace p2pool
+17
View File
@@ -85,6 +85,23 @@ struct parse_wrapper<T, hash>
}
};
#ifdef WITH_INDEXED_HASHES
template<typename T>
struct parse_wrapper<T, indexed_hash>
{
static NOINLINE bool parse(T& v, const char* name, indexed_hash& out_value)
{
hash h;
if (!parse_wrapper<T, hash>::parse(v, name, h)) {
return false;
}
out_value = static_cast<indexed_hash>(h);
return true;
}
};
#endif
template<typename T>
struct parse_wrapper<T, difficulty_type>
{
+8 -1
View File
@@ -259,6 +259,13 @@ template<> struct Stream::Entry<hash>
}
};
#ifdef WITH_INDEXED_HASHES
template<> struct Stream::Entry<indexed_hash>
{
static FORCEINLINE void put(const indexed_hash& data, Stream* wrapper) { Stream::Entry<hash>::put(data, wrapper); }
};
#endif
template<> struct Stream::Entry<difficulty_type>
{
static NOINLINE void put(const difficulty_type& data, Stream* wrapper)
@@ -586,7 +593,7 @@ struct DummyStream
#define LOGWARN(level, ...) SIDE_EFFECT_CHECK(level, log_category_prefix << __VA_ARGS__)
#ifdef P2POOL_ABORT_ON_LOG_ERROR
#define LOGERR(level, ...) abort()
#define LOGERR(level, ...) do { SIDE_EFFECT_CHECK(level, log_category_prefix << __VA_ARGS__); abort(); } while (0)
#else
#define LOGERR(level, ...) SIDE_EFFECT_CHECK(level, log_category_prefix << __VA_ARGS__)
#endif
+4
View File
@@ -340,6 +340,10 @@ int main(int argc, char* argv[])
p2pool::destroy_crypto_cache();
#ifdef WITH_INDEXED_HASHES
p2pool::indexed_hash::cleanup_storage();
#endif
p2pool::log::stop();
uv_loop_close(uv_default_loop());
+13 -2
View File
@@ -331,12 +331,23 @@ void MergeMiningClientTari::on_external_block(const PoolBlock& block)
std::vector<hash> proof;
uint32_t path;
if (!merkle_hash_with_proof(block.m_transactions, 0, proof, path, root)) {
#ifdef WITH_INDEXED_HASHES
std::vector<hash> transactions;
transactions.reserve(block.m_transactions.size());
for (const auto& h : block.m_transactions) {
transactions.emplace_back(h);
}
#else
const std::vector<hash>& transactions = block.m_transactions;
#endif
if (!merkle_hash_with_proof(transactions, 0, proof, path, root)) {
LOGWARN(3, "on_external_block: merkle_hash_with_proof failed for coinbase transaction");
return;
}
if (!verify_merkle_proof(block.m_transactions[0], proof, path, root)) {
if (!verify_merkle_proof(transactions[0], proof, path, root)) {
LOGWARN(3, "on_external_block: verify_merkle_proof failed for coinbase transaction");
return;
}
+1 -1
View File
@@ -842,7 +842,7 @@ P2PServer::Broadcast::Broadcast(const PoolBlock& block, const PoolBlock* parent)
// 0 outputs in the pruned blob
data->pruned_blob.push_back(0);
const uint64_t total_reward = std::accumulate(block.m_outputs.begin(), block.m_outputs.end(), 0ULL,
const uint64_t total_reward = std::accumulate(block.m_outputAmounts.begin(), block.m_outputAmounts.end(), 0ULL,
[](uint64_t a, const PoolBlock::TxOutput& b)
{
return a + b.m_reward;
+35 -10
View File
@@ -90,7 +90,8 @@ PoolBlock& PoolBlock::operator=(const PoolBlock& b)
m_prevId = b.m_prevId;
m_nonce = b.m_nonce;
m_txinGenHeight = b.m_txinGenHeight;
m_outputs = b.m_outputs;
m_ephPublicKeys = b.m_ephPublicKeys;
m_outputAmounts = b.m_outputAmounts;
m_txkeyPub = b.m_txkeyPub;
m_extraNonceSize = b.m_extraNonceSize;
m_extraNonce = b.m_extraNonce;
@@ -139,7 +140,7 @@ PoolBlock& PoolBlock::operator=(const PoolBlock& b)
std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, size_t* miner_tx_size, int* outputs_offset, int* outputs_blob_size, const uint32_t* nonce, const uint32_t* extra_nonce) const
{
std::vector<uint8_t> data;
data.reserve(128 + m_outputs.size() * 39 + m_transactions.size() * HASH_SIZE);
data.reserve(std::min<size_t>(128 + m_outputAmounts.size() * 39 + m_transactions.size() * HASH_SIZE, 131072));
// Header
data.push_back(m_majorVersion);
@@ -169,12 +170,15 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, si
*outputs_offset = outputs_offset0;
}
writeVarint(m_outputs.size(), data);
writeVarint(m_outputAmounts.size(), data);
for (size_t i = 0, n = m_outputAmounts.size(); i < n; ++i) {
const TxOutput& output = m_outputAmounts[i];
for (const TxOutput& output : m_outputs) {
writeVarint(output.m_reward, data);
data.push_back(TXOUT_TO_TAGGED_KEY);
data.insert(data.end(), output.m_ephPublicKey.h, output.m_ephPublicKey.h + HASH_SIZE);
const hash h = m_ephPublicKeys[i];
data.insert(data.end(), h.h, h.h + HASH_SIZE);
data.push_back(static_cast<uint8_t>(output.m_viewTag));
}
@@ -225,8 +229,16 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, si
}
writeVarint(m_transactions.size() - 1, data);
#ifdef WITH_INDEXED_HASHES
for (size_t i = 1, n = m_transactions.size(); i < n; ++i) {
const hash h = m_transactions[i];
data.insert(data.end(), h.h, h.h + HASH_SIZE);
}
#else
const uint8_t* t = reinterpret_cast<const uint8_t*>(m_transactions.data());
data.insert(data.end(), t + HASH_SIZE, t + m_transactions.size() * HASH_SIZE);
#endif
#if POOL_BLOCK_DEBUG
if ((nonce == &m_nonce) && (extra_nonce == &m_extraNonce) && !m_mainChainDataDebug.empty() && (data != m_mainChainDataDebug)) {
@@ -369,15 +381,27 @@ bool PoolBlock::get_pow_hash(RandomX_Hasher_Base* hasher, uint64_t height, const
memcpy(hashes, tmp.h, HASH_SIZE);
count = m_transactions.size();
uint8_t* h = reinterpret_cast<uint8_t*>(m_transactions.data());
keccak(reinterpret_cast<uint8_t*>(hashes), HASH_SIZE * 3, tmp.h);
// Save the coinbase tx hash into the first element of m_transactions
memcpy(h, tmp.h, HASH_SIZE);
m_transactions[0] = static_cast<indexed_hash>(tmp);
root_hash tmp_root;
#ifdef WITH_INDEXED_HASHES
std::vector<hash> transactions;
transactions.reserve(m_transactions.size());
for (const auto& h : m_transactions) {
transactions.emplace_back(h);
}
merkle_hash(transactions, tmp_root);
#else
merkle_hash(m_transactions, tmp_root);
#endif
memcpy(blob + blob_size, tmp_root.h, HASH_SIZE);
blob_size += HASH_SIZE;
}
@@ -392,13 +416,14 @@ bool PoolBlock::get_pow_hash(RandomX_Hasher_Base* hasher, uint64_t height, const
uint64_t PoolBlock::get_payout(const Wallet& w) const
{
for (size_t i = 0, n = m_outputs.size(); i < n; ++i) {
const TxOutput& out = m_outputs[i];
for (size_t i = 0, n = m_outputAmounts.size(); i < n; ++i) {
const TxOutput& out = m_outputAmounts[i];
hash eph_public_key;
uint8_t view_tag;
const uint8_t expected_view_tag = out.m_viewTag;
if (w.get_eph_public_key(m_txkeySec, i, eph_public_key, view_tag, &expected_view_tag) && (eph_public_key == out.m_ephPublicKey)) {
if (w.get_eph_public_key(m_txkeySec, i, eph_public_key, view_tag, &expected_view_tag) && (m_ephPublicKeys[i] == eph_public_key)) {
return out.m_reward;
}
}
+6 -6
View File
@@ -98,17 +98,17 @@ struct PoolBlock
struct TxOutput
{
FORCEINLINE TxOutput() : m_ephPublicKey(), m_reward(0), m_viewTag(0) {}
FORCEINLINE TxOutput(uint64_t r, const hash& k, uint8_t view_tag) : m_ephPublicKey(k), m_reward(r), m_viewTag(view_tag) {}
FORCEINLINE TxOutput() : m_reward(0), m_viewTag(0) {}
FORCEINLINE TxOutput(uint64_t r, uint8_t view_tag) : m_reward(r), m_viewTag(view_tag) {}
hash m_ephPublicKey;
uint64_t m_reward : 56;
uint64_t m_viewTag : 8;
};
static_assert(sizeof(TxOutput) == sizeof(hash) + sizeof(uint64_t), "TxOutput bit packing didn't work with this compiler, fix the code!");
static_assert(sizeof(TxOutput) == sizeof(uint64_t), "TxOutput bit packing didn't work with this compiler, fix the code!");
std::vector<TxOutput> m_outputs;
std::vector<indexed_hash> m_ephPublicKeys;
std::vector<TxOutput> m_outputAmounts;
hash m_txkeyPub;
uint64_t m_extraNonceSize;
@@ -119,7 +119,7 @@ struct PoolBlock
root_hash m_merkleRoot;
// All block transaction hashes including the miner transaction hash at index 0
std::vector<hash> m_transactions;
std::vector<indexed_hash> m_transactions;
// Miner's wallet
Wallet m_minerWallet{ nullptr };
+41 -17
View File
@@ -105,11 +105,14 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
if (num_outputs > std::numeric_limits<uint64_t>::max() / MIN_OUTPUT_SIZE) return __LINE__;
if (static_cast<uint64_t>(data_end - data) < num_outputs * MIN_OUTPUT_SIZE) return __LINE__;
m_outputs.resize(num_outputs);
m_outputs.shrink_to_fit();
m_ephPublicKeys.resize(num_outputs);
m_outputAmounts.resize(num_outputs);
m_ephPublicKeys.shrink_to_fit();
m_outputAmounts.shrink_to_fit();
for (uint64_t i = 0; i < num_outputs; ++i) {
TxOutput& t = m_outputs[i];
TxOutput& t = m_outputAmounts[i];
uint64_t reward;
READ_VARINT(reward);
@@ -124,7 +127,9 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
EXPECT_BYTE(TXOUT_TO_TAGGED_KEY);
READ_BUF(t.m_ephPublicKey.h, HASH_SIZE);
hash ephPublicKey;
READ_BUF(ephPublicKey.h, HASH_SIZE);
m_ephPublicKeys[i] = ephPublicKey;
uint8_t view_tag;
READ_BYTE(view_tag);
@@ -221,17 +226,18 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
const int transactions_offset = static_cast<int>(data - data_begin);
std::vector<uint64_t> parent_indices;
std::vector<hash> transactions;
if (compact) {
if (static_cast<uint64_t>(data_end - data) < num_transactions) return __LINE__;
m_transactions.resize(1);
parent_indices.resize(1);
// limit reserved memory size because we can't check "num_transactions" properly here
const uint64_t k = std::min<uint64_t>(num_transactions + 1, 256);
m_transactions.reserve(k);
transactions.reserve(k);
parent_indices.reserve(k);
transactions.resize(1);
parent_indices.resize(1);
for (uint64_t i = 0; i < num_transactions; ++i) {
uint64_t parent_index;
READ_VARINT(parent_index);
@@ -241,7 +247,7 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
READ_BUF(id.h, HASH_SIZE);
}
m_transactions.emplace_back(id);
transactions.emplace_back(id);
parent_indices.emplace_back(parent_index);
}
}
@@ -249,13 +255,13 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
if (num_transactions > std::numeric_limits<uint64_t>::max() / HASH_SIZE) return __LINE__;
if (static_cast<uint64_t>(data_end - data) < num_transactions * HASH_SIZE) return __LINE__;
m_transactions.resize(1);
m_transactions.reserve(num_transactions + 1);
transactions.reserve(num_transactions + 1);
transactions.resize(1);
for (uint64_t i = 0; i < num_transactions; ++i) {
hash id;
READ_BUF(id.h, HASH_SIZE);
m_transactions.emplace_back(id);
transactions.emplace_back(id);
}
}
@@ -263,8 +269,6 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
const int transactions_blob_size = static_cast<int>(num_transactions) * HASH_SIZE;
const int transactions_blob_size_diff = transactions_blob_size - transactions_actual_blob_size;
m_transactions.shrink_to_fit();
#if POOL_BLOCK_DEBUG
m_mainChainDataDebug.reserve((data - data_begin) + outputs_blob_size_diff + transactions_blob_size_diff);
m_mainChainDataDebug.assign(data_begin, data_begin + outputs_offset);
@@ -298,22 +302,42 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
READ_BUF(m_parent.h, HASH_SIZE);
m_transactions.clear();
m_transactions.reserve(transactions.size());
if (compact) {
const PoolBlock* parent = sidechain.find_block(m_parent);
if (!parent) {
return __LINE__;
}
for (uint64_t i = 1, n = m_transactions.size(); i < n; ++i) {
const uint64_t n = transactions.size();
if (n > 0) {
m_transactions.emplace_back(transactions[0]);
}
for (uint64_t i = 1; i < n; ++i) {
const uint64_t parent_index = parent_indices[i];
if (parent_index) {
if (parent_index >= parent->m_transactions.size()) {
return __LINE__;
}
m_transactions[i] = parent->m_transactions[parent_index];
transactions[i] = parent->m_transactions[parent_index];
m_transactions.emplace_back(parent->m_transactions[parent_index]);
}
else {
m_transactions.emplace_back(transactions[i]);
}
}
}
else {
for (const hash& h : transactions) {
m_transactions.emplace_back(h);
}
}
m_transactions.shrink_to_fit();
uint64_t num_uncles;
READ_VARINT(num_uncles);
@@ -416,7 +440,7 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
return __LINE__;
}
const uint8_t* transactions_blob = reinterpret_cast<uint8_t*>(m_transactions.data() + 1);
const uint8_t* transactions_blob = reinterpret_cast<uint8_t*>(transactions.data() + 1);
#if POOL_BLOCK_DEBUG
memcpy(m_mainChainDataDebug.data() + outputs_offset, outputs_blob.data(), outputs_blob_size);
+26 -18
View File
@@ -830,19 +830,22 @@ bool SideChain::get_outputs_blob(PoolBlock* block, uint64_t total_reward, std::v
auto it = block->m_sidechainId.empty() ? m_blocksById.end() : m_blocksById.find(block->m_sidechainId);
if (it != m_blocksById.end()) {
const PoolBlock* b = it->second;
const size_t n = b->m_outputs.size();
const size_t n = b->m_outputAmounts.size();
blob.reserve(n * 39 + 64);
writeVarint(n, blob);
for (const PoolBlock::TxOutput& output : b->m_outputs) {
for (size_t i = 0; i < n; ++i) {
const PoolBlock::TxOutput& output = b->m_outputAmounts[i];
writeVarint(output.m_reward, blob);
blob.emplace_back(TXOUT_TO_TAGGED_KEY);
blob.insert(blob.end(), output.m_ephPublicKey.h, output.m_ephPublicKey.h + HASH_SIZE);
const hash h = b->m_ephPublicKeys[i];
blob.insert(blob.end(), h.h, h.h + HASH_SIZE);
blob.emplace_back(static_cast<uint8_t>(output.m_viewTag));
}
block->m_outputs = b->m_outputs;
block->m_ephPublicKeys = b->m_ephPublicKeys;
block->m_outputAmounts = b->m_outputAmounts;
return true;
}
@@ -887,8 +890,11 @@ bool SideChain::get_outputs_blob(PoolBlock* block, uint64_t total_reward, std::v
writeVarint(n, blob);
block->m_outputs.clear();
block->m_outputs.reserve(n);
block->m_ephPublicKeys.clear();
block->m_outputAmounts.clear();
block->m_ephPublicKeys.reserve(n);
block->m_outputAmounts.reserve(n);
hash eph_public_key;
for (size_t i = 0; i < n; ++i) {
@@ -911,10 +917,12 @@ bool SideChain::get_outputs_blob(PoolBlock* block, uint64_t total_reward, std::v
blob.emplace_back(view_tag);
block->m_outputs.emplace_back(tmpRewards[i], eph_public_key, view_tag);
block->m_ephPublicKeys.emplace_back(eph_public_key);
block->m_outputAmounts.emplace_back(tmpRewards[i], view_tag);
}
block->m_outputs.shrink_to_fit();
block->m_ephPublicKeys.shrink_to_fit();
block->m_outputAmounts.shrink_to_fit();
return true;
}
@@ -1082,12 +1090,12 @@ double SideChain::get_reward_share(const Wallet& w) const
const PoolBlock* tip = m_chainTip;
if (tip) {
hash eph_public_key;
for (size_t i = 0, n = tip->m_outputs.size(); i < n; ++i) {
const PoolBlock::TxOutput& out = tip->m_outputs[i];
for (size_t i = 0, n = tip->m_outputAmounts.size(); i < n; ++i) {
const PoolBlock::TxOutput& out = tip->m_outputAmounts[i];
if (!reward) {
uint8_t view_tag;
const uint8_t expected_view_tag = out.m_viewTag;
if (w.get_eph_public_key(tip->m_txkeySec, i, eph_public_key, view_tag, &expected_view_tag) && (out.m_ephPublicKey == eph_public_key)) {
if (w.get_eph_public_key(tip->m_txkeySec, i, eph_public_key, view_tag, &expected_view_tag) && (tip->m_ephPublicKeys[i] == eph_public_key)) {
reward = out.m_reward;
}
}
@@ -1699,16 +1707,16 @@ void SideChain::verify(PoolBlock* block)
return;
}
if (shares.size() != block->m_outputs.size()) {
if (shares.size() != block->m_outputAmounts.size()) {
LOGWARN(3, "block at height = " << block->m_sidechainHeight <<
", id = " << block->m_sidechainId <<
", mainchain height = " << block->m_txinGenHeight
<< " has invalid number of outputs: got " << block->m_outputs.size() << ", expected " << shares.size());
<< " has invalid number of outputs: got " << block->m_outputAmounts.size() << ", expected " << shares.size());
block->m_invalid = true;
return;
}
uint64_t total_reward = std::accumulate(block->m_outputs.begin(), block->m_outputs.end(), 0ULL,
uint64_t total_reward = std::accumulate(block->m_outputAmounts.begin(), block->m_outputAmounts.end(), 0ULL,
[](uint64_t a, const PoolBlock::TxOutput& b)
{
return a + b.m_reward;
@@ -1723,17 +1731,17 @@ void SideChain::verify(PoolBlock* block)
return;
}
if (rewards.size() != block->m_outputs.size()) {
if (rewards.size() != block->m_outputAmounts.size()) {
LOGWARN(3, "block at height = " << block->m_sidechainHeight <<
", id = " << block->m_sidechainId <<
", mainchain height = " << block->m_txinGenHeight
<< " has invalid number of outputs: got " << block->m_outputs.size() << ", expected " << rewards.size());
<< " has invalid number of outputs: got " << block->m_outputAmounts.size() << ", expected " << rewards.size());
block->m_invalid = true;
return;
}
for (size_t i = 0, n = rewards.size(); i < n; ++i) {
const PoolBlock::TxOutput& out = block->m_outputs[i];
const PoolBlock::TxOutput& out = block->m_outputAmounts[i];
if (rewards[i] != out.m_reward) {
LOGWARN(3, "block at height = " << block->m_sidechainHeight <<
@@ -1764,7 +1772,7 @@ void SideChain::verify(PoolBlock* block)
return;
}
if (eph_public_key != out.m_ephPublicKey) {
if (eph_public_key != block->m_ephPublicKeys[i]) {
LOGWARN(3, "block at height = " << block->m_sidechainHeight <<
", id = " << block->m_sidechainId <<
", mainchain height = " << block->m_txinGenHeight <<
+7 -1
View File
@@ -6,7 +6,8 @@ include(cmake/standard.cmake)
option(STATIC_LIBS "Use locally built libuv and libzmq static libs" OFF)
option(WITH_LTO "Use link-time compiler optimization (if linking fails for you, run cmake with -DWITH_LTO=OFF)" ON)
option(WITH_COVERAGE "Generate code coverage data" OFF)
option(DEV_DEBUG "[Developer only] Compile a debug build" OFF)
option(WITH_INDEXED_HASHES "Save memory used for storing transaction hashes and public keys (a bit slower block verification, use it only if you compile for a very low memory system)" ON)
option(DEV_DEBUG "[Developer only] Compile a debug build" ON)
if (DEV_DEBUG)
add_definitions(-DDEV_DEBUG)
@@ -84,6 +85,11 @@ set(SOURCES
../src/wallet.cpp
)
if (WITH_INDEXED_HASHES)
add_compile_definitions(WITH_INDEXED_HASHES)
set(SOURCES ${SOURCES} ../src/indexed_hash.cpp)
endif()
if (AMD64)
set(SOURCES ${SOURCES} ../src/keccak_bmi.cpp)
if (CMAKE_C_COMPILER_ID MATCHES GNU OR CMAKE_C_COMPILER_ID MATCHES Clang)
+25 -10
View File
@@ -36,7 +36,7 @@ static hash H(const char* s)
TEST(block_template, update)
{
init_crypto_cache();
{
SideChain sidechain(nullptr, NetworkType::Mainnet);
BlockTemplate tpl(&sidechain, nullptr);
tpl.rng().seed(123);
@@ -83,8 +83,11 @@ TEST(block_template, update)
// Test 2: mempool with high fee and low fee transactions, it must choose high fee transactions
for (uint64_t i = 0; i < 513; ++i) {
hash h;
h.u64()[0] = i;
TxMempoolData tx;
*reinterpret_cast<uint64_t*>(tx.id.h) = i;
tx.id = static_cast<indexed_hash>(h);
tx.fee = (i < 256) ? 30000000 : 60000000;
tx.weight = 1500;
mempool.add(tx);
@@ -115,7 +118,7 @@ TEST(block_template, update)
ASSERT_EQ(b->m_transactions.size(), 203);
for (size_t i = 1; i < b->m_transactions.size(); ++i) {
ASSERT_GE(*reinterpret_cast<const uint64_t*>(b->m_transactions[i].h), 256);
ASSERT_GE(static_cast<hash>(b->m_transactions[i]).u64()[0], 256);
}
tpl.get_hashing_blobs(0, 1000, blobs, height, diff, aux_diff, sidechain_diff, seed_hash, nonce_offset, template_id);
@@ -135,8 +138,11 @@ TEST(block_template, update)
std::vector<TxMempoolData> transactions;
for (uint64_t i = 0; i < 10; ++i) {
hash h;
h.u64()[0] = i;
TxMempoolData tx;
*reinterpret_cast<uint64_t*>(tx.id.h) = i;
tx.id = static_cast<indexed_hash>(h);
tx.fee = 30000000;
tx.weight = 1500;
transactions.push_back(tx);
@@ -171,10 +177,11 @@ TEST(block_template, update)
std::mt19937_64 rng;
for (uint64_t i = 0; i < 10000; ++i) {
hash h;
h.u64()[0] = i;
TxMempoolData tx;
*reinterpret_cast<uint64_t*>(tx.id.h) = i;
tx.id = static_cast<indexed_hash>(h);
tx.weight = 1500 + (rng() % 10007);
tx.fee = 30000000 + (rng() % 100000007);
@@ -199,14 +206,18 @@ TEST(block_template, update)
keccak(blobs.data(), static_cast<int>(blobs.size()), blobs_hash.h);
ASSERT_EQ(blobs_hash, H("4f62562aa84400eb085f58447d8daa45257369f1ec046b2150212329c9e86ae4"));
}
destroy_crypto_cache();
#ifdef WITH_INDEXED_HASHES
indexed_hash::cleanup_storage();
#endif
}
TEST(block_template, submit_sidechain_block)
{
init_crypto_cache();
{
SideChain sidechain(nullptr, NetworkType::Mainnet, "unit_test");
ASSERT_EQ(sidechain.consensus_hash(), H("81d45b62c10afa4fdda7cebb02dd5ad82c43b577eb3fb0857824427c55fd8a8d"));
@@ -270,8 +281,12 @@ TEST(block_template, submit_sidechain_block)
ASSERT_EQ(tip->m_sidechainHeight, sidechain.chain_window_size() * 3 - 1);
ASSERT_EQ(tip->m_sidechainId, H("12d57571a28d62d2b6dca3a647500d23ac22864138b22a133f237b459a0862da"));
}
destroy_crypto_cache();
#ifdef WITH_INDEXED_HASHES
indexed_hash::cleanup_storage();
#endif
}
}
+6 -2
View File
@@ -26,7 +26,7 @@ namespace p2pool {
TEST(crypto, derivation)
{
init_crypto_cache();
{
hash pub, sec;
generate_keys(pub, sec);
ASSERT_TRUE(check_keys(pub, sec));
@@ -105,9 +105,13 @@ TEST(crypto, derivation)
}
} while (!f.eof());
}
}
clear_crypto_cache(0);
destroy_crypto_cache();
#ifdef WITH_INDEXED_HASHES
indexed_hash::cleanup_storage();
#endif
}
}
+19 -7
View File
@@ -39,7 +39,7 @@ static hash H(const char* s)
TEST(pool_block, deserialize)
{
init_crypto_cache();
{
PoolBlock b;
SideChain sidechain(nullptr, NetworkType::Mainnet, "default");
@@ -93,7 +93,8 @@ TEST(pool_block, deserialize)
ASSERT_EQ(b.m_timestamp, 1728813765U);
ASSERT_EQ(b.m_nonce, 352454720U);
ASSERT_EQ(b.m_txinGenHeight, 3258099U);
ASSERT_EQ(b.m_outputs.size(), 27U);
ASSERT_EQ(b.m_ephPublicKeys.size(), 27U);
ASSERT_EQ(b.m_outputAmounts.size(), 27U);
ASSERT_EQ(b.m_extraNonceSize, 4U);
ASSERT_EQ(b.m_extraNonce, 2983923783U);
ASSERT_EQ(b.m_transactions.size(), 21U);
@@ -128,14 +129,18 @@ TEST(pool_block, deserialize)
ASSERT_EQ(b.serialize_mainchain_data(), mainchain_data);
ASSERT_EQ(b.serialize_sidechain_data(), sidechain_data);
}
destroy_crypto_cache();
#ifdef WITH_INDEXED_HASHES
indexed_hash::cleanup_storage();
#endif
}
TEST(pool_block, verify)
{
init_crypto_cache();
{
struct STest
{
const char* m_poolName;
@@ -151,7 +156,7 @@ TEST(pool_block, verify)
{ "mini", "sidechain_dump_mini.dat", 3456189, 11207082, 578, false, H("08debd1378bae899017eb58362f4c638d78e5218558025142dcbc2651c76b27e") },
{ "mini", "sidechain_dump_mini.dat", 3456189, 11207082, 578, true, H("08debd1378bae899017eb58362f4c638d78e5218558025142dcbc2651c76b27e") },
{ "nano", "sidechain_dump_nano.dat", 3456189, 188542, 115, false, H("dd667c41eb15ffb0eb662065545dc0dfbbcac8393348a4fc0a7367040319b0d5") },
{ "nano", "sidechain_dump_nano.dat", 3456189, 188542, 115, true, H("dd667c41eb15ffb0eb662065545dc0dfbbcac8393348a4fc0a7367040319b0d5") },
{ "nano", "sidechain_dump_nano.dat", 3456189, 188542, 115, true, H("dd667c41eb15ffb0eb662065545dc0dfbbcac8393348a4fc0a7367040319b0d5") },
};
for (const STest& t : tests)
@@ -231,8 +236,11 @@ TEST(pool_block, verify)
Mempool mempool;
for (uint64_t i = 0; i < 8192; ++i) {
hash h;
h.u64()[0] = i;
TxMempoolData tx;
*reinterpret_cast<uint64_t*>(tx.id.h) = i;
tx.id = static_cast<indexed_hash>(h);
tx.fee = (r() % 1'000'000'000) + 30'000'000;
tx.weight = (r() % 20'000) + 1'500;
mempool.add(tx);
@@ -302,8 +310,12 @@ TEST(pool_block, verify)
ASSERT_EQ(v1, tip_full_blob);
}
}
}
destroy_crypto_cache();
#ifdef WITH_INDEXED_HASHES
indexed_hash::cleanup_storage();
#endif
}
}