Skip to content

Commit

Permalink
Merge pull request #427 from evoskuil/master
Browse files Browse the repository at this point in the history
Factor out block.set_hashes.
  • Loading branch information
evoskuil authored Aug 14, 2024
2 parents d134b1e + 2843e4f commit 703a6e2
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 47 deletions.
6 changes: 5 additions & 1 deletion include/bitcoin/network/messages/block.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion include/bitcoin/network/net/distributor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ class BCT_API distributor
{
// Subscribers are notified only with stop code or error::success.
const auto ptr = messages::deserialize<Message>(data, version);
if (!ptr) return error::invalid_message;

if (!ptr)
return error::invalid_message;

subscriber.notify(error::success, ptr);
}

Expand Down Expand Up @@ -196,6 +199,7 @@ class BCT_API distributor
memory& memory_;
};

// block message uses specialized deserializer for memory management.
template <>
code distributor::do_notify<messages::block>(
distributor::block_subscriber& subscriber, uint32_t version,
Expand Down
141 changes: 98 additions & 43 deletions src/messages/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<uint8_t>(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<uint8_t>(arena->allocate(zero));
const auto allocated = limit<size_t>(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<uint8_t>(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<chain::block>(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<uint8_t>(arena.allocate(zero));
const auto allocation = std::distance(begin, end);
const auto size = possible_narrow_sign_cast<size_t>(allocation);
message->block_ptr->set_allocation(size);

// Invokes destruct and deallocate (which may not need to free memory).
////return std::make_shared<block>(std::shared_ptr<chain::block>(raw,
//// allocator.deleter<chain::block>()));
return message;

// Allocate memory for copy of block_ptr and its block, and define deleter.
////const auto copy = pointer_cast<chain::block>(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<chain::block>()(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<block>(std::shared_ptr<chain::block>(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<block>(std::shared_ptr<chain::block>(copy, wiper));
}

// static
Expand Down Expand Up @@ -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
10 changes: 8 additions & 2 deletions src/net/distributor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,14 @@ code distributor::do_notify<messages::block>(
{
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);
}

Expand Down
1 change: 1 addition & 0 deletions src/net/proxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down

0 comments on commit 703a6e2

Please sign in to comment.