diff --git a/.gitignore b/.gitignore index 8d4cfa2e..1d4af758 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,8 @@ CMakeUserPresets.json build-asan/ test_runner/ doc/release-notes/*.backup +debug.log +error.log +*.mdb +bchn/ +benchmark_results.txt diff --git a/conan.lock b/conan.lock index 88442103..00a9362b 100644 --- a/conan.lock +++ b/conan.lock @@ -1,7 +1,7 @@ { "version": "0.5", "requires": [ - "zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1765381907.696", + "zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1765284699.337", "tiny-aes-c/1.0.0#0f0a930c53a0d33d5a91a4c930f836ab%1750950589.092", "spdlog/1.16.0#942c2c39562ae25ba575d9c8e2bdf3b6%1760375663.769", "simdutf/7.1.0#31a29f1641af1eec7fc18d86620477ad%1762802019.944", @@ -20,7 +20,7 @@ "build_requires": [ "secp256k1-precompute/1.0.0#53841449a185e25ee2337aa5e4f3583f%1751564430.278", "m4/1.4.19#b38ced39a01e31fef5435bc634461fd2%1700758725.451", - "cmake/4.2.1#951586eff155256a7b5298631d6b1416%1765387599.255", + "cmake/4.2.0#ae0a44f44a1ef9ab68fd4b3e9a1f8671%1764093466.37", "cmake/3.31.10#313d16a1aa16bbdb2ca0792467214b76%1763665505.054", "b2/5.3.3#107c15377719889654eb9a162a673975%1750340310.079" ], diff --git a/src/blockchain/include/kth/blockchain/interface/block_chain.hpp b/src/blockchain/include/kth/blockchain/interface/block_chain.hpp index 92c0e424..1271a2a7 100644 --- a/src/blockchain/include/kth/blockchain/interface/block_chain.hpp +++ b/src/blockchain/include/kth/blockchain/interface/block_chain.hpp @@ -119,8 +119,7 @@ struct KB_API block_chain : safe_chain, fast_chain, noncopyable { /// Insert a block to the blockchain, height is checked for existence. /// Reads and reorgs are undefined when chain is gapped. - bool insert(block_const_ptr block, size_t height) override; - // bool insert(block_const_ptr block, size_t height, int) override; + bool insert(block_const_ptr block, size_t height, uint32_t median_time_past) override; /// Push an unconfirmed transaction to the tx table and index outputs. void push(transaction_const_ptr tx, dispatcher& dispatch, result_handler handler) override; diff --git a/src/blockchain/include/kth/blockchain/interface/fast_chain.hpp b/src/blockchain/include/kth/blockchain/interface/fast_chain.hpp index 31cd30b4..c53a94c0 100644 --- a/src/blockchain/include/kth/blockchain/interface/fast_chain.hpp +++ b/src/blockchain/include/kth/blockchain/interface/fast_chain.hpp @@ -83,8 +83,7 @@ struct KB_API fast_chain { #if ! defined(KTH_DB_READONLY) /// Insert a block to the blockchain, height is checked for existence. - // virtual bool insert(block_const_ptr block, size_t height, int) = 0; - virtual bool insert(block_const_ptr block, size_t height) = 0; + virtual bool insert(block_const_ptr block, size_t height, uint32_t median_time_past) = 0; /// Push an unconfirmed transaction to the tx table and index outputs. virtual void push(transaction_const_ptr tx, dispatcher& dispatch, diff --git a/src/blockchain/src/interface/block_chain.cpp b/src/blockchain/src/interface/block_chain.cpp index a81e682e..8efadcfe 100644 --- a/src/blockchain/src/interface/block_chain.cpp +++ b/src/blockchain/src/interface/block_chain.cpp @@ -167,7 +167,7 @@ bool block_chain::get_block_exists_safe(hash_digest const& block_hash) const { bool block_chain::get_block_hash(hash_digest& out_hash, size_t height) const { auto const result = database_.internal_db().get_header(height); if ( ! result.is_valid()) return false; - out_hash = result.hash(); + out_hash = domain::chain::hash(result); return true; } @@ -259,9 +259,8 @@ std::pair block_chain::get_utxo_ // ---------------------------------------------------------------------------- #if ! defined(KTH_DB_READONLY) -// bool block_chain::insert(block_const_ptr block, size_t height, int) { -bool block_chain::insert(block_const_ptr block, size_t height) { - return database_.insert(*block, height) == error::success; +bool block_chain::insert(block_const_ptr block, size_t height, uint32_t median_time_past) { + return database_.insert(*block, height, median_time_past) == error::success; } void block_chain::push(transaction_const_ptr tx, dispatcher&, result_handler handler) { @@ -640,7 +639,7 @@ void block_chain::fetch_locator_block_hashes(get_blocks_const_ptr locator, } static auto const id = inventory::type_id::block; - hashes->inventories().emplace_back(id, result.header().hash()); + hashes->inventories().emplace_back(id, hash(result.header())); } handler(error::success, std::move(hashes)); @@ -1241,7 +1240,7 @@ void block_chain::fetch_block_hash_timestamp(size_t height, block_hash_time_fetc return; } - handler(error::success, result.hash(), result.timestamp(), height); + handler(error::success, hash(result), result.timestamp(), height); } @@ -1406,7 +1405,7 @@ void block_chain::fetch_block_locator(block::indexes const& heights, block_locat handler(error::not_found, nullptr); break; } - hashes.push_back(result.hash()); + hashes.push_back(hash(result)); } handler(error::success, message); diff --git a/src/blockchain/src/pools/branch.cpp b/src/blockchain/src/pools/branch.cpp index a40490a4..899104cb 100644 --- a/src/blockchain/src/pools/branch.cpp +++ b/src/blockchain/src/pools/branch.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -122,9 +123,27 @@ size_t branch::height_at(size_t index) const { } // private +// TODO: measure the cost of computing MTP on-the-fly vs storing it in the block. +// Previously: return (*blocks_)[index]->header().validation.median_time_past; uint32_t branch::median_time_past_at(size_t index) const { KTH_ASSERT(index < size()); - return (*blocks_)[index]->header().validation.median_time_past; + // return (*blocks_)[index]->header().validation.median_time_past; + + // Collect timestamps from blocks before index (not including index itself) + std::array timestamps{}; + size_t count = 0; + + // Go backwards from index-1, collecting up to 11 timestamps + for (size_t i = 0; i < domain::chain::median_time_past_blocks && i < index; ++i) { + timestamps[count++] = (*blocks_)[index - 1 - i]->header().timestamp(); + } + + if (count == 0) { + return 0; + } + + std::sort(timestamps.begin(), timestamps.begin() + count); + return timestamps[count / 2]; } // TODO(legacy): absorb into the main chain for speed and code consolidation. diff --git a/src/database/include/kth/database/data_base.hpp b/src/database/include/kth/database/data_base.hpp index fa5952ae..c02b83b0 100644 --- a/src/database/include/kth/database/data_base.hpp +++ b/src/database/include/kth/database/data_base.hpp @@ -65,7 +65,7 @@ struct KD_API data_base : store, noncopyable { /// Store a block in the database. /// Returns store_block_duplicate if a block already exists at height. - code insert(domain::chain::block const& block, size_t height); + code insert(domain::chain::block const& block, size_t height, uint32_t median_time_past); /// Add an unconfirmed tx to the store (without indexing). /// Returns unspent_duplicate if existing unspent hash duplicate exists. @@ -73,7 +73,7 @@ struct KD_API data_base : store, noncopyable { /// Returns store_block_missing_parent if not linked. /// Returns store_block_invalid_height if height is not the current top + 1. - code push(domain::chain::block const& block, size_t height); + code push(domain::chain::block const& block, size_t height, uint32_t median_time_past); code prune_reorg(); @@ -83,7 +83,7 @@ struct KD_API data_base : store, noncopyable { // ------------------------------------------------------------------------ /// Invoke pop_all and then push_all under a common lock. - void reorganize(infrastructure::config::checkpoint const& fork_point, block_const_ptr_list_const_ptr incoming_blocks, block_const_ptr_list_ptr outgoing_blocks, dispatcher& dispatch, result_handler handler); + void reorganize(infrastructure::config::checkpoint const& fork_point, block_const_ptr_list_const_ptr incoming_blocks, std::vector const& median_time_pasts, block_const_ptr_list_ptr outgoing_blocks, dispatcher& dispatch, result_handler handler); #endif // ! defined(KTH_DB_READONLY) protected: @@ -92,7 +92,7 @@ struct KD_API data_base : store, noncopyable { #if ! defined(KTH_DB_READONLY) // Sets error if first_height is not the current top + 1 or not linked. - void push_all(block_const_ptr_list_const_ptr in_blocks, size_t first_height, dispatcher& dispatch, result_handler handler); + void push_all(block_const_ptr_list_const_ptr in_blocks, std::vector const& median_time_pasts, size_t first_height, dispatcher& dispatch, result_handler handler); // Pop the set of blocks above the given hash. // Sets error if the database is corrupt or the hash doesn't exist. @@ -124,11 +124,11 @@ struct KD_API data_base : store, noncopyable { // Asynchronous writers. // ------------------------------------------------------------------------ #if ! defined(KTH_DB_READONLY) - void push_next(code const& ec, block_const_ptr_list_const_ptr blocks, size_t index, size_t height, dispatcher& dispatch, result_handler handler); + void push_next(code const& ec, block_const_ptr_list_const_ptr blocks, std::vector const& median_time_pasts, size_t index, size_t height, dispatcher& dispatch, result_handler handler); void do_push(block_const_ptr block, size_t height, uint32_t median_time_past, dispatcher& dispatch, result_handler handler); - void handle_pop(code const& ec, block_const_ptr_list_const_ptr incoming_blocks, size_t first_height, dispatcher& dispatch, result_handler handler); + void handle_pop(code const& ec, block_const_ptr_list_const_ptr incoming_blocks, std::vector const& median_time_pasts, size_t first_height, dispatcher& dispatch, result_handler handler); void handle_push(code const& ec, result_handler handler) const; #endif // ! defined(KTH_DB_READONLY) diff --git a/src/database/include/kth/database/databases/internal_database.ipp b/src/database/include/kth/database/databases/internal_database.ipp index 09082b05..fc5012c7 100644 --- a/src/database/include/kth/database/databases/internal_database.ipp +++ b/src/database/include/kth/database/databases/internal_database.ipp @@ -1002,7 +1002,7 @@ result_code internal_database_basis::push_genesis(domain::chain::block co auto const& txs = block.transactions(); auto const& coinbase = txs.front(); auto const& hash = coinbase.hash(); - auto const median_time_past = block.header().validation.median_time_past; + constexpr uint32_t median_time_past = 0u; // Genesis block has no previous blocks, so MTP is 0. res = insert_transaction(tx_count, coinbase, 0, median_time_past, 0, db_txn); if (res != result_code::success && res != result_code::duplicated_key) { diff --git a/src/database/src/data_base.cpp b/src/database/src/data_base.cpp index 18d0021e..35478299 100644 --- a/src/database/src/data_base.cpp +++ b/src/database/src/data_base.cpp @@ -132,7 +132,7 @@ uint32_t get_next_height(internal_database const& db) { static inline hash_digest get_previous_hash(internal_database const& db, size_t height) { - return height == 0 ? null_hash : db.get_header(height - 1).hash(); + return height == 0 ? null_hash : domain::chain::hash(db.get_header(height - 1)); } //TODO(fernando): const? @@ -175,10 +175,7 @@ code data_base::verify_push(block const& block, size_t height) const { // Add block to the database at the given height (gaps allowed/created). // This is designed for write concurrency but only with itself. -code data_base::insert(domain::chain::block const& block, size_t height) { - - auto const median_time_past = block.header().validation.median_time_past; - +code data_base::insert(domain::chain::block const& block, size_t height, uint32_t median_time_past) { auto const ec = verify_insert(block, height); if (ec) return ec; @@ -210,8 +207,7 @@ code data_base::push(domain::chain::transaction const& tx, uint32_t forks) { #if ! defined(KTH_DB_READONLY) // Add a block in order (creates no gaps, must be at top). // This is designed for write exclusivity and read concurrency. -code data_base::push(block const& block, size_t height) { - auto const median_time_past = block.header().validation.median_time_past; +code data_base::push(block const& block, size_t height, uint32_t median_time_past) { auto res = internal_db_->push_block(block, height, median_time_past); if ( ! succeed(res)) { return error::database_push_failed; //TODO(fernando): create a new operation_failed @@ -291,15 +287,14 @@ bool data_base::pop_outputs(const output::list& outputs, size_t height) { // Add a list of blocks in order. // If the dispatch threadpool is shut down when this is running the handler // will never be invoked, resulting in a threadpool.join indefinite hang. -void data_base::push_all(block_const_ptr_list_const_ptr in_blocks, size_t first_height, dispatcher& dispatch, result_handler handler) { +void data_base::push_all(block_const_ptr_list_const_ptr in_blocks, std::vector const& median_time_pasts, size_t first_height, dispatcher& dispatch, result_handler handler) { DEBUG_ONLY(*safe_add(in_blocks->size(), first_height)); // This is the beginning of the push_all sequence. - push_next(error::success, in_blocks, 0, first_height, dispatch, handler); + push_next(error::success, in_blocks, median_time_pasts, 0, first_height, dispatch, handler); } -// TODO(legacy): resolve inconsistency with height and median_time_past passing. -void data_base::push_next(code const& ec, block_const_ptr_list_const_ptr blocks, size_t index, size_t height, dispatcher& dispatch, result_handler handler) { +void data_base::push_next(code const& ec, block_const_ptr_list_const_ptr blocks, std::vector const& median_time_pasts, size_t index, size_t height, dispatcher& dispatch, result_handler handler) { if (ec || index >= blocks->size()) { // This ends the loop. handler(ec); @@ -307,12 +302,12 @@ void data_base::push_next(code const& ec, block_const_ptr_list_const_ptr blocks, } auto const block = (*blocks)[index]; - auto const median_time_past = block->header().validation.median_time_past; + auto const median_time_past = median_time_pasts[index]; // Set push start time for the block. block->validation.start_push = asio::steady_clock::now(); - result_handler const next = std::bind(&data_base::push_next, this, _1, blocks, index + 1, height + 1, std::ref(dispatch), handler); + result_handler const next = std::bind(&data_base::push_next, this, _1, blocks, std::cref(median_time_pasts), index + 1, height + 1, std::ref(dispatch), handler); // This is the beginning of the block sub-sequence. dispatch.concurrent(&data_base::do_push, this, block, height, median_time_past, std::ref(dispatch), next); @@ -389,15 +384,15 @@ code data_base::prune_reorg() { #if ! defined(KTH_DB_READONLY) // This is designed for write exclusivity and read concurrency. -void data_base::reorganize(infrastructure::config::checkpoint const& fork_point, block_const_ptr_list_const_ptr incoming_blocks, block_const_ptr_list_ptr outgoing_blocks, dispatcher& dispatch, result_handler handler) { +void data_base::reorganize(infrastructure::config::checkpoint const& fork_point, block_const_ptr_list_const_ptr incoming_blocks, std::vector const& median_time_pasts, block_const_ptr_list_ptr outgoing_blocks, dispatcher& dispatch, result_handler handler) { auto const next_height = *safe_add(fork_point.height(), size_t(1)); // TODO: remove std::bind, use lambda instead. // TOOD: Even better use C++20 coroutines. - result_handler const pop_handler = std::bind(&data_base::handle_pop, this, _1, incoming_blocks, next_height, std::ref(dispatch), handler); + result_handler const pop_handler = std::bind(&data_base::handle_pop, this, _1, incoming_blocks, std::cref(median_time_pasts), next_height, std::ref(dispatch), handler); pop_above(outgoing_blocks, fork_point.hash(), dispatch, pop_handler); } -void data_base::handle_pop(code const& ec, block_const_ptr_list_const_ptr incoming_blocks, size_t first_height, dispatcher& dispatch, result_handler handler) { +void data_base::handle_pop(code const& ec, block_const_ptr_list_const_ptr incoming_blocks, std::vector const& median_time_pasts, size_t first_height, dispatcher& dispatch, result_handler handler) { result_handler const push_handler = std::bind(&data_base::handle_push, this, _1, handler); if (ec) { @@ -405,7 +400,7 @@ void data_base::handle_pop(code const& ec, block_const_ptr_list_const_ptr incomi return; } - push_all(incoming_blocks, first_height, std::ref(dispatch), push_handler); + push_all(incoming_blocks, median_time_pasts, first_height, std::ref(dispatch), push_handler); } // We never invoke the caller's handler under the mutex, we never fail to clear diff --git a/src/database/test/internal_database.cpp b/src/database/test/internal_database.cpp index 679e6f14..36529a17 100644 --- a/src/database/test/internal_database.cpp +++ b/src/database/test/internal_database.cpp @@ -652,13 +652,13 @@ TEST_CASE("internal database insert genesis", "[None]") { REQUIRE(db.push_block(genesis, 0, 1) == result_code::success); REQUIRE(db.get_header(genesis.hash()).first.is_valid()); - REQUIRE(db.get_header(genesis.hash()).first.hash() == genesis.hash()); + REQUIRE(chain::hash(db.get_header(genesis.hash()).first) == genesis.hash()); REQUIRE(db.get_header(genesis.hash()).second == 0); REQUIRE(db.get_header(0).is_valid()); - REQUIRE(db.get_header(0).hash() == genesis.hash()); + REQUIRE(chain::hash(db.get_header(0)) == genesis.hash()); - REQUIRE(db.get_block(0).header().hash() == genesis.hash()); + REQUIRE(chain::hash(db.get_block(0).header()) == genesis.hash()); hash_digest txid; std::string txid_enc = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"; diff --git a/src/domain/CMakeLists.txt b/src/domain/CMakeLists.txt index dfc4a8d1..b33bd7d9 100644 --- a/src/domain/CMakeLists.txt +++ b/src/domain/CMakeLists.txt @@ -162,8 +162,8 @@ set(kth_sources_just_legacy src/chain/block.cpp src/chain/chain_state.cpp src/chain/compact.cpp - src/chain/header_basis.cpp - src/chain/header.cpp + src/chain/header_raw.cpp + src/chain/header_members.cpp src/chain/input_basis.cpp src/chain/input.cpp src/chain/output_basis.cpp @@ -339,7 +339,7 @@ set(kth_headers include/kth/domain/concepts.hpp include/kth/domain/chain/points_value.hpp include/kth/domain/chain/chain_state.hpp - include/kth/domain/chain/header_basis.hpp + include/kth/domain/chain/header_raw.hpp include/kth/domain/chain/block_basis.hpp include/kth/domain/chain/input_point.hpp include/kth/domain/chain/input_basis.hpp @@ -349,12 +349,13 @@ set(kth_headers include/kth/domain/chain/token_data.hpp include/kth/domain/chain/token_data_serialization.hpp include/kth/domain/chain/output_point.hpp - include/kth/domain/chain/hash_memoizer.hpp + # include/kth/domain/chain/hash_memoizer.hpp # No longer used include/kth/domain/chain/script_basis.hpp include/kth/domain/chain/transaction_basis.hpp include/kth/domain/chain/stealth.hpp include/kth/domain/chain/point_iterator.hpp include/kth/domain/chain/header.hpp + include/kth/domain/chain/header_members.hpp include/kth/domain/chain/history.hpp include/kth/domain/chain/compact.hpp include/kth/domain/chain/input.hpp @@ -618,6 +619,30 @@ if (ENABLE_TEST AND NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten") endif() endif() +# Benchmarks +# ------------------------------------------------------------------------------ +if (ENABLE_TEST AND NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + find_package(nanobench REQUIRED) + + # Benchmark with header_raw implementation (default, array-based) + add_executable(kth_domain_benchmarks_header_raw + test/chain/header_benchmarks.cpp + ) + target_include_directories(kth_domain_benchmarks_header_raw PUBLIC $) + target_link_libraries(kth_domain_benchmarks_header_raw PRIVATE ${PROJECT_NAME}) + target_link_libraries(kth_domain_benchmarks_header_raw PRIVATE nanobench::nanobench) + # No define needed - header_raw is the default + + # Benchmark with header_members implementation (member-based) + add_executable(kth_domain_benchmarks_header_members + test/chain/header_benchmarks.cpp + ) + target_include_directories(kth_domain_benchmarks_header_members PUBLIC $) + target_link_libraries(kth_domain_benchmarks_header_members PRIVATE ${PROJECT_NAME}) + target_link_libraries(kth_domain_benchmarks_header_members PRIVATE nanobench::nanobench) + target_compile_definitions(kth_domain_benchmarks_header_members PRIVATE KTH_USE_HEADER_MEMBERS) +endif() + # Examples # ------------------------------------------------------------------------------ if (WITH_EXAMPLES) diff --git a/src/domain/include/kth/domain/chain/hash_memoizer.hpp b/src/domain/include/kth/domain/chain/hash_memoizer.hpp.disabled similarity index 100% rename from src/domain/include/kth/domain/chain/hash_memoizer.hpp rename to src/domain/include/kth/domain/chain/hash_memoizer.hpp.disabled diff --git a/src/domain/include/kth/domain/chain/header.hpp b/src/domain/include/kth/domain/chain/header.hpp index a38256aa..6f20434d 100644 --- a/src/domain/include/kth/domain/chain/header.hpp +++ b/src/domain/include/kth/domain/chain/header.hpp @@ -5,125 +5,124 @@ #ifndef KTH_DOMAIN_CHAIN_HEADER_HPP #define KTH_DOMAIN_CHAIN_HEADER_HPP -#include +#include +#include +#include #include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#include -namespace kth::domain::chain { -struct KD_API header : header_basis, hash_memoizer
{ // NOLINT(cppcoreguidelines-special-member-functions) -public: - using list = std::vector
; - using ptr = std::shared_ptr
; - using const_ptr = std::shared_ptr
; - using ptr_list = std::vector
; - using const_ptr_list = std::vector; - - // THIS IS FOR LIBRARY USE ONLY, DO NOT CREATE A DEPENDENCY ON IT. - struct validation_t { - size_t height = 0; - uint32_t median_time_past = 0; - }; - - // Constructors. - //----------------------------------------------------------------------------- - using header_basis::header_basis; // inherit constructors from header_basis - - header() = default; +#include - explicit - header(header_basis const& basis) - : header_basis(basis) - {} - - /// This class is copy constructible and copy assignable. - // Note(kth): Cannot be defaulted because the std::mutex data member. - header(header const& x); - header& operator=(header const& x); +#include +// Header implementation selection. +// Define KTH_USE_HEADER_MEMBERS to use member-based implementation. +// Default is raw byte array implementation. - // Deserialization. - //----------------------------------------------------------------------------- +#if defined(KTH_USE_HEADER_MEMBERS) + #include +#else + #include +#endif +namespace kth::domain::chain { - static - expect
from_data(byte_reader& reader, bool wire = true); +// ============================================================================= +// Header Concepts +// ============================================================================= - // Serialization. - //----------------------------------------------------------------------------- +/// Concept for types that have a timestamp() member function. +template +concept Timestamped = requires(T const& t) { + { t.timestamp() } -> std::convertible_to; +}; - data_chunk to_data(bool wire = true) const; - // void to_data(data_sink& stream, bool wire=true) const; - void to_data(data_sink& stream, bool wire = true) const; +/// Concept for types that have all standard header field accessors. +template +concept HeaderLike = requires(T const& t) { + { t.version() } -> std::convertible_to; + { t.previous_block_hash() } -> std::convertible_to; + { t.merkle() } -> std::convertible_to; + { t.timestamp() } -> std::convertible_to; + { t.bits() } -> std::convertible_to; + { t.nonce() } -> std::convertible_to; +}; - template - void to_data(W& sink, bool wire = true) const { - header_basis::to_data(sink, wire); +/// Concept for types that can be hashed via hash() free function. +template +concept Hashable = requires(T const& t) { + { hash(t) } -> std::convertible_to; +}; - if ( ! wire) { - sink.write_4_bytes_little_endian(validation.median_time_past); - } +/// Concept for a range of Timestamped elements (for MTP calculation). +template +concept TimestampedRange = std::ranges::range && + Timestamped>; + +/// Concept for a range of HeaderLike elements. +template +concept HeaderRange = std::ranges::range && + HeaderLike>; + +// ============================================================================= +// Median Time Past (MTP) Calculation +// ============================================================================= + +/// Number of blocks used for MTP calculation (Bitcoin consensus rule). +inline constexpr size_t median_time_past_blocks = 11; + +/// Calculate median time past from a range of timestamped elements. +/// The range should contain the previous blocks in reverse chronological order +/// (most recent first), up to median_time_past_blocks elements. +/// Returns the median timestamp of the provided elements. +template +[[nodiscard]] constexpr +uint32_t median_time_past(R&& range) { + std::array timestamps{}; + size_t count = 0; + + for (auto const& elem : range) { + if (count >= median_time_past_blocks) break; + timestamps[count++] = elem.timestamp(); } - // Properties (size, accessors, cache). - //----------------------------------------------------------------------------- - - size_t serialized_size(bool wire = true) const; - - void set_version(uint32_t value); - void set_previous_block_hash(hash_digest const& value); - void set_merkle(hash_digest const& value); - void set_timestamp(uint32_t value); - void set_bits(uint32_t value); - void set_nonce(uint32_t value); - - // hash_digest hash() const; - hash_digest hash_pow() const; - -#if defined(KTH_CURRENCY_LTC) - hash_digest litecoin_proof_of_work_hash() const; -#endif //KTH_CURRENCY_LTC - - // Validation. - //----------------------------------------------------------------------------- - - bool is_valid_proof_of_work(bool retarget = true) const; - - code check(bool retarget = false) const; - code accept(chain_state const& state) const; - + if (count == 0) { + return 0; + } - // THIS IS FOR LIBRARY USE ONLY, DO NOT CREATE A DEPENDENCY ON IT. - mutable validation_t validation{}; + // Sort only the elements we collected + std::sort(timestamps.begin(), timestamps.begin() + count); + + // Return the median (middle element for odd count, lower-middle for even) + return timestamps[count / 2]; +} + +/// Calculate median time past for a specific index in a range. +/// Takes the range and the index of the block for which to calculate MTP. +/// Uses up to median_time_past_blocks elements before the given index. +template + requires Timestamped> +[[nodiscard]] constexpr +uint32_t median_time_past_at(R&& range, size_t index) { + std::array timestamps{}; + size_t count = 0; + + // Collect timestamps from blocks before index (not including index) + // Go backwards from index-1 + for (size_t i = 0; i < median_time_past_blocks && i < index; ++i) { + timestamps[count++] = range[index - 1 - i].timestamp(); + } -// protected: - // So that block may call reset from its own. - // friend class block; + if (count == 0) { + return 0; + } - void reset(); - // void invalidate_cache() const; + std::sort(timestamps.begin(), timestamps.begin() + count); + return timestamps[count / 2]; +} -// private: -// mutable upgrade_mutex mutex_; -// mutable std::shared_ptr hash_; -}; +// Static assertion to verify the selected implementation satisfies the concepts +static_assert(Timestamped
, "header must be Timestamped"); +static_assert(HeaderLike
, "header must be HeaderLike"); } // namespace kth::domain::chain -#endif +#endif // KTH_DOMAIN_CHAIN_HEADER_HPP diff --git a/src/domain/include/kth/domain/chain/header.hpp.disabled b/src/domain/include/kth/domain/chain/header.hpp.disabled new file mode 100644 index 00000000..a38256aa --- /dev/null +++ b/src/domain/include/kth/domain/chain/header.hpp.disabled @@ -0,0 +1,129 @@ +// Copyright (c) 2016-2025 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef KTH_DOMAIN_CHAIN_HEADER_HPP +#define KTH_DOMAIN_CHAIN_HEADER_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +namespace kth::domain::chain { +struct KD_API header : header_basis, hash_memoizer
{ // NOLINT(cppcoreguidelines-special-member-functions) +public: + using list = std::vector
; + using ptr = std::shared_ptr
; + using const_ptr = std::shared_ptr
; + using ptr_list = std::vector
; + using const_ptr_list = std::vector; + + // THIS IS FOR LIBRARY USE ONLY, DO NOT CREATE A DEPENDENCY ON IT. + struct validation_t { + size_t height = 0; + uint32_t median_time_past = 0; + }; + + // Constructors. + //----------------------------------------------------------------------------- + using header_basis::header_basis; // inherit constructors from header_basis + + header() = default; + + explicit + header(header_basis const& basis) + : header_basis(basis) + {} + + /// This class is copy constructible and copy assignable. + // Note(kth): Cannot be defaulted because the std::mutex data member. + header(header const& x); + header& operator=(header const& x); + + + // Deserialization. + //----------------------------------------------------------------------------- + + + static + expect
from_data(byte_reader& reader, bool wire = true); + + // Serialization. + //----------------------------------------------------------------------------- + + data_chunk to_data(bool wire = true) const; + // void to_data(data_sink& stream, bool wire=true) const; + void to_data(data_sink& stream, bool wire = true) const; + + template + void to_data(W& sink, bool wire = true) const { + header_basis::to_data(sink, wire); + + if ( ! wire) { + sink.write_4_bytes_little_endian(validation.median_time_past); + } + } + + // Properties (size, accessors, cache). + //----------------------------------------------------------------------------- + + size_t serialized_size(bool wire = true) const; + + void set_version(uint32_t value); + void set_previous_block_hash(hash_digest const& value); + void set_merkle(hash_digest const& value); + void set_timestamp(uint32_t value); + void set_bits(uint32_t value); + void set_nonce(uint32_t value); + + // hash_digest hash() const; + hash_digest hash_pow() const; + +#if defined(KTH_CURRENCY_LTC) + hash_digest litecoin_proof_of_work_hash() const; +#endif //KTH_CURRENCY_LTC + + // Validation. + //----------------------------------------------------------------------------- + + bool is_valid_proof_of_work(bool retarget = true) const; + + code check(bool retarget = false) const; + code accept(chain_state const& state) const; + + + // THIS IS FOR LIBRARY USE ONLY, DO NOT CREATE A DEPENDENCY ON IT. + mutable validation_t validation{}; + +// protected: + // So that block may call reset from its own. + // friend class block; + + void reset(); + // void invalidate_cache() const; + +// private: +// mutable upgrade_mutex mutex_; +// mutable std::shared_ptr hash_; +}; + +} // namespace kth::domain::chain + +#endif diff --git a/src/domain/include/kth/domain/chain/header_basis.hpp b/src/domain/include/kth/domain/chain/header_basis.hpp.disabled similarity index 100% rename from src/domain/include/kth/domain/chain/header_basis.hpp rename to src/domain/include/kth/domain/chain/header_basis.hpp.disabled diff --git a/src/domain/include/kth/domain/chain/header_members.hpp b/src/domain/include/kth/domain/chain/header_members.hpp new file mode 100644 index 00000000..1ca2391a --- /dev/null +++ b/src/domain/include/kth/domain/chain/header_members.hpp @@ -0,0 +1,170 @@ +// Copyright (c) 2016-2025 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef KTH_DOMAIN_CHAIN_HEADER_MEMBERS_HPP +#define KTH_DOMAIN_CHAIN_HEADER_MEMBERS_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace kth::domain::chain { + +// Forward declaration +struct chain_state; + +/// Block header - stores fields as separate members. +/// Hash is computed on demand via free function hash(header). +/// See also: header_raw.hpp for an alternative implementation with raw byte storage. +struct KD_API header { + using list = std::vector
; + using ptr = std::shared_ptr
; + using const_ptr = std::shared_ptr
; + using ptr_list = std::vector; + using const_ptr_list = std::vector; + +private: + uint32_t version_{0}; + hash_digest previous_block_hash_{null_hash}; + hash_digest merkle_{null_hash}; + uint32_t timestamp_{0}; + uint32_t bits_{0}; + uint32_t nonce_{0}; + +public: + // Constructors. + //------------------------------------------------------------------------- + + constexpr header() = default; + + constexpr header(uint32_t version, hash_digest const& previous_block_hash, + hash_digest const& merkle, uint32_t timestamp, + uint32_t bits, uint32_t nonce) + : version_(version) + , previous_block_hash_(previous_block_hash) + , merkle_(merkle) + , timestamp_(timestamp) + , bits_(bits) + , nonce_(nonce) + {} + + // Defaulted copy/move (implicitly constexpr in C++20+) + constexpr header(header const&) = default; + constexpr header(header&&) = default; + constexpr header& operator=(header const&) = default; + constexpr header& operator=(header&&) = default; + + // Comparison operators (C++20 defaulted). + //------------------------------------------------------------------------- + + constexpr bool operator==(header const&) const = default; + + // Getters. + //------------------------------------------------------------------------- + + [[nodiscard]] constexpr uint32_t version() const { return version_; } + [[nodiscard]] constexpr hash_digest const& previous_block_hash() const { return previous_block_hash_; } + [[nodiscard]] constexpr hash_digest const& merkle() const { return merkle_; } + [[nodiscard]] constexpr uint32_t timestamp() const { return timestamp_; } + [[nodiscard]] constexpr uint32_t bits() const { return bits_; } + [[nodiscard]] constexpr uint32_t nonce() const { return nonce_; } + + // Deserialization. + //------------------------------------------------------------------------- + + static + expect
from_data(byte_reader& reader, bool wire = true); + + [[nodiscard]] + constexpr bool is_valid() const { + return (version_ != 0) || + (previous_block_hash_ != null_hash) || + (merkle_ != null_hash) || + (timestamp_ != 0) || + (bits_ != 0) || + (nonce_ != 0); + } + + // Serialization. + //------------------------------------------------------------------------- + + [[nodiscard]] + data_chunk to_data(bool wire = true) const; + + void to_data(data_sink& stream, bool wire = true) const; + + template + void to_data(W& sink, bool wire = true) const { + sink.write_4_bytes_little_endian(version_); + sink.write_hash(previous_block_hash_); + sink.write_hash(merkle_); + sink.write_4_bytes_little_endian(timestamp_); + sink.write_4_bytes_little_endian(bits_); + sink.write_4_bytes_little_endian(nonce_); + } + + // Properties (size). + //------------------------------------------------------------------------- + + static constexpr + size_t satoshi_fixed_size() { + return sizeof(version_) + + hash_size + + hash_size + + sizeof(timestamp_) + + sizeof(bits_) + + sizeof(nonce_); + } + + [[nodiscard]] + constexpr size_t serialized_size(bool /*wire*/ = true) const { + return satoshi_fixed_size(); + } + + // Proof computation. + //------------------------------------------------------------------------- + + [[nodiscard]] + static uint256_t proof(uint32_t bits); + + [[nodiscard]] + uint256_t proof() const; + + // Validation. + //------------------------------------------------------------------------- + + [[nodiscard]] + bool is_valid_timestamp() const; + + [[nodiscard]] + bool is_valid_proof_of_work(hash_digest const& hash, bool retarget = true) const; + + [[nodiscard]] + code check(hash_digest const& hash, bool retarget = false) const; + + [[nodiscard]] + code accept(chain_state const& state, hash_digest const& hash) const; +}; + +// Free function for hash computation. +//----------------------------------------------------------------------------- + +/// Compute the hash of a header (double SHA256 for Bitcoin, scrypt for Litecoin). +[[nodiscard]] +hash_digest hash(header const& h); + +} // namespace kth::domain::chain + +#endif // KTH_DOMAIN_CHAIN_HEADER_MEMBERS_HPP diff --git a/src/domain/include/kth/domain/chain/header_raw.hpp b/src/domain/include/kth/domain/chain/header_raw.hpp new file mode 100644 index 00000000..f6ae7676 --- /dev/null +++ b/src/domain/include/kth/domain/chain/header_raw.hpp @@ -0,0 +1,320 @@ +// Copyright (c) 2016-2025 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef KTH_DOMAIN_CHAIN_HEADER_RAW_HPP +#define KTH_DOMAIN_CHAIN_HEADER_RAW_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace kth::domain::chain { + +// Forward declaration +struct chain_state; + +/// Immutable block header - stores raw 80 bytes as a contiguous byte array. +/// All field access is through getters that decode from the raw data. +/// See also: header_members.hpp for an alternative implementation with explicit member fields. +struct KD_API header { + // Constants for field offsets within the 80-byte header. + static constexpr size_t version_offset = 0; + static constexpr size_t previous_block_hash_offset = 4; + static constexpr size_t merkle_offset = 36; + static constexpr size_t timestamp_offset = 68; + static constexpr size_t bits_offset = 72; + static constexpr size_t nonce_offset = 76; + static constexpr size_t serialized_size_wire = 80; + + using list = std::vector
; + using ptr = std::shared_ptr
; + using const_ptr = std::shared_ptr
; + using ptr_list = std::vector; + using const_ptr_list = std::vector; + +private: + // Raw 80-byte header data (little-endian as on wire). + std::array data_{}; + + // Validation metadata (not part of wire format, persisted when wire=false). + // uint32_t median_time_past_{0}; + + // Private helper to build data array from fields (used by constructor). + static constexpr std::array + make_data(uint32_t version, hash_digest const& previous_block_hash, + hash_digest const& merkle, uint32_t timestamp, + uint32_t bits, uint32_t nonce) { + std::array data{}; + + // Version (little-endian) + auto const ver_bytes = to_little_endian(version); + std::copy(ver_bytes.begin(), ver_bytes.end(), data.begin() + version_offset); + + // Previous block hash + std::copy(previous_block_hash.begin(), previous_block_hash.end(), + data.begin() + previous_block_hash_offset); + + // Merkle root + std::copy(merkle.begin(), merkle.end(), data.begin() + merkle_offset); + + // Timestamp (little-endian) + auto const ts_bytes = to_little_endian(timestamp); + std::copy(ts_bytes.begin(), ts_bytes.end(), data.begin() + timestamp_offset); + + // Bits (little-endian) + auto const bits_bytes = to_little_endian(bits); + std::copy(bits_bytes.begin(), bits_bytes.end(), data.begin() + bits_offset); + + // Nonce (little-endian) + auto const nonce_bytes = to_little_endian(nonce); + std::copy(nonce_bytes.begin(), nonce_bytes.end(), data.begin() + nonce_offset); + + return data; + } + +public: + // Constructors. + //------------------------------------------------------------------------- + + constexpr header() = default; + + // Construct from raw bytes (must be exactly 80 bytes). + explicit constexpr header(std::span raw_data) + : data_{} + // , median_time_past_{0} + { + std::copy(raw_data.begin(), raw_data.end(), data_.begin()); + } + + // Construct from raw byte array. + explicit constexpr header(std::array const& raw_data) + : data_{raw_data} + // , median_time_past_{0} + {} + + // // Construct from raw byte array with median_time_past. + // constexpr header(std::array const& raw_data, + // uint32_t mtp) + // : data_{raw_data} + // // , median_time_past_{mtp} + // {} + + // Construct from individual fields. + constexpr header(uint32_t version, hash_digest const& previous_block_hash, + hash_digest const& merkle, uint32_t timestamp, + uint32_t bits, uint32_t nonce) + : data_{make_data(version, previous_block_hash, merkle, timestamp, bits, nonce)} + // , median_time_past_{0} + {} + + // // Construct from individual fields with median_time_past. + // constexpr header(uint32_t version, hash_digest const& previous_block_hash, + // hash_digest const& merkle, uint32_t timestamp, + // uint32_t bits, uint32_t nonce, uint32_t mtp) + // : data_{make_data(version, previous_block_hash, merkle, timestamp, bits, nonce)} + // // , median_time_past_{mtp} + // {} + + // Defaulted copy/move. + constexpr header(header const&) = default; + constexpr header(header&&) = default; + constexpr header& operator=(header const&) = default; + constexpr header& operator=(header&&) = default; + + // Comparison operators (C++20 defaulted). + //------------------------------------------------------------------------- + + constexpr bool operator==(header const&) const = default; + + // Getters - return values (with endianness conversion). + //------------------------------------------------------------------------- + + [[nodiscard]] + constexpr uint32_t version() const { + return from_little_endian(version_span()); + } + + [[nodiscard]] + constexpr hash_digest previous_block_hash() const { + hash_digest result{}; + auto const span = previous_block_hash_span(); + std::copy(span.begin(), span.end(), result.begin()); + return result; + } + + [[nodiscard]] + constexpr hash_digest merkle() const { + hash_digest result{}; + auto const span = merkle_span(); + std::copy(span.begin(), span.end(), result.begin()); + return result; + } + + [[nodiscard]] + constexpr uint32_t timestamp() const { + return from_little_endian(timestamp_span()); + } + + [[nodiscard]] + constexpr uint32_t bits() const { + return from_little_endian(bits_span()); + } + + [[nodiscard]] + constexpr uint32_t nonce() const { + return from_little_endian(nonce_span()); + } + + // [[nodiscard]] + // constexpr uint32_t median_time_past() const { + // return median_time_past_; + // } + + // Getters - return spans (zero-copy views into raw data). + //------------------------------------------------------------------------- + + [[nodiscard]] + constexpr std::span version_span() const { + return std::span( + data_.data() + version_offset, sizeof(uint32_t)); + } + + [[nodiscard]] + constexpr std::span previous_block_hash_span() const { + return std::span( + data_.data() + previous_block_hash_offset, hash_size); + } + + [[nodiscard]] + constexpr std::span merkle_span() const { + return std::span( + data_.data() + merkle_offset, hash_size); + } + + [[nodiscard]] + constexpr std::span timestamp_span() const { + return std::span( + data_.data() + timestamp_offset, sizeof(uint32_t)); + } + + [[nodiscard]] + constexpr std::span bits_span() const { + return std::span( + data_.data() + bits_offset, sizeof(uint32_t)); + } + + [[nodiscard]] + constexpr std::span nonce_span() const { + return std::span( + data_.data() + nonce_offset, sizeof(uint32_t)); + } + + // Full raw data access. + [[nodiscard]] + constexpr std::span raw_data() const { + return std::span(data_); + } + + // Mutable raw data access (for direct deserialization). + // Use with caution - allows modifying internal state. + [[nodiscard]] + constexpr std::array& raw_data_mutable() { + return data_; + } + + // Deserialization. + //------------------------------------------------------------------------- + + static + expect
from_data(byte_reader& reader, bool wire = true); + + [[nodiscard]] + constexpr bool is_valid() const { + // Check if any field is non-zero (header has been set). + constexpr std::array zero_data{}; + return data_ != zero_data; + } + + // Serialization. + //------------------------------------------------------------------------- + + [[nodiscard]] + data_chunk to_data(bool wire = true) const; + + void to_data(data_sink& stream, bool wire = true) const; + + template + void to_data(W& sink, bool wire = true) const { + // Write raw bytes directly - already in little-endian wire format. + sink.write_bytes(data_.data(), serialized_size_wire); + + // if ( ! wire) { + // sink.write_4_bytes_little_endian(median_time_past_); + // } + } + + // Properties (size). + //------------------------------------------------------------------------- + + static constexpr + size_t satoshi_fixed_size() { + return serialized_size_wire; + } + + [[nodiscard]] + constexpr size_t serialized_size(bool wire = true) const { + // return serialized_size_wire + (wire ? 0 : sizeof(median_time_past_)); + return serialized_size_wire; + } + + // Proof computation. + //------------------------------------------------------------------------- + + [[nodiscard]] + static + uint256_t proof(uint32_t bits); + + [[nodiscard]] + uint256_t proof() const; + + // Validation. + //------------------------------------------------------------------------- + + [[nodiscard]] + bool is_valid_timestamp() const; + + [[nodiscard]] + bool is_valid_proof_of_work(hash_digest const& hash, bool retarget = true) const; + + [[nodiscard]] + code check(hash_digest const& hash, bool retarget = false) const; + + [[nodiscard]] + code accept(chain_state const& state, hash_digest const& hash) const; +}; + +// Free function for hash computation. +//----------------------------------------------------------------------------- + +/// Compute the hash of a header (double SHA256 for Bitcoin, scrypt for Litecoin). +[[nodiscard]] +hash_digest hash(header const& h); + +} // namespace kth::domain::chain + +#endif // KTH_DOMAIN_CHAIN_HEADER_RAW_HPP diff --git a/src/domain/include/kth/domain/constants/common.hpp b/src/domain/include/kth/domain/constants/common.hpp index 53adc4e4..50fc9255 100644 --- a/src/domain/include/kth/domain/constants/common.hpp +++ b/src/domain/include/kth/domain/constants/common.hpp @@ -13,7 +13,7 @@ // #include // #include -// #include +#include // #include // #include diff --git a/src/domain/include/kth/domain/message/header.hpp b/src/domain/include/kth/domain/message/header.hpp index 06a386de..57f43424 100644 --- a/src/domain/include/kth/domain/message/header.hpp +++ b/src/domain/include/kth/domain/message/header.hpp @@ -2,109 +2,71 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef KTH_DOMAIN_MESSAGE_HEADER_MESSAGE_HPP -#define KTH_DOMAIN_MESSAGE_HEADER_MESSAGE_HPP - -#include -#include -#include -#include +#ifndef KTH_DOMAIN_MESSAGE_HEADER_HPP +#define KTH_DOMAIN_MESSAGE_HEADER_HPP #include -#include #include -#include -#include -#include -#include - - -#include +#include namespace kth::domain::message { +/// Message header - wraps chain::header with P2P wire serialization. +/// The P2P "headers" message includes a trailing zero byte per header. struct KD_API header : chain::header { -public: using list = std::vector
; using ptr = std::shared_ptr
; - using const_ptr = std::shared_ptr; + using const_ptr = std::shared_ptr
; using ptr_list = std::vector; using const_ptr_list = std::vector; static - size_t satoshi_fixed_size(uint32_t version); - - header() = default; - header(uint32_t version, hash_digest const& previous_block_hash, hash_digest const& merkle, uint32_t timestamp, uint32_t bits, uint32_t nonce); - header(chain::header const& x); - header(header const& x) = default; - header(header&& x) = default; + std::string const command; - header& operator=(chain::header const& x); + static + uint32_t const version_minimum; - /// This class is move assignable but not copy assignable. //TODO(fernando): why? - header& operator=(header&& x) = default; - header& operator=(header const& /*x*/) /*= delete*/; + static + uint32_t const version_maximum; - friend - bool operator==(header const& x, header const& y) { - return chain::header(x) == chain::header(y); - } + // Inherit constructors + using chain::header::header; - friend - bool operator!=(header const& x, header const& y) { - return !(x == y); - } + header() = default; - friend - bool operator==(header const& x, chain::header const& y) { - return chain::header(x) == y; - } + header(chain::header const& x) + : chain::header(x) + {} - friend - bool operator!=(header const& x, chain::header const& y) { - return !(x == y); - } + header(header const&) = default; + header(header&&) = default; + header& operator=(header const&) = default; + header& operator=(header&&) = default; - friend - bool operator==(chain::header const& x, header const& y) { - return x == chain::header(y); - } - - friend - bool operator!=(chain::header const& x, header const& y) { - return !(x == y); - } + // P2P wire protocol size (includes trailing zero byte). + static + size_t satoshi_fixed_size(uint32_t version); + // Deserialization from P2P wire (reads trailing zero byte). static expect
from_data(byte_reader& reader, uint32_t version); + // Serialization to P2P wire (writes trailing zero byte). data_chunk to_data(uint32_t version) const; void to_data(uint32_t version, data_sink& stream) const; template void to_data(uint32_t version, W& sink) const { - chain::header::to_data(sink); + chain::header::to_data(sink, true); // wire=true if (version != version::level::canonical) { - sink.write_variable_little_endian(0); + sink.write_variable_little_endian(uint64_t{0}); } } - void reset(); size_t serialized_size(uint32_t version) const; - - - static - std::string const command; - - static - uint32_t const version_minimum; - - static - uint32_t const version_maximum; }; } // namespace kth::domain::message -#endif +#endif // KTH_DOMAIN_MESSAGE_HEADER_HPP diff --git a/src/domain/include/kth/domain/message/header.hpp.disabled b/src/domain/include/kth/domain/message/header.hpp.disabled new file mode 100644 index 00000000..06a386de --- /dev/null +++ b/src/domain/include/kth/domain/message/header.hpp.disabled @@ -0,0 +1,110 @@ +// Copyright (c) 2016-2025 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef KTH_DOMAIN_MESSAGE_HEADER_MESSAGE_HPP +#define KTH_DOMAIN_MESSAGE_HEADER_MESSAGE_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +#include + +namespace kth::domain::message { + +struct KD_API header : chain::header { +public: + using list = std::vector
; + using ptr = std::shared_ptr
; + using const_ptr = std::shared_ptr; + using ptr_list = std::vector; + using const_ptr_list = std::vector; + + static + size_t satoshi_fixed_size(uint32_t version); + + header() = default; + header(uint32_t version, hash_digest const& previous_block_hash, hash_digest const& merkle, uint32_t timestamp, uint32_t bits, uint32_t nonce); + header(chain::header const& x); + header(header const& x) = default; + header(header&& x) = default; + + header& operator=(chain::header const& x); + + /// This class is move assignable but not copy assignable. //TODO(fernando): why? + header& operator=(header&& x) = default; + header& operator=(header const& /*x*/) /*= delete*/; + + friend + bool operator==(header const& x, header const& y) { + return chain::header(x) == chain::header(y); + } + + friend + bool operator!=(header const& x, header const& y) { + return !(x == y); + } + + friend + bool operator==(header const& x, chain::header const& y) { + return chain::header(x) == y; + } + + friend + bool operator!=(header const& x, chain::header const& y) { + return !(x == y); + } + + friend + bool operator==(chain::header const& x, header const& y) { + return x == chain::header(y); + } + + friend + bool operator!=(chain::header const& x, header const& y) { + return !(x == y); + } + + static + expect
from_data(byte_reader& reader, uint32_t version); + + data_chunk to_data(uint32_t version) const; + void to_data(uint32_t version, data_sink& stream) const; + + template + void to_data(uint32_t version, W& sink) const { + chain::header::to_data(sink); + + if (version != version::level::canonical) { + sink.write_variable_little_endian(0); + } + } + + void reset(); + size_t serialized_size(uint32_t version) const; + + + static + std::string const command; + + static + uint32_t const version_minimum; + + static + uint32_t const version_maximum; +}; + +} // namespace kth::domain::message + +#endif diff --git a/src/domain/src/chain/block_basis.cpp b/src/domain/src/chain/block_basis.cpp index 95e51a7c..c357d585 100644 --- a/src/domain/src/chain/block_basis.cpp +++ b/src/domain/src/chain/block_basis.cpp @@ -75,7 +75,7 @@ bool block_basis::operator!=(block_basis const& x) const { // private void block_basis::reset() { - header_.reset(); + header_ = chain::header{}; transactions_.clear(); transactions_.shrink_to_fit(); } @@ -172,7 +172,7 @@ void block_basis::set_transactions(transaction::list&& value) { // Convenience property. hash_digest block_basis::hash() const { - return header_.hash(); + return chain::hash(header_); } // Validation helpers. @@ -418,7 +418,8 @@ code block_basis::connect_transactions(chain_state const& state) const { code block_basis::check(size_t serialized_size_false) const { code ec; - if ((ec = header_.check())) { + auto const header_hash = chain::hash(header_); + if ((ec = header_.check(header_hash))) { return ec; // TODO(legacy): relates to total of tx.size(false) (pool cache). -> no witness size @@ -485,7 +486,8 @@ code block_basis::accept(chain_state const& state, size_t serialized_size, bool auto const bip141 = false; // No segwit code ec; - if ((ec = header_.accept(state))) { + auto const header_hash = chain::hash(header_); + if ((ec = header_.accept(state, header_hash))) { return ec; } diff --git a/src/domain/src/chain/chain_state.cpp b/src/domain/src/chain/chain_state.cpp index bf787837..c04a3434 100644 --- a/src/domain/src/chain/chain_state.cpp +++ b/src/domain/src/chain/chain_state.cpp @@ -1327,7 +1327,7 @@ chain_state::data chain_state::to_block(chain_state const& pool, block const& bl // Replace pool chain state with block state at same (next) height. // Preserve data.timestamp.retarget promotion. auto const& header = block.header(); - data.hash = header.hash(); + data.hash = hash(header); data.bits.self = header.bits(); data.version.self = header.version(); data.timestamp.self = header.timestamp(); diff --git a/src/domain/src/chain/header.cpp b/src/domain/src/chain/header.cpp.disabled similarity index 100% rename from src/domain/src/chain/header.cpp rename to src/domain/src/chain/header.cpp.disabled diff --git a/src/domain/src/chain/header_basis.cpp b/src/domain/src/chain/header_basis.cpp.disabled similarity index 100% rename from src/domain/src/chain/header_basis.cpp rename to src/domain/src/chain/header_basis.cpp.disabled diff --git a/src/domain/src/chain/header_members.cpp b/src/domain/src/chain/header_members.cpp new file mode 100644 index 00000000..b823d9b2 --- /dev/null +++ b/src/domain/src/chain/header_members.cpp @@ -0,0 +1,233 @@ +// Copyright (c) 2016-2025 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace kth::domain::chain { + +// Hash statistics tracking for header_members +namespace { + boost::concurrent_flat_map header_members_hash_call_counts; + std::atomic header_members_total_hash_calls{0}; + constexpr size_t header_members_log_interval = 10000; // Log every N hash calls +} // namespace + +namespace detail { + +#pragma pack(push, 1) +struct header_members_packed { + uint32_t version; + byte previous_block_hash[32]; + byte merkle_root[32]; + uint32_t timestamp; + uint32_t bits; + uint32_t nonce; +}; +#pragma pack(pop) + +} // namespace detail + +// Use system clock because we require accurate time of day. +using wall_clock = std::chrono::system_clock; + +// Deserialization. +//----------------------------------------------------------------------------- + +expect
header::from_data(byte_reader& reader, bool /*wire*/) { + auto const packed_exp = reader.read_packed(); + if ( ! packed_exp) { + return std::unexpected(packed_exp.error()); + } + auto const& packed = *packed_exp; + + hash_digest previous_block_hash{}; + std::memcpy(previous_block_hash.data(), packed.previous_block_hash, hash_size); + + hash_digest merkle{}; + std::memcpy(merkle.data(), packed.merkle_root, hash_size); + + return header{ + packed.version, + previous_block_hash, + merkle, + packed.timestamp, + packed.bits, + packed.nonce + }; +} + +// Serialization. +//----------------------------------------------------------------------------- + +data_chunk header::to_data(bool wire) const { + data_chunk data; + auto const size = serialized_size(wire); + data.reserve(size); + data_sink ostream(data); + to_data(ostream, wire); + ostream.flush(); + KTH_ASSERT(data.size() == size); + return data; +} + +void header::to_data(data_sink& stream, bool wire) const { + ostream_writer sink_w(stream); + to_data(sink_w, wire); +} + +// Hash function. +//----------------------------------------------------------------------------- + +hash_digest hash(header const& hdr) { +#if defined(KTH_CURRENCY_LTC) + auto const result = litecoin_hash(hdr.to_data()); +#else + auto const result = bitcoin_hash(hdr.to_data()); +#endif + + // Track hash call statistics + header_members_hash_call_counts.emplace_or_visit(result, 1, [](auto& pair) { ++pair.second; }); + + auto const current_total = ++header_members_total_hash_calls; + if (current_total % header_members_log_interval == 0) { + size_t unique_hashes = 0; + size_t max_calls = 0; + hash_digest most_called_hash{}; + + header_members_hash_call_counts.visit_all([&](auto const& pair) { + ++unique_hashes; + if (pair.second > max_calls) { + max_calls = pair.second; + most_called_hash = pair.first; + } + }); + + spdlog::info("[hash_stats_members] Total calls: {}, Unique hashes: {}, Duplicate calls: {}, Most called hash: {} ({} times)", + current_total, + unique_hashes, + current_total - unique_hashes, + encode_hash(most_called_hash), + max_calls); + } + + return result; +} + +// Proof computation. +//----------------------------------------------------------------------------- + +uint256_t header::proof(uint32_t bits) { + compact const header_bits(bits); + + if (header_bits.is_overflowed()) { + return 0; + } + + uint256_t const& target = header_bits.big(); + + //************************************************************************* + // CONSENSUS: satoshi will throw division by zero in the case where the + // target is (2^256)-1 as the overflow will result in a zero divisor. + // While actually achieving this work is improbable, this method operates + // on user data method and therefore must be guarded. + //************************************************************************* + auto const divisor = target + 1; + + // We need to compute 2**256 / (target + 1), but we can't represent 2**256 + // as it's too large for uint256. However as 2**256 is at least as large as + // target + 1, it is equal to ((2**256 - target - 1) / (target + 1)) + 1, or + // (~target / (target + 1)) + 1. + return (divisor == 0) ? 0 : (~target / divisor) + 1; +} + +uint256_t header::proof() const { + return proof(bits_); +} + +// Validation helpers. +//----------------------------------------------------------------------------- + +/// BUGBUG: bitcoin 32bit unix time: en.wikipedia.org/wiki/Year_2038_problem +bool header::is_valid_timestamp() const { + static auto const two_hours = std::chrono::seconds(timestamp_future_seconds); + auto const time = wall_clock::from_time_t(timestamp_); + auto const future = wall_clock::now() + two_hours; + return time <= future; +} + +// [CheckProofOfWork] +bool header::is_valid_proof_of_work(hash_digest const& hash, bool retarget) const { + compact const compact_bits(bits_); + static uint256_t const pow_limit(compact{work_limit(retarget)}); + + if (compact_bits.is_overflowed()) { + return false; + } + + uint256_t const& target = compact_bits.big(); + + // Ensure claimed work is within limits. + if (target < 1 || target > pow_limit) { + return false; + } + + // Ensure actual work is at least claimed amount (smaller is more work). + return to_uint256(hash) <= target; +} + +// Validation. +//----------------------------------------------------------------------------- + +code header::check(hash_digest const& hash, bool retarget) const { + if ( ! is_valid_proof_of_work(hash, retarget)) { + return error::invalid_proof_of_work; + } + + if ( ! is_valid_timestamp()) { + return error::futuristic_timestamp; + } + + return error::success; +} + +code header::accept(chain_state const& state, hash_digest const& hash) const { + if (bits_ != state.work_required()) { + return error::incorrect_proof_of_work; + } + + if (state.is_checkpoint_conflict(hash)) { + return error::checkpoints_failed; + } + + if (state.is_under_checkpoint()) { + return error::success; + } + + if (version_ < state.minimum_version()) { + return error::old_version_block; + } + + if (timestamp_ <= state.median_time_past()) { + return error::timestamp_too_early; + } + + return error::success; +} + +} // namespace kth::domain::chain diff --git a/src/domain/src/chain/header_raw.cpp b/src/domain/src/chain/header_raw.cpp new file mode 100644 index 00000000..3a933202 --- /dev/null +++ b/src/domain/src/chain/header_raw.cpp @@ -0,0 +1,255 @@ +// Copyright (c) 2016-2025 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace kth::domain::chain { + +// Hash statistics tracking +namespace { + boost::concurrent_flat_map hash_call_counts; + std::atomic total_hash_calls{0}; + constexpr size_t log_interval = 10000; // Log every N hash calls +} // namespace + +// Use system clock because we require accurate time of day. +using wall_clock = std::chrono::system_clock; + +// Deserialization. +//----------------------------------------------------------------------------- + +//TODO: qué hacemos con el parametro wire? +expect
header::from_data(byte_reader& reader, bool _wire) { + header res; + auto const read = reader.read_bytes_to(std::span{res.raw_data_mutable()}); + if ( ! read) { + return std::unexpected(read.error()); + } + return res; +} + +// expect
header::from_data(byte_reader& reader, bool /*wire*/) { +// auto bytes = reader.read_array(); +// if ( ! bytes) { +// return std::unexpected(bytes.error()); +// } +// return header{*bytes}; +// } + +//TODO: qué hacemos con el parametro wire? +// expect
header::from_data(byte_reader& reader, bool _wire) { +// std::array data{}; +// auto const read = reader.read_bytes_to(std::span{data.data(), data.size()}); +// if ( ! read) { +// return std::unexpected(read.error()); +// } + +// return header{data}; +// } + + +// expect
header::from_data(byte_reader& reader, bool wire) { + +// //TODO: use +// // expect byte_reader::read_bytes_to(std::span dest) + + +// // Read 80 bytes directly. +// auto const bytes = reader.read_bytes(serialized_size_wire); +// if ( ! bytes) { +// return std::unexpected(bytes.error()); +// } + +// // Copy into a fixed-size array for construction. +// std::array data{}; +// std::memcpy(data.data(), bytes->data(), serialized_size_wire); + +// if (wire) { +// return header{data}; +// } + +// // Read median_time_past for non-wire format. +// auto const mtp = reader.read_little_endian(); +// if ( ! mtp) { +// return std::unexpected(mtp.error()); +// } + +// return header{data, *mtp}; +// } + +// Serialization. +//----------------------------------------------------------------------------- + +data_chunk header::to_data(bool wire) const { + data_chunk data; + auto const size = serialized_size(wire); + data.reserve(size); + data_sink ostream(data); + to_data(ostream, wire); + ostream.flush(); + KTH_ASSERT(data.size() == size); + return data; +} + +void header::to_data(data_sink& stream, bool wire) const { + ostream_writer sink_w(stream); + to_data(sink_w, wire); +} + +// Hash function. +//----------------------------------------------------------------------------- + +hash_digest hash(header const& hdr) { +#if defined(KTH_CURRENCY_LTC) + auto const result = litecoin_hash(hdr.raw_data()); +#else + auto const result = bitcoin_hash(hdr.raw_data()); +#endif + + // Track hash call statistics + hash_call_counts.emplace_or_visit(result, 1, [](auto& pair) { ++pair.second; }); + + auto const current_total = ++total_hash_calls; + if (current_total % log_interval == 0) { + size_t unique_hashes = 0; + size_t max_calls = 0; + hash_digest most_called_hash{}; + + hash_call_counts.visit_all([&](auto const& pair) { + ++unique_hashes; + if (pair.second > max_calls) { + max_calls = pair.second; + most_called_hash = pair.first; + } + }); + + spdlog::info("[hash_stats] Total calls: {}, Unique hashes: {}, Duplicate calls: {}, Most called hash: {} ({} times)", + current_total, + unique_hashes, + current_total - unique_hashes, + encode_hash(most_called_hash), + max_calls); + } + + return result; +} + +// Proof computation. +//----------------------------------------------------------------------------- + +uint256_t header::proof(uint32_t bits) { + compact const header_bits(bits); + + if (header_bits.is_overflowed()) { + return 0; + } + + uint256_t const& target = header_bits.big(); + + //************************************************************************* + // CONSENSUS: satoshi will throw division by zero in the case where the + // target is (2^256)-1 as the overflow will result in a zero divisor. + // While actually achieving this work is improbable, this method operates + // on user data method and therefore must be guarded. + //************************************************************************* + auto const divisor = target + 1; + + // We need to compute 2**256 / (target + 1), but we can't represent 2**256 + // as it's too large for uint256. However as 2**256 is at least as large as + // target + 1, it is equal to ((2**256 - target - 1) / (target + 1)) + 1, or + // (~target / (target + 1)) + 1. + return (divisor == 0) ? 0 : (~target / divisor) + 1; +} + +uint256_t header::proof() const { + return proof(bits()); +} + +// Validation helpers. +//----------------------------------------------------------------------------- + +/// BUGBUG: bitcoin 32bit unix time: en.wikipedia.org/wiki/Year_2038_problem +bool header::is_valid_timestamp() const { + static auto const two_hours = std::chrono::seconds(timestamp_future_seconds); + auto const time = wall_clock::from_time_t(timestamp()); + auto const future = wall_clock::now() + two_hours; + return time <= future; +} + +// [CheckProofOfWork] +bool header::is_valid_proof_of_work(hash_digest const& hash, bool retarget) const { + compact const compact_bits(bits()); + static uint256_t const pow_limit(compact{work_limit(retarget)}); + + if (compact_bits.is_overflowed()) { + return false; + } + + uint256_t const& target = compact_bits.big(); + + // Ensure claimed work is within limits. + if (target < 1 || target > pow_limit) { + return false; + } + + // Ensure actual work is at least claimed amount (smaller is more work). + return to_uint256(hash) <= target; +} + +// Validation. +//----------------------------------------------------------------------------- + +code header::check(hash_digest const& hash, bool retarget) const { + if ( ! is_valid_proof_of_work(hash, retarget)) { + return error::invalid_proof_of_work; + } + + if ( ! is_valid_timestamp()) { + return error::futuristic_timestamp; + } + + return error::success; +} + +code header::accept(chain_state const& state, hash_digest const& hash) const { + if (bits() != state.work_required()) { + return error::incorrect_proof_of_work; + } + + if (state.is_checkpoint_conflict(hash)) { + return error::checkpoints_failed; + } + + if (state.is_under_checkpoint()) { + return error::success; + } + + if (version() < state.minimum_version()) { + return error::old_version_block; + } + + if (timestamp() <= state.median_time_past()) { + return error::timestamp_too_early; + } + + return error::success; +} + +} // namespace kth::domain::chain diff --git a/src/domain/src/chain/output_basis.cpp b/src/domain/src/chain/output_basis.cpp index de333067..6c9fa2e5 100644 --- a/src/domain/src/chain/output_basis.cpp +++ b/src/domain/src/chain/output_basis.cpp @@ -67,7 +67,7 @@ expect output_basis::from_data(byte_reader& reader, bool /*wire*/) token_data_opt token_data = std::nullopt; auto const has_token_data = *token_prefix_byte == chain::encoding::PREFIX_BYTE; if (has_token_data) { - reader.skip(1); // skip prefix byte + reader.unsafe_skip_byte(); // skip prefix byte, don't need to check error here as peek succeeded auto token = token::encoding::from_data(reader); if ( ! token) { return std::unexpected(token.error()); diff --git a/src/domain/src/message/header.cpp b/src/domain/src/message/header.cpp index 5c3744f7..854089dd 100644 --- a/src/domain/src/message/header.cpp +++ b/src/domain/src/message/header.cpp @@ -6,11 +6,7 @@ #include #include -#include -#include -#include -#include #include #include #include @@ -24,40 +20,17 @@ uint32_t const header::version_maximum = version::level::maximum; size_t header::satoshi_fixed_size(uint32_t version) { auto const canonical = (version == version::level::canonical); - return chain::header::satoshi_fixed_size() + (canonical ? 0 : infrastructure::message::variable_uint_size(0)); -} - -header::header(uint32_t version, - hash_digest const& previous_block_hash, - hash_digest const& merkle, - uint32_t timestamp, - uint32_t bits, - uint32_t nonce) - : chain::header(version, previous_block_hash, merkle, timestamp, bits, nonce) -{} - -header::header(chain::header const& x) - : chain::header(x) { -} - -header& header::operator=(chain::header const& x) { - chain::header::operator=(x); - return *this; -} - -header& header::operator=(header const& x) { - chain::header::operator=(x); - return *this; + return chain::header::satoshi_fixed_size() + + (canonical ? 0 : infrastructure::message::variable_uint_size(0)); } // Deserialization. //----------------------------------------------------------------------------- -// static expect
header::from_data(byte_reader& reader, uint32_t version) { - auto header = chain::header::from_data(reader); - if ( ! header) { - return std::unexpected(header.error()); + auto chain_header = chain::header::from_data(reader, true); // wire=true + if ( ! chain_header) { + return std::unexpected(chain_header.error()); } // The header message must trail a zero byte (yes, it's stoopid). @@ -71,7 +44,8 @@ expect
header::from_data(byte_reader& reader, uint32_t version) { return std::unexpected(error::version_too_new); } } - return header; + + return header{*chain_header}; } // Serialization. @@ -93,10 +67,6 @@ void header::to_data(uint32_t version, data_sink& stream) const { to_data(version, sink_w); } -void header::reset() { - chain::header::reset(); -} - size_t header::serialized_size(uint32_t version) const { return satoshi_fixed_size(version); } diff --git a/src/domain/src/message/header.cpp.disabled b/src/domain/src/message/header.cpp.disabled new file mode 100644 index 00000000..5c3744f7 --- /dev/null +++ b/src/domain/src/message/header.cpp.disabled @@ -0,0 +1,104 @@ +// Copyright (c) 2016-2025 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace kth::domain::message { + +std::string const header::command = "headers"; +uint32_t const header::version_minimum = version::level::minimum; +uint32_t const header::version_maximum = version::level::maximum; + +size_t header::satoshi_fixed_size(uint32_t version) { + auto const canonical = (version == version::level::canonical); + return chain::header::satoshi_fixed_size() + (canonical ? 0 : infrastructure::message::variable_uint_size(0)); +} + +header::header(uint32_t version, + hash_digest const& previous_block_hash, + hash_digest const& merkle, + uint32_t timestamp, + uint32_t bits, + uint32_t nonce) + : chain::header(version, previous_block_hash, merkle, timestamp, bits, nonce) +{} + +header::header(chain::header const& x) + : chain::header(x) { +} + +header& header::operator=(chain::header const& x) { + chain::header::operator=(x); + return *this; +} + +header& header::operator=(header const& x) { + chain::header::operator=(x); + return *this; +} + +// Deserialization. +//----------------------------------------------------------------------------- + +// static +expect
header::from_data(byte_reader& reader, uint32_t version) { + auto header = chain::header::from_data(reader); + if ( ! header) { + return std::unexpected(header.error()); + } + + // The header message must trail a zero byte (yes, it's stoopid). + // bitcoin.org/en/developer-reference#headers + if (version != version::level::canonical) { + auto const trail = reader.read_byte(); + if ( ! trail) { + return std::unexpected(trail.error()); + } + if (*trail != 0x00) { + return std::unexpected(error::version_too_new); + } + } + return header; +} + +// Serialization. +//----------------------------------------------------------------------------- + +data_chunk header::to_data(uint32_t version) const { + data_chunk data; + auto const size = serialized_size(version); + data.reserve(size); + data_sink ostream(data); + to_data(version, ostream); + ostream.flush(); + KTH_ASSERT(data.size() == size); + return data; +} + +void header::to_data(uint32_t version, data_sink& stream) const { + ostream_writer sink_w(stream); + to_data(version, sink_w); +} + +void header::reset() { + chain::header::reset(); +} + +size_t header::serialized_size(uint32_t version) const { + return satoshi_fixed_size(version); +} + +} // namespace kth::domain::message diff --git a/src/domain/src/message/headers.cpp b/src/domain/src/message/headers.cpp index a67a5352..aeb9725a 100644 --- a/src/domain/src/message/headers.cpp +++ b/src/domain/src/message/headers.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -107,14 +108,14 @@ bool headers::is_sequential() const { return true; } - auto previous = elements_.front().hash(); + auto previous = chain::hash(elements_.front()); for (auto it = elements_.begin() + 1; it != elements_.end(); ++it) { if (it->previous_block_hash() != previous) { return false; } - previous = it->hash(); + previous = chain::hash(*it); } return true; @@ -124,7 +125,7 @@ void headers::to_hashes(hash_list& out) const { out.clear(); out.reserve(elements_.size()); auto const map = [&out](header const& header) { - out.push_back(header.hash()); + out.push_back(chain::hash(header)); }; std::for_each(elements_.begin(), elements_.end(), map); @@ -135,7 +136,7 @@ void headers::to_inventory(inventory_vector::list& out, out.clear(); out.reserve(elements_.size()); auto const map = [&out, type](header const& header) { - out.emplace_back(type, header.hash()); + out.emplace_back(type, chain::hash(header)); }; std::for_each(elements_.begin(), elements_.end(), map); diff --git a/src/domain/src/utility/property_tree.cpp b/src/domain/src/utility/property_tree.cpp index 434155b6..654727ad 100644 --- a/src/domain/src/utility/property_tree.cpp +++ b/src/domain/src/utility/property_tree.cpp @@ -46,7 +46,7 @@ ptree property_list(config::header const& header) { ptree tree; tree.put("bits", block_header.bits()); - tree.put("hash", hash256(block_header.hash())); + tree.put("hash", hash256(hash(block_header))); tree.put("merkle_tree_hash", hash256(block_header.merkle())); tree.put("nonce", block_header.nonce()); tree.put("previous_block_hash", hash256(block_header.previous_block_hash())); diff --git a/src/domain/test/chain/block.cpp b/src/domain/test/chain/block.cpp index 8f108892..17749bc7 100644 --- a/src/domain/test/chain/block.cpp +++ b/src/domain/test/chain/block.cpp @@ -178,7 +178,7 @@ TEST_CASE("block constructor 5 always equals params", "[chain block]") { TEST_CASE("block hash always returns header hash", "[chain block]") { chain::block const instance; - REQUIRE(instance.header().hash() == instance.hash()); + REQUIRE(chain::hash(instance.header()) == instance.hash()); } TEST_CASE("block is valid merkle root uninitialized returns true", "[chain block]") { diff --git a/src/domain/test/chain/compare_header_benchmarks.py b/src/domain/test/chain/compare_header_benchmarks.py new file mode 100755 index 00000000..ca0d696e --- /dev/null +++ b/src/domain/test/chain/compare_header_benchmarks.py @@ -0,0 +1,410 @@ +#!/usr/bin/env python3 +""" +Header Implementation Benchmark Comparison Tool + +This script runs both header implementation benchmarks (header_raw and header_members), +parses the results, and generates a markdown comparison report. + +Usage: + python compare_header_benchmarks.py [--output report.md] [--runs N] +""" + +import subprocess +import re +import argparse +import sys +from datetime import datetime +from pathlib import Path +from typing import Optional + + +def find_benchmark_executables() -> tuple[Path, Path]: + """Find the benchmark executables in the build directory.""" + # Try common build paths + build_paths = [ + Path("build/build/Release/src/domain"), + Path("build/Release/src/domain"), + Path("build/src/domain"), + Path("../../build/build/Release/src/domain"), + ] + + raw_exe = None + members_exe = None + + for base in build_paths: + raw_candidate = base / "kth_domain_benchmarks_header_raw" + members_candidate = base / "kth_domain_benchmarks_header_members" + + if raw_candidate.exists(): + raw_exe = raw_candidate + if members_candidate.exists(): + members_exe = members_candidate + + if raw_exe and members_exe: + break + + if not raw_exe: + # Try absolute path + raw_exe = Path("/Users/fernando/dev/kth/kth-mono/build/build/Release/src/domain/kth_domain_benchmarks_header_raw") + if not members_exe: + members_exe = Path("/Users/fernando/dev/kth/kth-mono/build/build/Release/src/domain/kth_domain_benchmarks_header_members") + + return raw_exe, members_exe + + +def run_benchmark(executable: Path, runs: int = 1) -> str: + """Run a benchmark executable and return its output.""" + if not executable.exists(): + raise FileNotFoundError(f"Benchmark executable not found: {executable}") + + print(f"Running {executable.name}...", file=sys.stderr) + + # Run multiple times and take the last run (warmup effect) + output = "" + for i in range(runs): + result = subprocess.run( + [str(executable)], + capture_output=True, + text=True, + timeout=300 # 5 minute timeout + ) + output = result.stdout + result.stderr + if i < runs - 1: + print(f" Run {i + 1}/{runs} complete (warmup)", file=sys.stderr) + + print(f" Final run complete", file=sys.stderr) + return output + + +def parse_benchmark_output(output: str) -> dict: + """ + Parse nanobench output and extract benchmark results. + + Format: + | ns/op | op/s | err% | total | Benchmark Title + |--------------------:|--------------------:|--------:|----------:|:--------------- + | 2.14 | 467,112,663.79 | 1.9% | 0.01 | `implementation_name` + """ + results = {} + + # Extract memory layout info + sizeof_match = re.search(r'sizeof\(header\):\s+(\d+)\s+bytes', output) + alignof_match = re.search(r'alignof\(header\):\s+(\d+)\s+bytes', output) + + if sizeof_match: + results['sizeof'] = int(sizeof_match.group(1)) + if alignof_match: + results['alignof'] = int(alignof_match.group(1)) + + lines = output.split('\n') + current_title = None + + for line in lines: + # Match header row (contains title, no backticks, no separator characters) + # | ns/op | op/s | err% | total | Construction from Fields + header_match = re.match( + r'\|\s+ns/op\s+\|\s+op/s\s+\|\s+err%\s+\|\s+total\s+\|\s+(.+)$', + line + ) + if header_match: + current_title = header_match.group(1).strip() + continue + + # Skip separator rows (contain :--- or ---:) + if '|---' in line or '|:--' in line: + continue + + # Match data row (contains backticks with implementation name) + # | 2.14 | 467,112,663.79 | 1.9% | 0.01 | `header_raw (array-based)` + # | 19,597.78 | 51,026.20 | 5.2% | 0.01 | `header_raw (array-based)` + # May also have :wavy_dash: prefix for unstable results + data_match = re.match( + r'\|\s+([\d,]+\.?\d*)\s+\|\s+([\d,]+\.?\d*)\s+\|\s+([\d.]+)%\s+\|\s+([\d.]+)\s+\|.*`([^`]+)`', + line + ) + if data_match and current_title: + ns_op = float(data_match.group(1).replace(',', '')) + ops_s = float(data_match.group(2).replace(',', '')) + err_pct = float(data_match.group(3)) + impl_name = data_match.group(5) + + results[current_title] = { + 'ns_op': ns_op, + 'ops_s': ops_s, + 'err_pct': err_pct, + 'impl': impl_name + } + + return results + + +def calculate_diff(raw_val: float, members_val: float) -> tuple[float, str]: + """Calculate percentage difference and return formatted string.""" + if raw_val == 0: + return 0, "N/A" + + diff_pct = ((members_val - raw_val) / raw_val) * 100 + + if abs(diff_pct) < 1: + return diff_pct, "~same" + elif diff_pct > 0: + return diff_pct, f"+{diff_pct:.1f}% slower" + else: + return diff_pct, f"{abs(diff_pct):.1f}% faster" + + +def generate_markdown_report( + raw_results: dict, + members_results: dict, + output_path: Optional[Path] = None +) -> str: + """Generate a markdown comparison report.""" + + report = [] + report.append("# Header Implementation Benchmark Comparison") + report.append("") + report.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + report.append("") + + # Memory layout comparison + report.append("## Memory Layout") + report.append("") + report.append("| Property | header_raw | header_members | Difference |") + report.append("|----------|------------|----------------|------------|") + + raw_sizeof = raw_results.get('sizeof', 'N/A') + members_sizeof = members_results.get('sizeof', 'N/A') + sizeof_diff = "" + if isinstance(raw_sizeof, int) and isinstance(members_sizeof, int): + diff = members_sizeof - raw_sizeof + sizeof_diff = f"{diff:+d} bytes" if diff != 0 else "same" + + report.append(f"| sizeof(header) | {raw_sizeof} bytes | {members_sizeof} bytes | {sizeof_diff} |") + + raw_alignof = raw_results.get('alignof', 'N/A') + members_alignof = members_results.get('alignof', 'N/A') + alignof_diff = "" + if isinstance(raw_alignof, int) and isinstance(members_alignof, int): + diff = members_alignof - raw_alignof + alignof_diff = f"{diff:+d} bytes" if diff != 0 else "same" + + report.append(f"| alignof(header) | {raw_alignof} bytes | {members_alignof} bytes | {alignof_diff} |") + report.append("") + + # Benchmark categories + categories = { + "Construction": [ + "Construction from Fields", + "Construction from Raw Bytes", + "Default Construction", + ], + "Field Access": [ + "Access: version", + "Access: timestamp", + "Access: bits", + "Access: nonce", + "Access: previous_block_hash", + "Access: merkle", + "Access: ALL fields", + ], + "Serialization": [ + "Serialization: to_data(wire=true)", + "Raw Data Access (zero-copy)", + ], + "Deserialization": [ + "Deserialization: from_data(wire=true)", + ], + "Hashing": [ + "Hash Computation: chain::hash()", + ], + "Copy/Move": [ + "Copy Construction", + "Move Construction", + ], + "Comparison": [ + "Equality Comparison (==)", + ], + "Batch Operations": [ + "Batch Construction (10000 headers)", + "Batch Construction from Raw (10000 headers)", + "Batch Hashing (10000 headers)", + ], + "Realistic Workload": [ + "IBD Simulation: construct + access + hash (2000 headers)", + ], + } + + report.append("## Performance Comparison") + report.append("") + report.append("Lower ns/op is better. The 'Winner' column shows which implementation is faster.") + report.append("") + + all_benchmarks = [] # Track for summary + + for category, benchmarks in categories.items(): + has_data = False + category_rows = [] + + for bench_name in benchmarks: + raw_data = raw_results.get(bench_name) + members_data = members_results.get(bench_name) + + if raw_data or members_data: + has_data = True + + raw_ns = raw_data['ns_op'] if raw_data else None + raw_err = raw_data['err_pct'] if raw_data else None + members_ns = members_data['ns_op'] if members_data else None + members_err = members_data['err_pct'] if members_data else None + + # Determine winner + winner = "" + diff_str = "" + if raw_ns and members_ns: + diff_pct, diff_str = calculate_diff(raw_ns, members_ns) + if diff_pct < -1: + winner = "**members**" + elif diff_pct > 1: + winner = "**raw**" + else: + winner = "tie" + all_benchmarks.append((bench_name, diff_pct)) + elif raw_ns: + winner = "raw only" + diff_str = "N/A" + elif members_ns: + winner = "members only" + diff_str = "N/A" + + raw_str = f"{raw_ns:.2f} ±{raw_err:.1f}%" if raw_ns else "N/A" + members_str = f"{members_ns:.2f} ±{members_err:.1f}%" if members_ns else "N/A" + + category_rows.append(f"| {bench_name} | {raw_str} | {members_str} | {diff_str} | {winner} |") + + if has_data: + report.append(f"### {category}") + report.append("") + report.append("| Benchmark | header_raw (ns/op) | header_members (ns/op) | Difference | Winner |") + report.append("|-----------|-------------------|----------------------|------------|--------|") + report.extend(category_rows) + report.append("") + + # Summary + report.append("## Summary") + report.append("") + + # Count wins + raw_wins = 0 + members_wins = 0 + ties = 0 + + for bench_name, diff_pct in all_benchmarks: + if diff_pct < -1: + members_wins += 1 + elif diff_pct > 1: + raw_wins += 1 + else: + ties += 1 + + report.append(f"- **header_raw wins**: {raw_wins} benchmarks") + report.append(f"- **header_members wins**: {members_wins} benchmarks") + report.append(f"- **Ties** (within 1%): {ties} benchmarks") + report.append("") + + # Key observations + report.append("### Key Observations") + report.append("") + report.append("- `header_raw` (80 bytes): Array-based storage with zero-copy raw data access") + report.append("- `header_members` (80 bytes): Traditional member-based storage") + report.append("") + report.append("**header_raw advantages:**") + report.append("- Zero-copy raw data access for serialization") + report.append("- Better cache locality for sequential header processing") + report.append("") + report.append("**header_members advantages:**") + report.append("- More intuitive code structure") + report.append("- Direct field access without byte extraction") + report.append("") + + result = '\n'.join(report) + + if output_path: + output_path.write_text(result) + print(f"Report written to: {output_path}", file=sys.stderr) + + return result + + +def main(): + parser = argparse.ArgumentParser( + description="Compare header implementation benchmarks" + ) + parser.add_argument( + '--output', '-o', + type=Path, + default=Path("header_benchmark_comparison.md"), + help="Output markdown file path (default: header_benchmark_comparison.md)" + ) + parser.add_argument( + '--runs', '-r', + type=int, + default=3, + help="Number of benchmark runs (last run is used, earlier runs are warmup)" + ) + parser.add_argument( + '--raw-exe', + type=Path, + help="Path to header_raw benchmark executable" + ) + parser.add_argument( + '--members-exe', + type=Path, + help="Path to header_members benchmark executable" + ) + + args = parser.parse_args() + + # Find executables + if args.raw_exe and args.members_exe: + raw_exe, members_exe = args.raw_exe, args.members_exe + else: + raw_exe, members_exe = find_benchmark_executables() + + print(f"Using executables:", file=sys.stderr) + print(f" raw: {raw_exe}", file=sys.stderr) + print(f" members: {members_exe}", file=sys.stderr) + print("", file=sys.stderr) + + # Run benchmarks + try: + raw_output = run_benchmark(raw_exe, args.runs) + members_output = run_benchmark(members_exe, args.runs) + except FileNotFoundError as e: + print(f"Error: {e}", file=sys.stderr) + print("Please build the benchmarks first with:", file=sys.stderr) + print(" cmake --build build --target kth_domain_benchmarks_header_raw", file=sys.stderr) + print(" cmake --build build --target kth_domain_benchmarks_header_members", file=sys.stderr) + sys.exit(1) + except subprocess.TimeoutExpired: + print("Error: Benchmark timed out", file=sys.stderr) + sys.exit(1) + + # Parse results + raw_results = parse_benchmark_output(raw_output) + members_results = parse_benchmark_output(members_output) + + # Debug: print parsed benchmark names + print(f"\nParsed {len(raw_results) - 2} benchmarks from header_raw", file=sys.stderr) + print(f"Parsed {len(members_results) - 2} benchmarks from header_members", file=sys.stderr) + + # Generate report + report = generate_markdown_report(raw_results, members_results, args.output) + + # Also print to stdout + print(report) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/domain/test/chain/header.cpp b/src/domain/test/chain/header.cpp index 182bb4fd..d3a5a0a0 100644 --- a/src/domain/test/chain/header.cpp +++ b/src/domain/test/chain/header.cpp @@ -9,6 +9,162 @@ using namespace kth; using namespace kd; +// ============================================================================= +// Compile-time tests (static_assert) +// ============================================================================= +// These tests verify that header operations work at compile-time in C++23. +// If any of these fail, compilation will fail with an error. + +namespace { + +// Test data for compile-time tests +constexpr hash_digest ct_prev_hash = {{ + 0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, + 0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f, + 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c, + 0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00 +}}; + +constexpr hash_digest ct_merkle = {{ + 0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2, + 0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61, + 0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32, + 0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a +}}; + +// Test: Default constructor is constexpr +constexpr chain::header ct_default_header{}; +static_assert( ! ct_default_header.is_valid(), "default header should be invalid"); +static_assert(ct_default_header.version() == 0, "default version should be 0"); +static_assert(ct_default_header.timestamp() == 0, "default timestamp should be 0"); +static_assert(ct_default_header.bits() == 0, "default bits should be 0"); +static_assert(ct_default_header.nonce() == 0, "default nonce should be 0"); +// static_assert(ct_default_header.median_time_past() == 0, "default mtp should be 0"); + +// Test: Field constructor is constexpr +constexpr chain::header ct_header_from_fields{ + 1u, // version + ct_prev_hash, // previous_block_hash + ct_merkle, // merkle + 1231006505u, // timestamp (Bitcoin genesis) + 0x1d00ffffu, // bits + 2083236893u // nonce +}; +static_assert(ct_header_from_fields.is_valid(), "constructed header should be valid"); +static_assert(ct_header_from_fields.version() == 1u, "version mismatch"); +static_assert(ct_header_from_fields.timestamp() == 1231006505u, "timestamp mismatch"); +static_assert(ct_header_from_fields.bits() == 0x1d00ffffu, "bits mismatch"); +static_assert(ct_header_from_fields.nonce() == 2083236893u, "nonce mismatch"); +// static_assert(ct_header_from_fields.median_time_past() == 0, "mtp should be 0"); + +// Test: Constructor with median_time_past is constexpr +// constexpr chain::header ct_header_with_mtp{ +// 1u, ct_prev_hash, ct_merkle, 1231006505u, 0x1d00ffffu, 2083236893u, 12345u +// }; +// static_assert(ct_header_with_mtp.median_time_past() == 12345u, "mtp mismatch"); + +// Test: Copy constructor is constexpr +constexpr chain::header ct_header_copy{ct_header_from_fields}; +static_assert(ct_header_copy.version() == ct_header_from_fields.version(), "copy version mismatch"); +static_assert(ct_header_copy.timestamp() == ct_header_from_fields.timestamp(), "copy timestamp mismatch"); + +// Test: Equality operator is constexpr +static_assert(ct_header_copy == ct_header_from_fields, "copy should equal original"); +static_assert( ! (ct_header_copy != ct_header_from_fields), "copy should not be unequal to original"); +static_assert(ct_default_header != ct_header_from_fields, "default should not equal constructed"); + +// Test: previous_block_hash getter is constexpr +static_assert(ct_header_from_fields.previous_block_hash() == ct_prev_hash, "prev_hash mismatch"); + +// Test: merkle getter is constexpr +static_assert(ct_header_from_fields.merkle() == ct_merkle, "merkle mismatch"); + +// Test: serialized_size is constexpr +static_assert(ct_header_from_fields.serialized_size(true) == 80, "wire size should be 80"); +static_assert(ct_header_from_fields.serialized_size(false) == 80, "non-wire size should be 80"); + +// Test: satoshi_fixed_size is constexpr +static_assert(chain::header::satoshi_fixed_size() == 80, "fixed size should be 80"); + +// Test: raw_data span is constexpr +constexpr auto ct_raw_data = ct_header_from_fields.raw_data(); +static_assert(ct_raw_data.size() == 80, "raw_data size should be 80"); + +// Test: Field spans are constexpr +constexpr auto ct_version_span = ct_header_from_fields.version_span(); +static_assert(ct_version_span.size() == 4, "version_span size should be 4"); + +constexpr auto ct_prev_hash_span = ct_header_from_fields.previous_block_hash_span(); +static_assert(ct_prev_hash_span.size() == 32, "previous_block_hash_span size should be 32"); + +constexpr auto ct_merkle_span = ct_header_from_fields.merkle_span(); +static_assert(ct_merkle_span.size() == 32, "merkle_span size should be 32"); + +constexpr auto ct_timestamp_span = ct_header_from_fields.timestamp_span(); +static_assert(ct_timestamp_span.size() == 4, "timestamp_span size should be 4"); + +constexpr auto ct_bits_span = ct_header_from_fields.bits_span(); +static_assert(ct_bits_span.size() == 4, "bits_span size should be 4"); + +constexpr auto ct_nonce_span = ct_header_from_fields.nonce_span(); +static_assert(ct_nonce_span.size() == 4, "nonce_span size should be 4"); + +// Test: Raw byte array constructor is constexpr +constexpr std::array ct_raw_bytes = {{ + // version (little-endian): 1 + 0x01, 0x00, 0x00, 0x00, + // previous block hash (32 bytes) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // merkle root (32 bytes) + 0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2, + 0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61, + 0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32, + 0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, + // timestamp (little-endian): 1231006505 + 0x29, 0xab, 0x5f, 0x49, + // bits (little-endian): 0x1d00ffff + 0xff, 0xff, 0x00, 0x1d, + // nonce (little-endian): 2083236893 + 0x1d, 0xac, 0x2b, 0x7c +}}; +constexpr chain::header ct_header_from_bytes{ct_raw_bytes}; +static_assert(ct_header_from_bytes.version() == 1, "version from bytes mismatch"); +static_assert(ct_header_from_bytes.timestamp() == 1231006505u, "timestamp from bytes mismatch"); +static_assert(ct_header_from_bytes.bits() == 0x1d00ffffu, "bits from bytes mismatch"); +static_assert(ct_header_from_bytes.nonce() == 2083236893u, "nonce from bytes mismatch"); + +// Test: Endianness round-trip at compile time +// Create header from fields, verify raw bytes match expected layout +constexpr chain::header ct_endian_test{2u, null_hash, null_hash, 3u, 4u, 5u}; +static_assert(ct_endian_test.version() == 2u, "endian test version"); +static_assert(ct_endian_test.timestamp() == 3u, "endian test timestamp"); +static_assert(ct_endian_test.bits() == 4u, "endian test bits"); +static_assert(ct_endian_test.nonce() == 5u, "endian test nonce"); + +// Verify little-endian encoding in raw_data +constexpr auto ct_endian_raw = ct_endian_test.raw_data(); +// version = 2 should be 0x02, 0x00, 0x00, 0x00 in little-endian +static_assert(ct_endian_raw[0] == 0x02, "version byte 0"); +static_assert(ct_endian_raw[1] == 0x00, "version byte 1"); +static_assert(ct_endian_raw[2] == 0x00, "version byte 2"); +static_assert(ct_endian_raw[3] == 0x00, "version byte 3"); + +// Test: Constexpr lambda to build header (C++20+) +constexpr auto make_test_header = []() { + return chain::header{42u, null_hash, null_hash, 100u, 200u, 300u}; +}; +constexpr chain::header ct_lambda_header = make_test_header(); +static_assert(ct_lambda_header.version() == 42u, "lambda header version"); + +} // anonymous namespace + +// ============================================================================= +// Runtime tests (Catch2) +// ============================================================================= + // Start Test Suite: chain header tests TEST_CASE("chain header constructor 1 always initialized invalid", "[chain header]") { @@ -161,13 +317,7 @@ TEST_CASE("chain header version accessor always returns initialized value", " REQUIRE(value == instance.version()); } -TEST_CASE("chain header version setter roundtrip success", "[chain header]") { - uint32_t expected = 4521u; - chain::header instance; - REQUIRE(expected != instance.version()); - instance.set_version(expected); - REQUIRE(expected == instance.version()); -} +// Note: setter tests removed - header is now immutable TEST_CASE("chain header previous block hash accessor 1 always returns initialized value", "[chain header]") { auto const value = "abababababababababababababababababababababababababababababababab"_hash; @@ -195,26 +345,6 @@ TEST_CASE("chain header previous block hash accessor 2 always returns initial REQUIRE(value == instance.previous_block_hash()); } -TEST_CASE("chain header previous block hash setter 1 roundtrip success", "[chain header]") { - auto const expected = "fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe"_hash; - chain::header instance; - REQUIRE(expected != instance.previous_block_hash()); - instance.set_previous_block_hash(expected); - REQUIRE(expected == instance.previous_block_hash()); -} - -TEST_CASE("chain header previous block hash setter 2 roundtrip success", "[chain header]") { - auto const expected = "fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe"_hash; - - // This must be non-const. - auto duplicate = expected; - - chain::header instance; - REQUIRE(expected != instance.previous_block_hash()); - instance.set_previous_block_hash(std::move(duplicate)); - REQUIRE(expected == instance.previous_block_hash()); -} - TEST_CASE("chain header merkle accessor 1 always returns initialized value", "[chain header]") { auto const value = "fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe"_hash; chain::header instance( @@ -241,26 +371,6 @@ TEST_CASE("chain header merkle accessor 2 always returns initialized value", REQUIRE(value == instance.merkle()); } -TEST_CASE("chain header merkle setter 1 roundtrip success", "[chain header]") { - auto const expected = "abababababababababababababababababababababababababababababababab"_hash; - chain::header instance; - REQUIRE(expected != instance.merkle()); - instance.set_merkle(expected); - REQUIRE(expected == instance.merkle()); -} - -TEST_CASE("chain header merkle setter 2 roundtrip success", "[chain header]") { - auto const expected = "abababababababababababababababababababababababababababababababab"_hash; - - // This must be non-const. - hash_digest duplicate = expected; - - chain::header instance; - REQUIRE(expected != instance.merkle()); - instance.set_merkle(std::move(duplicate)); - REQUIRE(expected == instance.merkle()); -} - TEST_CASE("chain header timestamp accessor always returns initialized value", "[chain header]") { uint32_t value = 753234u; chain::header instance( @@ -274,14 +384,6 @@ TEST_CASE("chain header timestamp accessor always returns initialized value", REQUIRE(value == instance.timestamp()); } -TEST_CASE("chain header timestamp setter roundtrip success", "[chain header]") { - uint32_t expected = 4521u; - chain::header instance; - REQUIRE(expected != instance.timestamp()); - instance.set_timestamp(expected); - REQUIRE(expected == instance.timestamp()); -} - TEST_CASE("chain header bits accessor always returns initialized value", "[chain header]") { uint32_t value = 4356344u; chain::header instance( @@ -295,14 +397,6 @@ TEST_CASE("chain header bits accessor always returns initialized value", "[ch REQUIRE(value == instance.bits()); } -TEST_CASE("chain header bits setter roundtrip success", "[chain header]") { - uint32_t expected = 4521u; - chain::header instance; - REQUIRE(expected != instance.bits()); - instance.set_bits(expected); - REQUIRE(expected == instance.bits()); -} - TEST_CASE("chain header nonce accessor always returns initialized value", "[chain header]") { uint32_t value = 34564u; chain::header instance( @@ -316,45 +410,26 @@ TEST_CASE("chain header nonce accessor always returns initialized value", "[c REQUIRE(value == instance.nonce()); } -TEST_CASE("chain header nonce setter roundtrip success", "[chain header]") { - uint32_t expected = 4521u; - chain::header instance; - REQUIRE(expected != instance.nonce()); - instance.set_nonce(expected); - REQUIRE(expected == instance.nonce()); -} - -TEST_CASE("chain header is valid timestamp timestamp less than 2 hours from now returns true", "[chain header]") { - chain::header instance; - auto const now = std::chrono::system_clock::now(); - auto const now_time = std::chrono::system_clock::to_time_t(now); - instance.set_timestamp(static_cast(now_time)); - REQUIRE(instance.is_valid_timestamp()); -} - -TEST_CASE("chain header is valid timestamp timestamp greater than 2 hours from now returns false", "[chain header]") { - chain::header instance; - auto const now = std::chrono::system_clock::now(); - auto const duration = std::chrono::hours(3); - auto const future = std::chrono::system_clock::to_time_t(now + duration); - instance.set_timestamp(static_cast(future)); - REQUIRE( ! instance.is_valid_timestamp()); -} - TEST_CASE("chain header proof1 genesis mainnet expected", "[chain header]") { REQUIRE(chain::header::proof(0x1d00ffff) == 0x0000000100010001); } TEST_CASE("chain header is valid proof of work bits exceeds maximum returns false", "[chain header]") { - chain::header instance; - instance.set_bits(retarget_proof_of_work_limit + 1); - REQUIRE( ! instance.is_valid_proof_of_work()); + chain::header const instance{ + 1u, null_hash, null_hash, 0u, + retarget_proof_of_work_limit + 1, 0u + }; + auto const hdr_hash = hash(instance); + REQUIRE( ! instance.is_valid_proof_of_work(hdr_hash)); } TEST_CASE("chain header is valid proof of work retarget bits exceeds maximum returns false", "[chain header]") { - chain::header instance; - instance.set_bits(no_retarget_proof_of_work_limit + 1); - REQUIRE( ! instance.is_valid_proof_of_work(false)); + chain::header const instance{ + 1u, null_hash, null_hash, 0u, + no_retarget_proof_of_work_limit + 1, 0u + }; + auto const hdr_hash = hash(instance); + REQUIRE( ! instance.is_valid_proof_of_work(hdr_hash, false)); } TEST_CASE("chain header is valid proof of work hash greater bits returns false", "[chain header]") { @@ -366,7 +441,8 @@ TEST_CASE("chain header is valid proof of work hash greater bits returns fals 0u, 34564u); - REQUIRE( ! instance.is_valid_proof_of_work()); + auto const hdr_hash = hash(instance); + REQUIRE( ! instance.is_valid_proof_of_work(hdr_hash)); } TEST_CASE("chain header is valid proof of work hash less than bits returns true", "[chain header]") { @@ -378,7 +454,8 @@ TEST_CASE("chain header is valid proof of work hash less than bits returns tr 402972254u, 2842832236u); - REQUIRE(instance.is_valid_proof_of_work()); + auto const hdr_hash = hash(instance); + REQUIRE(instance.is_valid_proof_of_work(hdr_hash)); } TEST_CASE("chain header operator assign equals always matches equivalent", "[chain header]") { diff --git a/src/domain/test/chain/header_benchmark_comparison.md b/src/domain/test/chain/header_benchmark_comparison.md new file mode 100644 index 00000000..15ce3dc7 --- /dev/null +++ b/src/domain/test/chain/header_benchmark_comparison.md @@ -0,0 +1,99 @@ +# Header Implementation Benchmark Comparison + +Generated: 2025-12-10 00:01:32 + +## Memory Layout + +| Property | header_raw | header_members | Difference | +|----------|------------|----------------|------------| +| sizeof(header) | 80 bytes | 80 bytes | same | +| alignof(header) | 1 bytes | 4 bytes | +3 bytes | + +## Performance Comparison + +Lower ns/op is better. The 'Winner' column shows which implementation is faster. + +### Construction + +| Benchmark | header_raw (ns/op) | header_members (ns/op) | Difference | Winner | +|-----------|-------------------|----------------------|------------|--------| +| Construction from Fields | 2.00 ±3.9% | 2.21 ±7.2% | +10.5% slower | **raw** | +| Construction from Raw Bytes | 1.52 ±3.2% | N/A | N/A | raw only | +| Default Construction | 0.56 ±0.8% | 0.57 ±2.2% | +1.8% slower | **raw** | + +### Field Access + +| Benchmark | header_raw (ns/op) | header_members (ns/op) | Difference | Winner | +|-----------|-------------------|----------------------|------------|--------| +| Access: version | 0.56 ±0.6% | 0.56 ±1.6% | ~same | tie | +| Access: timestamp | 0.59 ±0.4% | 0.59 ±0.3% | ~same | tie | +| Access: bits | 0.59 ±0.2% | 0.59 ±0.4% | ~same | tie | +| Access: nonce | 0.59 ±0.6% | 0.59 ±0.2% | ~same | tie | +| Access: previous_block_hash | 0.85 ±1.6% | 0.84 ±0.4% | 1.2% faster | **members** | +| Access: merkle | 0.83 ±6.8% | 0.84 ±0.2% | +1.2% slower | **raw** | +| Access: ALL fields | 1.84 ±2.5% | 1.11 ±0.2% | 39.7% faster | **members** | + +### Serialization + +| Benchmark | header_raw (ns/op) | header_members (ns/op) | Difference | Winner | +|-----------|-------------------|----------------------|------------|--------| +| Serialization: to_data(wire=true) | 59.47 ±3.1% | 59.93 ±0.4% | ~same | tie | +| Raw Data Access (zero-copy) | 0.54 ±2.3% | N/A | N/A | raw only | + +### Deserialization + +| Benchmark | header_raw (ns/op) | header_members (ns/op) | Difference | Winner | +|-----------|-------------------|----------------------|------------|--------| +| Deserialization: from_data(wire=true) | 3.96 ±0.8% | 4.62 ±0.3% | +16.7% slower | **raw** | + +### Hashing + +| Benchmark | header_raw (ns/op) | header_members (ns/op) | Difference | Winner | +|-----------|-------------------|----------------------|------------|--------| +| Hash Computation: chain::hash() | 728.32 ±1.1% | 714.20 ±0.5% | 1.9% faster | **members** | + +### Copy/Move + +| Benchmark | header_raw (ns/op) | header_members (ns/op) | Difference | Winner | +|-----------|-------------------|----------------------|------------|--------| +| Copy Construction | 1.39 ±5.0% | 2.08 ±0.6% | +49.6% slower | **raw** | +| Move Construction | 2.33 ±1.2% | 2.44 ±2.7% | +4.7% slower | **raw** | + +### Comparison + +| Benchmark | header_raw (ns/op) | header_members (ns/op) | Difference | Winner | +|-----------|-------------------|----------------------|------------|--------| +| Equality Comparison (==) | 1.70 ±1.8% | 1.07 ±1.2% | 37.1% faster | **members** | + +### Batch Operations + +| Benchmark | header_raw (ns/op) | header_members (ns/op) | Difference | Winner | +|-----------|-------------------|----------------------|------------|--------| +| Batch Construction (10000 headers) | 19320.63 ±3.0% | 19300.70 ±0.7% | ~same | tie | +| Batch Construction from Raw (10000 headers) | 13321.63 ±0.6% | N/A | N/A | raw only | +| Batch Hashing (10000 headers) | 7221902.67 ±2.6% | 7271025.00 ±2.0% | ~same | tie | + +### Realistic Workload + +| Benchmark | header_raw (ns/op) | header_members (ns/op) | Difference | Winner | +|-----------|-------------------|----------------------|------------|--------| +| IBD Simulation: construct + access + hash (2000 headers) | 1449350.00 ±0.4% | 1472833.33 ±0.8% | +1.6% slower | **raw** | + +## Summary + +- **header_raw wins**: 7 benchmarks +- **header_members wins**: 4 benchmarks +- **Ties** (within 1%): 7 benchmarks + +### Key Observations + +- `header_raw` (80 bytes): Array-based storage with zero-copy raw data access +- `header_members` (80 bytes): Traditional member-based storage + +**header_raw advantages:** +- Zero-copy raw data access for serialization +- Better cache locality for sequential header processing + +**header_members advantages:** +- More intuitive code structure +- Direct field access without byte extraction diff --git a/src/domain/test/chain/header_benchmarks.cpp b/src/domain/test/chain/header_benchmarks.cpp new file mode 100644 index 00000000..401f2454 --- /dev/null +++ b/src/domain/test/chain/header_benchmarks.cpp @@ -0,0 +1,479 @@ +// Copyright (c) 2016-2025 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#define ANKERL_NANOBENCH_IMPLEMENT +#include + +#include +#include + +#include +#include + +using namespace kth; +using namespace kth::domain::chain; +using ankerl::nanobench::Bench; + +// Common benchmark configuration for stability +inline Bench stable_bench() { + return Bench() + .warmup(100) // Warmup iterations to stabilize CPU/caches + .epochs(20) // More epochs for better statistics + .epochIterations(0) // Let nanobench determine optimal iterations + .relative(false); +} + +#if defined(KTH_USE_HEADER_MEMBERS) + constexpr char const* implementation_name = "header_members (member-based)"; +#else + constexpr char const* implementation_name = "header_raw (array-based)"; +#endif + +// Test data generation +struct test_header_data { + uint32_t version; + hash_digest previous_block_hash; + hash_digest merkle; + uint32_t timestamp; + uint32_t bits; + uint32_t nonce; +}; + +std::vector generate_test_headers(size_t count) { + std::mt19937 rng(42); // Fixed seed for reproducibility + std::uniform_int_distribution dist32; + std::uniform_int_distribution dist8; + + std::vector headers; + headers.reserve(count); + + for (size_t i = 0; i < count; ++i) { + test_header_data data; + data.version = dist32(rng); + data.timestamp = dist32(rng); + data.bits = dist32(rng); + data.nonce = dist32(rng); + + for (auto& byte : data.previous_block_hash) { + byte = dist8(rng); + } + for (auto& byte : data.merkle) { + byte = dist8(rng); + } + + headers.push_back(data); + } + + return headers; +} + +// Generate raw 80-byte header data +std::vector> generate_raw_headers(size_t count) { + std::mt19937 rng(42); + std::uniform_int_distribution dist8; + + std::vector> raw_headers; + raw_headers.reserve(count); + + for (size_t i = 0; i < count; ++i) { + std::array raw{}; + for (auto& byte : raw) { + byte = dist8(rng); + } + raw_headers.push_back(raw); + } + + return raw_headers; +} + +void benchmark_construction() { + fmt::print("\n========== CONSTRUCTION BENCHMARKS ==========\n"); + + auto test_data = generate_test_headers(10000); + size_t idx = 0; + + // Construction from individual fields + stable_bench().title("Construction from Fields").minEpochIterations(10000) + .run(implementation_name, [&] { + auto const& d = test_data[idx % test_data.size()]; + header h{d.version, d.previous_block_hash, d.merkle, d.timestamp, d.bits, d.nonce}; + ankerl::nanobench::doNotOptimizeAway(h); + ++idx; + }); + +#if ! defined(KTH_USE_HEADER_MEMBERS) + // Construction from raw bytes (array-based only) + auto raw_data = generate_raw_headers(10000); + idx = 0; + + stable_bench().title("Construction from Raw Bytes").minEpochIterations(10000) + .run("header (from 80-byte array)", [&] { + auto const& raw = raw_data[idx % raw_data.size()]; + header h{raw}; + ankerl::nanobench::doNotOptimizeAway(h); + ++idx; + }); +#endif + + // Default construction + stable_bench().title("Default Construction").minEpochIterations(100000) + .run(implementation_name, [&] { + header h{}; + ankerl::nanobench::doNotOptimizeAway(h); + }); +} + +void benchmark_field_access() { + fmt::print("\n========== FIELD ACCESS BENCHMARKS ==========\n"); + + auto test_data = generate_test_headers(1000); + + // Pre-construct headers + std::vector
headers; + headers.reserve(test_data.size()); + + for (auto const& d : test_data) { + headers.emplace_back(d.version, d.previous_block_hash, d.merkle, d.timestamp, d.bits, d.nonce); + } + + size_t idx = 0; + + // Version access + stable_bench().title("Access: version").minEpochIterations(100000) + .run(implementation_name, [&] { + auto v = headers[idx % headers.size()].version(); + ankerl::nanobench::doNotOptimizeAway(v); + ++idx; + }); + + idx = 0; + + // Timestamp access + stable_bench().title("Access: timestamp").minEpochIterations(100000) + .run(implementation_name, [&] { + auto v = headers[idx % headers.size()].timestamp(); + ankerl::nanobench::doNotOptimizeAway(v); + ++idx; + }); + + idx = 0; + + // Bits access + stable_bench().title("Access: bits").minEpochIterations(100000) + .run(implementation_name, [&] { + auto v = headers[idx % headers.size()].bits(); + ankerl::nanobench::doNotOptimizeAway(v); + ++idx; + }); + + idx = 0; + + // Nonce access + stable_bench().title("Access: nonce").minEpochIterations(100000) + .run(implementation_name, [&] { + auto v = headers[idx % headers.size()].nonce(); + ankerl::nanobench::doNotOptimizeAway(v); + ++idx; + }); + + idx = 0; + + // Previous block hash access (32 bytes) + stable_bench().title("Access: previous_block_hash").minEpochIterations(100000) + .run(implementation_name, [&] { + auto v = headers[idx % headers.size()].previous_block_hash(); + ankerl::nanobench::doNotOptimizeAway(v); + ++idx; + }); + + idx = 0; + + // Merkle root access (32 bytes) + stable_bench().title("Access: merkle").minEpochIterations(100000) + .run(implementation_name, [&] { + auto v = headers[idx % headers.size()].merkle(); + ankerl::nanobench::doNotOptimizeAway(v); + ++idx; + }); + + idx = 0; + + // All fields access (simulating typical usage) + stable_bench().title("Access: ALL fields").minEpochIterations(50000) + .run(implementation_name, [&] { + auto const& h = headers[idx % headers.size()]; + ankerl::nanobench::doNotOptimizeAway(h.version()); + ankerl::nanobench::doNotOptimizeAway(h.previous_block_hash()); + ankerl::nanobench::doNotOptimizeAway(h.merkle()); + ankerl::nanobench::doNotOptimizeAway(h.timestamp()); + ankerl::nanobench::doNotOptimizeAway(h.bits()); + ankerl::nanobench::doNotOptimizeAway(h.nonce()); + ++idx; + }); +} + +void benchmark_serialization() { + fmt::print("\n========== SERIALIZATION BENCHMARKS ==========\n"); + + auto test_data = generate_test_headers(1000); + + // Pre-construct headers + std::vector
headers; + + for (auto const& d : test_data) { + headers.emplace_back(d.version, d.previous_block_hash, d.merkle, d.timestamp, d.bits, d.nonce); + } + + size_t idx = 0; + + // Serialization (to_data) + stable_bench().title("Serialization: to_data(wire=true)").minEpochIterations(10000) + .run(implementation_name, [&] { + auto data = headers[idx % headers.size()].to_data(true); + ankerl::nanobench::doNotOptimizeAway(data); + ++idx; + }); + +#if ! defined(KTH_USE_HEADER_MEMBERS) + idx = 0; + + // Raw data access (array-based only - zero-copy) + stable_bench().title("Raw Data Access (zero-copy)").minEpochIterations(100000) + .run("header::raw_data()", [&] { + auto span = headers[idx % headers.size()].raw_data(); + ankerl::nanobench::doNotOptimizeAway(span.data()); + ankerl::nanobench::doNotOptimizeAway(span.size()); + ++idx; + }); +#endif +} + +void benchmark_deserialization() { + fmt::print("\n========== DESERIALIZATION BENCHMARKS ==========\n"); + + // Generate serialized data + auto test_data = generate_test_headers(1000); + std::vector serialized_data; + + for (auto const& d : test_data) { + header h{d.version, d.previous_block_hash, d.merkle, d.timestamp, d.bits, d.nonce}; + serialized_data.push_back(h.to_data(true)); + } + + size_t idx = 0; + + // Deserialization (from_data) + stable_bench().title("Deserialization: from_data(wire=true)").minEpochIterations(10000) + .run(implementation_name, [&] { + auto const& data = serialized_data[idx % serialized_data.size()]; + byte_reader reader(data); + auto result = header::from_data(reader, true); + ankerl::nanobench::doNotOptimizeAway(result); + ++idx; + }); +} + +void benchmark_hashing() { + fmt::print("\n========== HASHING BENCHMARKS ==========\n"); + + auto test_data = generate_test_headers(1000); + + // Pre-construct headers + std::vector
headers; + + for (auto const& d : test_data) { + headers.emplace_back(d.version, d.previous_block_hash, d.merkle, d.timestamp, d.bits, d.nonce); + } + + size_t idx = 0; + + // Hash computation + stable_bench().title("Hash Computation: chain::hash()").minEpochIterations(10000) + .run(implementation_name, [&] { + auto h = hash(headers[idx % headers.size()]); + ankerl::nanobench::doNotOptimizeAway(h); + ++idx; + }); +} + +void benchmark_copy_move() { + fmt::print("\n========== COPY/MOVE BENCHMARKS ==========\n"); + + auto test_data = generate_test_headers(1000); + + // Pre-construct headers + std::vector
headers; + + for (auto const& d : test_data) { + headers.emplace_back(d.version, d.previous_block_hash, d.merkle, d.timestamp, d.bits, d.nonce); + } + + size_t idx = 0; + + // Copy construction + stable_bench().title("Copy Construction").minEpochIterations(50000) + .run(implementation_name, [&] { + header copy{headers[idx % headers.size()]}; + ankerl::nanobench::doNotOptimizeAway(copy); + ++idx; + }); + + idx = 0; + + // Move construction + stable_bench().title("Move Construction").minEpochIterations(50000) + .run(implementation_name, [&] { + header src{headers[idx % headers.size()]}; + header moved{std::move(src)}; + ankerl::nanobench::doNotOptimizeAway(moved); + ++idx; + }); +} + +void benchmark_comparison() { + fmt::print("\n========== COMPARISON BENCHMARKS ==========\n"); + + auto test_data = generate_test_headers(1000); + + // Pre-construct headers + std::vector
headers; + + for (auto const& d : test_data) { + headers.emplace_back(d.version, d.previous_block_hash, d.merkle, d.timestamp, d.bits, d.nonce); + } + + size_t idx = 0; + + // Equality comparison + stable_bench().title("Equality Comparison (==)").minEpochIterations(50000) + .run(implementation_name, [&] { + auto const& a = headers[idx % headers.size()]; + auto const& b = headers[(idx + 1) % headers.size()]; + ankerl::nanobench::doNotOptimizeAway(a == b); + ++idx; + }); +} + +void benchmark_memory_layout() { + fmt::print("\n========== MEMORY LAYOUT INFO ==========\n"); + + fmt::print("Implementation: {}\n", implementation_name); + fmt::print("sizeof(header): {} bytes\n", sizeof(header)); + fmt::print("alignof(header): {} bytes\n", alignof(header)); + fmt::print("sizeof(hash_digest): {} bytes\n", sizeof(hash_digest)); + fmt::print("\n"); +} + +void benchmark_batch_operations() { + fmt::print("\n========== BATCH OPERATION BENCHMARKS ==========\n"); + + auto test_data = generate_test_headers(10000); + + // Batch construction + stable_bench().title("Batch Construction (10000 headers)").minEpochIterations(10) + .run(implementation_name, [&] { + std::vector
headers; + headers.reserve(test_data.size()); + for (auto const& d : test_data) { + headers.emplace_back(d.version, d.previous_block_hash, d.merkle, d.timestamp, d.bits, d.nonce); + } + ankerl::nanobench::doNotOptimizeAway(headers); + }); + +#if ! defined(KTH_USE_HEADER_MEMBERS) + auto raw_data = generate_raw_headers(10000); + + stable_bench().title("Batch Construction from Raw (10000 headers)").minEpochIterations(10) + .run("header (from raw)", [&] { + std::vector
headers; + headers.reserve(raw_data.size()); + for (auto const& raw : raw_data) { + headers.emplace_back(raw); + } + ankerl::nanobench::doNotOptimizeAway(headers); + }); +#endif + + // Pre-construct for batch hash + std::vector
headers; + + for (auto const& d : test_data) { + headers.emplace_back(d.version, d.previous_block_hash, d.merkle, d.timestamp, d.bits, d.nonce); + } + + // Batch hashing + stable_bench().title("Batch Hashing (10000 headers)").minEpochIterations(5) + .run(implementation_name, [&] { + hash_list hashes; + hashes.reserve(headers.size()); + for (auto const& h : headers) { + hashes.push_back(hash(h)); + } + ankerl::nanobench::doNotOptimizeAway(hashes); + }); +} + +void benchmark_realistic_ibd() { + fmt::print("\n========== REALISTIC IBD SIMULATION ==========\n"); + + // Simulate receiving 2000 headers (typical batch during IBD) + auto test_data = generate_test_headers(2000); + + // Simulate: deserialize -> access fields -> hash -> store + stable_bench().title("IBD Simulation: construct + access + hash (2000 headers)").minEpochIterations(5) + .run(implementation_name, [&] { + std::vector
headers; + hash_list hashes; + headers.reserve(2000); + hashes.reserve(2000); + + for (auto const& d : test_data) { + // Construct header + header h{d.version, d.previous_block_hash, d.merkle, d.timestamp, d.bits, d.nonce}; + + // Access fields (typical validation) + auto v = h.version(); + auto t = h.timestamp(); + auto b = h.bits(); + ankerl::nanobench::doNotOptimizeAway(v); + ankerl::nanobench::doNotOptimizeAway(t); + ankerl::nanobench::doNotOptimizeAway(b); + + // Compute hash + auto h_hash = hash(h); + hashes.push_back(h_hash); + + // Store header + headers.push_back(std::move(h)); + } + ankerl::nanobench::doNotOptimizeAway(headers); + ankerl::nanobench::doNotOptimizeAway(hashes); + }); +} + +int main() { + fmt::print("==============================================\n"); + fmt::print(" Header Implementation Benchmarks\n"); + fmt::print(" Implementation: {}\n", implementation_name); + fmt::print(" Using nanobench\n"); + fmt::print("==============================================\n"); + + benchmark_memory_layout(); + benchmark_construction(); + benchmark_field_access(); + benchmark_serialization(); + benchmark_deserialization(); + benchmark_hashing(); + benchmark_copy_move(); + benchmark_comparison(); + benchmark_batch_operations(); + benchmark_realistic_ibd(); + + fmt::print("\n==============================================\n"); + fmt::print(" Benchmarks Complete!\n"); + fmt::print("==============================================\n"); + + return 0; +} + diff --git a/src/domain/test/message/headers.cpp b/src/domain/test/message/headers.cpp index d82ffe41..e04748c5 100644 --- a/src/domain/test/message/headers.cpp +++ b/src/domain/test/message/headers.cpp @@ -514,7 +514,7 @@ TEST_CASE("headers is sequential sequential true", "[headers]") { static header const second{ 2u, - first.hash(), + chain::hash(first), "babababababababababababababababababababababababababababababababa"_hash, 20u, 200u, @@ -522,7 +522,7 @@ TEST_CASE("headers is sequential sequential true", "[headers]") { static header const third{ 3u, - second.hash(), + chain::hash(second), "7373737373737373737373737373737373737373737373737373737373737373"_hash, 30u, 300u, diff --git a/src/node/include/kth/node/utility/reservations.hpp b/src/node/include/kth/node/utility/reservations.hpp index 49279d1f..57bc697f 100644 --- a/src/node/include/kth/node/utility/reservations.hpp +++ b/src/node/include/kth/node/utility/reservations.hpp @@ -46,7 +46,7 @@ struct KND_API reservations { #if ! defined(KTH_DB_READONLY) /// Import the given block to the blockchain at the specified height. - bool import(block_const_ptr block, size_t height); + bool import(block_const_ptr block, size_t height, uint32_t median_time_past); #endif /// Populate a starved row by taking half of the hashes from a weak row. diff --git a/src/node/src/full_node.cpp b/src/node/src/full_node.cpp index fe7e374f..2c8689ff 100644 --- a/src/node/src/full_node.cpp +++ b/src/node/src/full_node.cpp @@ -281,7 +281,7 @@ bool full_node::handle_reorganized(code ec, size_t fork_height, block_const_ptr_ } for (auto const block: *outgoing) { - spdlog::debug("[node] Reorganization moved block to orphan pool [{}]", encode_hash(block->header().hash())); + spdlog::debug("[node] Reorganization moved block to orphan pool [{}]", encode_hash(chain::hash(block->header()))); } auto const height = *safe_add(fork_height, incoming->size()); diff --git a/src/node/src/protocols/protocol_block_in.cpp b/src/node/src/protocols/protocol_block_in.cpp index b31ce782..44b2d39d 100644 --- a/src/node/src/protocols/protocol_block_in.cpp +++ b/src/node/src/protocols/protocol_block_in.cpp @@ -434,14 +434,16 @@ bool protocol_block_in::handle_receive_compact_block(code const& ec, compact_blo //the header of the compact block is the header of the block auto const& header_temp = message->header(); + auto const header_hash = chain::hash(header_temp); + if ( ! header_temp.is_valid()) { - spdlog::debug("[node] Compact Block [{}] The compact block header is invalid [{}]", encode_hash(header_temp.hash()), authority()); + spdlog::debug("[node] Compact Block [{}] The compact block header is invalid [{}]", encode_hash(header_hash), authority()); stop(error::channel_stopped); return false; } //if the compact block exists in the map, is already in process - if (compact_blocks_map_.count(header_temp.hash()) > 0) { + if (compact_blocks_map_.count(header_hash) > 0) { return true; } @@ -474,7 +476,7 @@ bool protocol_block_in::handle_receive_compact_block(code const& ec, compact_blo for (size_t i = 0; i < prefiled_txs.size(); ++i) { if ( ! prefiled_txs[i].is_valid()) { - spdlog::debug("[node] Compact Block [{}] The prefilled transaction is invalid [{}]", encode_hash(header_temp.hash()), authority()); + spdlog::debug("[node] Compact Block [{}] The prefilled transaction is invalid [{}]", encode_hash(header_hash), authority()); stop(error::channel_stopped); return false; } @@ -486,7 +488,7 @@ bool protocol_block_in::handle_receive_compact_block(code const& ec, compact_blo if (lastprefilledindex > std::numeric_limits::max()) { - spdlog::debug("[node] Compact Block [{}] The prefilled index {} is out of range [{}]", encode_hash(header_temp.hash()), lastprefilledindex, authority()); + spdlog::debug("[node] Compact Block [{}] The prefilled index {} is out of range [{}]", encode_hash(header_hash), lastprefilledindex, authority()); stop(error::channel_stopped); return false; } @@ -497,7 +499,7 @@ bool protocol_block_in::handle_receive_compact_block(code const& ec, compact_blo // then we have txn for which we have neither a prefilled txn or a // shorttxid! - spdlog::debug("[node] Compact Block [{}] The prefilled index {} is out of range [{}]", encode_hash(header_temp.hash()), lastprefilledindex, authority()); + spdlog::debug("[node] Compact Block [{}] The prefilled index {} is out of range [{}]", encode_hash(header_hash), lastprefilledindex, authority()); stop(error::channel_stopped); return false; } @@ -534,8 +536,8 @@ bool protocol_block_in::handle_receive_compact_block(code const& ec, compact_blo // Duplicate txindexes, the block is now in-flight, so // just request it. - spdlog::info("[node] Compact Block, sendening getdata for hash ({}) to [{}]", encode_hash(header_temp.hash()), authority()); - send_get_data_compact_block(ec, header_temp.hash()); + spdlog::info("[node] Compact Block, sendening getdata for hash ({}) to [{}]", encode_hash(header_hash), authority()); + send_get_data_compact_block(ec, header_hash); return true; } @@ -547,8 +549,8 @@ bool protocol_block_in::handle_receive_compact_block(code const& ec, compact_blo // overkill. if (shorttxids.size() != short_ids.size()) { // Short ID collision - spdlog::info("[node] Compact Block, sendening getdata for hash ({}) to [{}]", encode_hash(header_temp.hash()), authority()); - send_get_data_compact_block(ec, header_temp.hash()); + spdlog::info("[node] Compact Block, sendening getdata for hash ({}) to [{}]", encode_hash(header_hash), authority()); + send_get_data_compact_block(ec, header_hash); return true; } @@ -572,8 +574,8 @@ bool protocol_block_in::handle_receive_compact_block(code const& ec, compact_blo organize_block(tempblock); return true; } else { - compact_blocks_map_.emplace(header_temp.hash(), temp_compact_block{std::move(header_temp), std::move(txs_available)}); - auto req_tx = get_block_transactions(header_temp.hash(),txs); + compact_blocks_map_.emplace(header_hash, temp_compact_block{std::move(header_temp), std::move(txs_available)}); + auto req_tx = get_block_transactions(header_hash, txs); SEND2(req_tx, handle_send, _1, get_block_transactions::command); return true; } @@ -597,7 +599,7 @@ void protocol_block_in::handle_store_block(code const& ec, block_const_ptr messa return; } - auto const hash = message->header().hash(); + auto const hash = chain::hash(message->header()); // Ask the peer for blocks from the chain top up to this orphan. if (ec == error::orphan_block) { diff --git a/src/node/src/protocols/protocol_block_out.cpp b/src/node/src/protocols/protocol_block_out.cpp index 5091204e..3d13e3e0 100644 --- a/src/node/src/protocols/protocol_block_out.cpp +++ b/src/node/src/protocols/protocol_block_out.cpp @@ -160,7 +160,7 @@ void protocol_block_out::handle_fetch_locator_headers(code const& ec, headers_pt //// return; // Save the locator top to limit an overlapping future request. - last_locator_top_.store(message->elements().front().hash()); + last_locator_top_.store(chain::hash(message->elements().front())); } bool protocol_block_out::handle_receive_get_block_transactions(code const& ec, get_block_transactions_const_ptr message) { @@ -489,7 +489,7 @@ bool protocol_block_out::handle_reorganized(code ec, size_t fork_height, block_c for (auto const block: *incoming) { if (block->validation.originator != nonce()) { - announce.inventories().push_back({ inventory::type_id::block, block->header().hash() }); + announce.inventories().push_back({ inventory::type_id::block, chain::hash(block->header()) }); } } diff --git a/src/node/src/sessions/session_header_sync.cpp b/src/node/src/sessions/session_header_sync.cpp index 02e8e244..8ad2eeb9 100644 --- a/src/node/src/sessions/session_header_sync.cpp +++ b/src/node/src/sessions/session_header_sync.cpp @@ -172,7 +172,7 @@ void session_header_sync::handle_complete(code const& ec, header_list::ptr row, // Store the hash if there is a gap reservation. for (auto const& header: headers) { - hashes_.enqueue(header.hash(), height++); + hashes_.enqueue(chain::hash(header), height++); } spdlog::debug("[node] Completed header slot ({})", row->slot()); diff --git a/src/node/src/utility/header_list.cpp b/src/node/src/utility/header_list.cpp index 05f1c7f0..da0bd584 100644 --- a/src/node/src/utility/header_list.cpp +++ b/src/node/src/utility/header_list.cpp @@ -47,7 +47,7 @@ hash_digest header_list::previous_hash() const { // Critical Section. shared_lock lock(mutex_); - return list_.empty() ? start_.hash() : list_.back().hash(); + return list_.empty() ? start_.hash() : chain::hash(list_.back()); /////////////////////////////////////////////////////////////////////////// } @@ -130,7 +130,7 @@ bool header_list::link(const domain::chain::header& header) const { } // Avoid copying and temporary reference assignment. - return header.previous_block_hash() == list_.back().hash(); + return header.previous_block_hash() == chain::hash(list_.back()); } bool header_list::check(header const& header) const { @@ -147,7 +147,7 @@ bool header_list::accept(header const& header) const { ////return !header.accept(...); // Verify last checkpoint. - return remaining() > 1 || header.hash() == stop_.hash(); + return remaining() > 1 || chain::hash(header) == stop_.hash(); } } // namespace kth::node diff --git a/src/node/src/utility/reservation.cpp b/src/node/src/utility/reservation.cpp index 1852f578..c7e69d60 100644 --- a/src/node/src/utility/reservation.cpp +++ b/src/node/src/utility/reservation.cpp @@ -271,7 +271,7 @@ void reservation::insert(hash_digest&& hash, size_t height) { #if ! defined(KTH_DB_READONLY) void reservation::import(block_const_ptr block) { size_t height; - auto const hash = block->header().hash(); + auto const hash = chain::hash(block->header()); auto const encoded = encode_hash(hash); if ( ! find_height_and_erase(hash, height)) { @@ -281,7 +281,8 @@ void reservation::import(block_const_ptr block) { bool success; auto const importer = [this, &block, &height, &success]() { - success = reservations_.import(block, height); + // TODO: calculate proper MTP from the chain. For now using 0 as placeholder. + success = reservations_.import(block, height, 0); }; // Do the block import with timer. diff --git a/src/node/src/utility/reservations.cpp b/src/node/src/utility/reservations.cpp index d3deaf57..97b45a25 100644 --- a/src/node/src/utility/reservations.cpp +++ b/src/node/src/utility/reservations.cpp @@ -36,9 +36,9 @@ bool reservations::start() { } #if ! defined(KTH_DB_READONLY) -bool reservations::import(block_const_ptr block, size_t height) { +bool reservations::import(block_const_ptr block, size_t height, uint32_t median_time_past) { //######################################################################### - return chain_.insert(block, height); + return chain_.insert(block, height, median_time_past); //######################################################################### } #endif //! defined(KTH_DB_READONLY) diff --git a/src/node/test/utility.cpp b/src/node/test/utility.cpp index 8f4aa87f..58a05e83 100644 --- a/src/node/test/utility.cpp +++ b/src/node/test/utility.cpp @@ -42,7 +42,7 @@ domain::message::headers::ptr message_factory(size_t count, for (size_t height = 0; height < count; ++height) { header const current_header{ 0, previous_hash, {}, 0, 0, 0 }; elements.push_back(current_header); - previous_hash = current_header.hash(); + previous_hash = chain::hash(current_header); } return headers;