diff --git a/include/bitcoin/network/messages/block.hpp b/include/bitcoin/network/messages/block.hpp index e12f5c15c..23c3e7a3b 100644 --- a/include/bitcoin/network/messages/block.hpp +++ b/include/bitcoin/network/messages/block.hpp @@ -39,7 +39,11 @@ struct BCT_API block static const uint32_t version_minimum; static const uint32_t version_maximum; - static cptr deserialize(memory& memory, uint32_t version, + /// Populate header and tx hashes onto the block. + static void set_hashes(const system::chain::block& block, + const system::data_chunk& data) NOEXCEPT; + + static cptr deserialize(arena& arena, uint32_t version, const system::data_chunk& data, bool witness=true) NOEXCEPT; static cptr deserialize(uint32_t version, const system::data_chunk& data, bool witness=true) NOEXCEPT; diff --git a/include/bitcoin/network/net/distributor.hpp b/include/bitcoin/network/net/distributor.hpp index 2d2fde99b..0b746ece8 100644 --- a/include/bitcoin/network/net/distributor.hpp +++ b/include/bitcoin/network/net/distributor.hpp @@ -117,7 +117,10 @@ class BCT_API distributor { // Subscribers are notified only with stop code or error::success. const auto ptr = messages::deserialize(data, version); - if (!ptr) return error::invalid_message; + + if (!ptr) + return error::invalid_message; + subscriber.notify(error::success, ptr); } @@ -196,6 +199,7 @@ class BCT_API distributor memory& memory_; }; +// block message uses specialized deserializer for memory management. template <> code distributor::do_notify( distributor::block_subscriber& subscriber, uint32_t version, diff --git a/src/messages/block.cpp b/src/messages/block.cpp index f72bdb28e..e84f8618d 100644 --- a/src/messages/block.cpp +++ b/src/messages/block.cpp @@ -33,6 +33,10 @@ namespace messages { using namespace system; +////BC_PUSH_WARNING(NO_MALLOC_OR_FREE) +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) +BC_PUSH_WARNING(NO_UNGUARDED_POINTERS) + // Measured through block 840,000 - assumes consistent platform sizing. constexpr auto maximal_block = 30'000'000_size; @@ -42,74 +46,121 @@ const uint32_t block::version_minimum = level::minimum_protocol; const uint32_t block::version_maximum = level::maximum_protocol; // static -typename block::cptr block::deserialize(uint32_t version, - const system::data_chunk& data, bool witness) NOEXCEPT +void block::set_hashes(const chain::block& block, const data_chunk& data) NOEXCEPT { - static default_memory memory{}; - return deserialize(memory, version, data, witness); -} - -// static -// TODO: Move cached hashes to arena. -// Must have at least maximal_block available to prevent block overflow. -// Since the block allocation is unknown, a maximal block is assumed. -// message, block and block_ptr are not allocated by reader's arena. -// chain::block consists only of three pointers and two integral values. -typename block::cptr block::deserialize(memory& memory, uint32_t version, - const system::data_chunk& data, bool witness) NOEXCEPT -{ - const auto arena = memory.get_arena(); - if (arena == nullptr) - return nullptr; - - istream source{ data }; - byte_reader reader{ source, arena }; - - const auto begin = pointer_cast(arena->require(maximal_block)); - const auto message = to_shared(deserialize(version, reader, witness)); - if (!reader) - return nullptr; + constexpr auto header_size = chain::header::serialized_size(); // Cache header hash. - constexpr auto header_size = chain::header::serialized_size(); - const auto& header = message->block_ptr->header(); - header.set_hash(bitcoin_hash(header_size, data.data())); + block.header().set_hash(bitcoin_hash(header_size, data.data())); - // Skip transaction count, guarded by read above. - BC_PUSH_WARNING(NO_UNGUARDED_POINTERS) + // Skip transaction count, guarded by preceding successful block construct. auto start = std::next(data.data(), header_size); std::advance(start, size_variable(*start)); - BC_POP_WARNING() // Cache transaction hashes. auto coinbase = true; - for (const auto& tx: *message->block_ptr->transactions_ptr()) + for (const auto& tx: *block.transactions_ptr()) { - const auto full = tx->serialized_size(true); + const auto witness_size = tx->serialized_size(true); // If !witness then wire txs cannot have been segregated. if (tx->is_segregated()) { - tx->set_nominal_hash(transaction::desegregated_hash(full, - tx->serialized_size(false), start)); + const auto nominal_size = tx->serialized_size(false); + + tx->set_nominal_hash(transaction::desegregated_hash( + witness_size, nominal_size, start)); if (!coinbase) - tx->set_witness_hash(bitcoin_hash(full, start)); + tx->set_witness_hash(bitcoin_hash(witness_size, start)); } else { - tx->set_nominal_hash(bitcoin_hash(full, start)); + tx->set_nominal_hash(bitcoin_hash(witness_size, start)); } coinbase = false; - std::advance(start, full); + std::advance(start, witness_size); } +} - // TODO: system::limit is not necessary if allocation begin < end. - const auto end = pointer_cast(arena->allocate(zero)); - const auto allocated = limit(std::distance(begin, end)); - message->block_ptr->set_allocation(allocated); +// static +typename block::cptr block::deserialize(uint32_t version, + const system::data_chunk& data, bool witness) NOEXCEPT +{ + static default_memory memory{}; + return deserialize(*memory.get_arena(), version, data, witness); +} + +// static +// WARNING: linear arena must allocate a maximal block and all contents. +// WARNING: chain::block::cptr destruct frees all contained shared_ptr objects. +typename block::cptr block::deserialize(arena& arena, uint32_t version, + const data_chunk& data, bool witness) NOEXCEPT +{ + ////if (version < version_minimum || version > version_maximum) + //// nullptr; + + // TODO: arena::initialize(). + // Returns current arena address (next allocation point). + // This implies a linear allocator, otherwise nullptr returned. + const auto begin = pointer_cast(arena.initialize()); + if (is_null(begin)) + return nullptr; + + istream source{ data }; + byte_reader reader{ source, &arena }; + + // block and all of its parts are allocated at the start of arena. + ////auto allocator = reader.get_allocator(); + ////const auto raw = allocator.new_object(reader, witness); + ////if (is_null(raw) || !reader) + //// return nullptr; + const auto message = to_shared(deserialize(version, reader, witness)); + if (!reader) + return nullptr; + + // Copy header and tx hashes into preallocated optionals. + set_hashes(*message->block_ptr, data); + + // Compute allocated size of raw and its block. + const auto end = pointer_cast(arena.allocate(zero)); + const auto allocation = std::distance(begin, end); + const auto size = possible_narrow_sign_cast(allocation); + message->block_ptr->set_allocation(size); + + // Invokes destruct and deallocate (which may not need to free memory). + ////return std::make_shared(std::shared_ptr(raw, + //// allocator.deleter())); return message; + + // Allocate memory for copy of block_ptr and its block, and define deleter. + ////const auto copy = pointer_cast(std::malloc(size)); + ////if (is_null(copy)) + //// return nullptr; + //// + // Copy the contiguous byte buffer that represents the block. + ////std::memcpy(copy, block_ptr, size); + //// + // Do not invoke block destructor, no free required for linear allocator. + ////allocator.deleter()(block_ptr); + ////std::free(copy); + //// + ////const auto thread = std::this_thread::get_id(); + ////const auto wiper = [thread](void* memory) NOEXCEPT + ////{ + //// if (std::this_thread::get_id() != thread) + //// { + //// std::free(memory); + //// } + ////}; + //// + // This leaks copy if destruct on same thread. + ////return std::make_shared(std::shared_ptr(copy, wiper)); + //// + // Create an owning block pointer with deallocator and assign to message. + ////const auto wiper = [](void* address) NOEXCEPT { std::free(address); }; + ////return std::make_shared(std::shared_ptr(copy, wiper)); } // static @@ -148,6 +199,10 @@ size_t block::size(uint32_t, bool witness) const NOEXCEPT return block_ptr ? block_ptr->serialized_size(witness) : zero; } +BC_POP_WARNING() +BC_POP_WARNING() +////BC_POP_WARNING() + } // namespace messages } // namespace network } // namespace libbitcoin diff --git a/src/net/distributor.cpp b/src/net/distributor.cpp index 0786d85fb..2805ecaa8 100644 --- a/src/net/distributor.cpp +++ b/src/net/distributor.cpp @@ -161,8 +161,14 @@ code distributor::do_notify( { if (!is_zero(subscriber.size())) { - const auto ptr = messages::block::deserialize(memory_, version, data); - if (!ptr) return error::invalid_message; + const auto arena = memory_.get_arena(); + if (arena == nullptr) + return error::operation_failed; + + const auto ptr = messages::block::deserialize(*arena, version, data); + if (!ptr) + return error::invalid_message; + subscriber.notify(error::success, ptr); } diff --git a/src/net/proxy.cpp b/src/net/proxy.cpp index a5e48e7f4..9e4f7af9f 100644 --- a/src/net/proxy.cpp +++ b/src/net/proxy.cpp @@ -306,6 +306,7 @@ void proxy::handle_read_payload(const code& ec, size_t LOG_ONLY(payload_size), if (head->command == messages::transaction::command || head->command == messages::block::command) { + // error::operation_failed implies null arena, not invalid payload. LOGR("Invalid " << head->command << " payload from [" << authority() << "] with hash [" << encode_hash(bitcoin_hash(payload_buffer_)) << "] " << code.message());