Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance: Store packed block in signed_block #1148

Merged
merged 7 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions libraries/chain/block_log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -661,10 +661,11 @@ namespace eosio { namespace chain {

signed_block_ptr read_block_by_num(uint32_t block_num) final {
try {
uint64_t pos = get_block_pos(block_num);
auto [ pos, size ] = get_block_position_and_size(block_num);
if (pos != block_log::npos) {
block_file.seek(pos);
return read_block(block_file, block_num);
fc::datastream_mirror ds(block_file, size);
return read_block(ds, block_num);
}
return retry_read_block_by_num(block_num);
}
Expand Down Expand Up @@ -801,7 +802,7 @@ namespace eosio { namespace chain {

void reset(const genesis_state& gs, const signed_block_ptr& first_block) override {
this->reset(1, gs, default_initial_version);
this->append(first_block, first_block->calculate_id(), fc::raw::pack(*first_block));
this->append(first_block, first_block->calculate_id(), first_block->packed_signed_block());
}

void reset(const chain_id_type& chain_id, uint32_t first_block_num) override {
Expand Down Expand Up @@ -1094,9 +1095,13 @@ namespace eosio { namespace chain {
}

signed_block_ptr retry_read_block_by_num(uint32_t block_num) final {
auto ds = catalog.ro_stream_for_block(block_num);
if (ds)
return read_block(*ds, block_num);
uint64_t block_size = 0;

auto ds = catalog.ro_stream_and_size_for_block(block_num, block_size);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not from your changes. I wish ro_stream_and_size_for_block returned ds and size explicitly.

if (ds) {
fc::datastream_mirror dsm(*ds, block_size);
return read_block(dsm, block_num);
}
return {};
}

Expand Down Expand Up @@ -1277,9 +1282,8 @@ namespace eosio { namespace chain {
}

void block_log::append(const signed_block_ptr& b, const block_id_type& id) {
std::vector<char> packed_block = fc::raw::pack(*b);
std::lock_guard g(my->mtx);
my->append(b, id, packed_block);
my->append(b, id, b->packed_signed_block());
}

void block_log::append(const signed_block_ptr& b, const block_id_type& id, const std::vector<char>& packed_block) {
Expand Down
4 changes: 2 additions & 2 deletions libraries/chain/block_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ block_state::block_state(const block_header_state& bhs,
, cached_trxs(std::move(trx_metas))
, action_mroot(action_mroot)
{
mutable_signed_block_ptr new_block = std::make_shared<signed_block>(signed_block_header{bhs.header});
mutable_block_ptr new_block = signed_block::create_mutable_block(signed_block_header{bhs.header});
new_block->transactions = std::move(trx_receipts);

if( qc ) {
Expand All @@ -125,7 +125,7 @@ block_state::block_state(const block_header_state& bhs,

sign(*new_block, block_id, signer, valid_block_signing_authority);

block = std::move(new_block);
block = signed_block::create_signed_block(std::move(new_block));
}

// Used for transition from dpos to Savanna.
Expand Down
4 changes: 2 additions & 2 deletions libraries/chain/block_state_legacy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ namespace eosio::chain {
{}

block_state_legacy::block_state_legacy( pending_block_header_state_legacy&& cur,
mutable_signed_block_ptr&& b,
mutable_block_ptr&& b,
deque<transaction_metadata_ptr>&& trx_metas,
const std::optional<digests_t>& action_receipt_digests_savanna,
const protocol_feature_set& pfs,
const validator_t& validator,
const signer_callback_type& signer
)
:block_header_state_legacy( inject_additional_signatures( std::move(cur), *b, pfs, validator, signer ) )
,block( std::move(b) )
,block( signed_block::create_signed_block(std::move(b)) )
,_pub_keys_recovered( true ) // called by produce_block so signature recovery of trxs must have been done
,_cached_trxs( std::move(trx_metas) )
,action_mroot_savanna( action_receipt_digests_savanna ? std::optional<digest_type>(calculate_merkle(*action_receipt_digests_savanna)) : std::nullopt )
Expand Down
22 changes: 5 additions & 17 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ struct assembled_block {
block_id_type id;
pending_block_header_state_legacy pending_block_header_state;
deque<transaction_metadata_ptr> trx_metas;
mutable_signed_block_ptr unsigned_block;
mutable_block_ptr unsigned_block;

// if the unsigned_block pre-dates block-signing authorities this may be present.
std::optional<producer_authority_schedule> new_producer_authority_cache;
Expand Down Expand Up @@ -728,7 +728,7 @@ struct building_block {
}

// in dpos, we create a signed_block here. In IF mode, we do it later (when we are ready to sign it)
auto block_ptr = std::make_shared<signed_block>(bb.pending_block_header_state.make_block_header(
auto block_ptr = signed_block::create_mutable_block(bb.pending_block_header_state.make_block_header(
transaction_mroot, action_mroot, bb.new_pending_producer_schedule, std::move(new_finalizer_policy),
vector<digest_type>(bb.new_protocol_feature_activations), pfs));

Expand Down Expand Up @@ -1567,20 +1567,8 @@ struct controller_impl {
return irreversible_mode() || bsp->is_valid();
};

using packed_block_future = std::future<std::vector<char>>;
std::vector<packed_block_future> v;
if (!irreversible_mode()) {
v.reserve( branch.size() );
for( auto bitr = branch.rbegin(); bitr != branch.rend() && should_process(*bitr); ++bitr ) {
v.emplace_back( post_async_task( thread_pool.get_executor(), [b=(*bitr)->block]() { return fc::raw::pack(*b); } ) );
}
}
auto it = v.begin();

for( auto bitr = branch.rbegin(); bitr != branch.rend() && should_process(*bitr); ++bitr ) {
packed_block_future f;
if (irreversible_mode()) {
f = post_async_task( thread_pool.get_executor(), [b=(*bitr)->block]() { return fc::raw::pack(*b); } );
result = apply_irreversible_block(fork_db, *bitr);
if (result != controller::apply_blocks_result::complete)
break;
Expand All @@ -1590,7 +1578,7 @@ struct controller_impl {

// blog.append could fail due to failures like running out of space.
// Do it before commit so that in case it throws, DB can be rolled back.
blog.append( (*bitr)->block, (*bitr)->id(), irreversible_mode() ? f.get() : it++->get() );
blog.append( (*bitr)->block, (*bitr)->id(), (*bitr)->block->packed_signed_block() );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now it is cleaner.


db.commit( (*bitr)->block_num() );
root_id = (*bitr)->id();
Expand Down Expand Up @@ -1659,7 +1647,7 @@ struct controller_impl {
auto head = std::make_shared<block_state_legacy>();
static_cast<block_header_state_legacy&>(*head) = genheader;
head->activated_protocol_features = std::make_shared<protocol_feature_activation_set>(); // no activated protocol features in genesis
head->block = std::make_shared<signed_block>(genheader.header);
head->block = signed_block::create_signed_block(signed_block::create_mutable_block(genheader.header));
chain_head = block_handle{head};

db.set_revision( chain_head.block_num() );
Expand Down Expand Up @@ -5531,7 +5519,7 @@ signed_block_ptr controller::fetch_block_by_number( uint32_t block_num )const {

std::vector<char> controller::fetch_serialized_block_by_number( uint32_t block_num)const { try {
if (signed_block_ptr b = my->fork_db_fetch_block_on_best_branch_by_num(block_num)) {
return fc::raw::pack(*b);
return b->packed_signed_block();
}

return my->blog.read_serialized_block_by_num(block_num);
Expand Down
3 changes: 1 addition & 2 deletions libraries/chain/deep_mind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ namespace eosio::chain {
{
assert(b);
assert(active_proposer_policy);
auto packed_blk = fc::raw::pack(*b);
auto finality_data = fc::raw::pack(fd);
auto packed_proposer_policy = fc::raw::pack(*active_proposer_policy);
auto packed_finalizer_policy = fc::raw::pack(active_finalizer_policy);
Expand All @@ -100,7 +99,7 @@ namespace eosio::chain {
("id", id)
("num", b->block_num())
("lib", lib)
("blk", fc::to_hex(packed_blk))
("blk", fc::to_hex(b->packed_signed_block()))
("fd", fc::to_hex(finality_data))
("pp", fc::to_hex(packed_proposer_policy))
("fp", fc::to_hex(packed_finalizer_policy))
Expand Down
42 changes: 37 additions & 5 deletions libraries/chain/include/eosio/chain/block.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,21 +85,28 @@ namespace eosio { namespace chain {

using block_extension = block_extension_types::block_extension_t;

using signed_block_ptr = std::shared_ptr<const signed_block>;
// mutable_block_ptr is built up until it is signed and converted to signed_block_ptr
// mutable_block_ptr is not thread safe and should be moved into signed_block_ptr when complete
using mutable_block_ptr = std::unique_ptr<signed_block>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not keep the original name mutable_signed_block_ptr? That's more clearer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because in most cases it is not "signed" yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment? We have other cases where mutable and non-mutable use the same root like mutale_db() and db().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


/**
*/
struct signed_block : public signed_block_header{
private:
signed_block( const signed_block& ) = default;
explicit signed_block( const signed_block_header& h ):signed_block_header(h){}
public:
signed_block() = default;
explicit signed_block( const signed_block_header& h ):signed_block_header(h){}
signed_block( signed_block&& ) = default;
signed_block& operator=(const signed_block&) = delete;
signed_block& operator=(signed_block&&) = default;
signed_block clone() const { return *this; }
mutable_block_ptr clone() const { return std::unique_ptr<signed_block>(new signed_block(*this)); }
static mutable_block_ptr create_mutable_block(const signed_block_header& h) { return std::unique_ptr<signed_block>(new signed_block(h)); }
static signed_block_ptr create_signed_block(mutable_block_ptr&& b) { b->pack(); return signed_block_ptr{std::move(b)}; }

deque<transaction_receipt> transactions; /// new or generated transactions
extensions_type block_extensions;
extensions_type block_extensions;

flat_multimap<uint16_t, block_extension> validate_and_extract_extensions()const;
std::optional<block_extension> extract_extension(uint16_t extension_id)const;
Expand All @@ -108,9 +115,17 @@ namespace eosio { namespace chain {
return std::get<Ext>(*extract_extension(Ext::extension_id()));
}
bool contains_extension(uint16_t extension_id)const;

const bytes& packed_signed_block() const { assert(!packed_block.empty()); return packed_block; }

private:
friend struct block_state;
friend struct block_state_legacy;
template<typename Stream> friend void fc::raw::unpack(Stream& s, eosio::chain::signed_block& v);
void pack() { packed_block = fc::raw::pack( *this ); }

bytes packed_block; // packed this
};
using signed_block_ptr = std::shared_ptr<const signed_block>;
using mutable_signed_block_ptr = std::shared_ptr<signed_block>;

struct producer_confirmation {
block_id_type block_id;
Expand All @@ -129,3 +144,20 @@ FC_REFLECT_DERIVED(eosio::chain::transaction_receipt, (eosio::chain::transaction
FC_REFLECT(eosio::chain::additional_block_signatures_extension, (signatures));
FC_REFLECT(eosio::chain::quorum_certificate_extension, (qc));
FC_REFLECT_DERIVED(eosio::chain::signed_block, (eosio::chain::signed_block_header), (transactions)(block_extensions) )

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not put this code some where in fc?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow. signed_block is defined in libraries/chain which depends on libraries/libfc.
I did try to move the forward declare out of fc, but couldn't get it to work without the fc forward declare.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant unpack(Stream& s, eosio::chain::signed_block& v) is defined in raw_fwd.hpp, why not put the implementation there. I think about it more. It is also OK to remain here.

namespace fc::raw {
template <typename Stream>
void unpack(Stream& s, eosio::chain::signed_block& v) {
try {
if constexpr (requires { s.extract_mirror(); }) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't quite get this code. Why the if constexpr check? Why the 4096 below?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because if signed_block is being unpacked with a datastream_mirror then we can use the extract_mirror() to populate signed_block::packed_block. If a different Stream type is being used, then wrap it in a datastream_mirror to capture the packed block data. The 4096 is used to reserve a size in the vector; open to removing the + 4096 if we think it might be wasteful.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I got confused by the method name is unpack but then it sets packed_block. Add a comment about 4096.

fc::reflector<eosio::chain::signed_block>::visit( fc::raw::detail::unpack_object_visitor<Stream, eosio::chain::signed_block>( v, s ) );
v.packed_block = s.extract_mirror();
} else {
fc::datastream_mirror<Stream> ds(s, sizeof(eosio::chain::signed_block) + 4096);
fc::reflector<eosio::chain::signed_block>::visit( fc::raw::detail::unpack_object_visitor<fc::datastream_mirror<Stream>, eosio::chain::signed_block>( v, ds ) );
v.packed_block = ds.extract_mirror();
}
} FC_RETHROW_EXCEPTIONS(warn, "error unpacking signed_block")
}
}

4 changes: 0 additions & 4 deletions libraries/chain/include/eosio/chain/block_log.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,6 @@ namespace eosio { namespace chain {
std::optional<signed_block_header> read_block_header_by_num(uint32_t block_num)const;
std::optional<block_id_type> read_block_id_by_num(uint32_t block_num)const;

signed_block_ptr read_block_by_id(const block_id_type& id)const {
return read_block_by_num(block_header::num_from_id(id));
}

/**
* Return offset of block in file, or block_log::npos if it does not exist.
*/
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/include/eosio/chain/block_state_legacy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace eosio::chain {
);

block_state_legacy( pending_block_header_state_legacy&& cur,
mutable_signed_block_ptr&& b, // unsigned block
mutable_block_ptr&& b, // unsigned block
deque<transaction_metadata_ptr>&& trx_metas,
const std::optional<digests_t>& action_receipt_digests_savanna,
const protocol_feature_set& pfs,
Expand Down
34 changes: 34 additions & 0 deletions libraries/libfc/include/fc/io/datastream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,40 @@ class datastream<Container, typename std::enable_if_t<(std::is_same_v<std::vecto
const Container& storage() const { return _container; }
};

/**
* Datastream wrapper that creates a copy of the datastream data read. After reading
* the data is available via extract_mirror()
* @tparam DataStream datastream to wrap
*/
template <typename DataStream>
class datastream_mirror {
public:
explicit datastream_mirror( DataStream& ds, size_t reserve = 0 ) : ds(ds) {
mirror.reserve(reserve);
}

void skip( size_t s ) { ds.skip(s); }
bool read( char* d, size_t s ) {
if (ds.read(d, s)) {
auto size = mirror.size();
if (mirror.capacity() < size + s)
mirror.reserve(std::bit_ceil(size + s));
mirror.resize(size + s);
memcpy(mirror.data() + size, d, s);
return true;
}
return false;
}

bool get( unsigned char& c ) { return read(&c, 1); }
bool get( char& c ) { return read(&c, 1); }

std::vector<char> extract_mirror() { return std::move(mirror); }

private:
DataStream& ds;
std::vector<char> mirror;
};


template<typename ST>
Expand Down
6 changes: 6 additions & 0 deletions libraries/libfc/include/fc/io/raw_fwd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#include <variant>
#include <filesystem>

namespace eosio::chain {
struct signed_block;
}

namespace fc {
class time_point;
class time_point_sec;
Expand All @@ -32,6 +36,8 @@ namespace fc {
template<typename T>
inline size_t pack_size( const T& v );

template <typename Stream> void unpack(Stream& s, eosio::chain::signed_block& v);

template<typename Stream, typename Storage> inline void pack( Stream& s, const fc::fixed_string<Storage>& u );
template<typename Stream, typename Storage> inline void unpack( Stream& s, fc::fixed_string<Storage>& u );

Expand Down
7 changes: 5 additions & 2 deletions plugins/net_plugin/net_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <fc/network/message_buffer.hpp>
#include <fc/io/json.hpp>
#include <fc/io/raw.hpp>
#include <fc/io/datastream.hpp>
#include <fc/variant_object.hpp>
#include <fc/crypto/rand.hpp>
#include <fc/exception/exception.hpp>
Expand Down Expand Up @@ -3190,8 +3191,10 @@ namespace eosio {
return true;
}

auto ds = pending_message_buffer.create_datastream();
fc::raw::unpack( ds, which );
auto mb_ds = pending_message_buffer.create_datastream();
fc::raw::unpack( mb_ds, which );

fc::datastream_mirror ds(mb_ds, message_length);
shared_ptr<signed_block> ptr = std::make_shared<signed_block>();
fc::raw::unpack( ds, *ptr );

Expand Down
7 changes: 4 additions & 3 deletions plugins/test_control_plugin/test_control_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ void test_control_plugin_impl::swap_action_in_block(const chain::signed_block_pt
return;
}

auto copy_b = std::make_shared<chain::signed_block>(b->clone());
auto copy_b = b->clone();
copy_b->previous = b->calculate_id();
copy_b->block_extensions.clear(); // remove QC extension since header will claim same as previous block
copy_b->timestamp = b->timestamp.next();
Expand Down Expand Up @@ -159,12 +159,13 @@ void test_control_plugin_impl::swap_action_in_block(const chain::signed_block_pt
copy_b->transaction_mroot = chain::calculate_merkle( std::move(trx_digests) );
// Re-sign the block
copy_b->producer_signature = _swap_on_options.blk_priv_key.sign(copy_b->calculate_id());
auto copy_b_signed = signed_block::create_signed_block(std::move(copy_b));

// will be processed on the next start_block if is_new_best_head
const auto&[add_result, bh] = _chain.accept_block(copy_b->calculate_id(), copy_b);
const auto&[add_result, bh] = _chain.accept_block(copy_b_signed->calculate_id(), copy_b_signed);
ilog("Swapped action ${f} to ${t}, add_result ${a}, block ${bn}",
("f", _swap_on_options.from)("t", _swap_on_options.to)("a", add_result)("bn", bh ? bh->block_num() : 0));
app().find_plugin<net_plugin>()->broadcast_block(copy_b, copy_b->calculate_id());
app().find_plugin<net_plugin>()->broadcast_block(copy_b_signed, copy_b_signed->calculate_id());
if (_swap_on_options.shutdown)
app().quit();
reset_swap_action();
Expand Down
7 changes: 4 additions & 3 deletions tests/block_log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct block_log_fixture {
}
else {
eosio::chain::genesis_state gs;
log->reset(gs, std::make_shared<eosio::chain::signed_block>());
log->reset(gs, eosio::chain::signed_block::create_signed_block(eosio::chain::signed_block::create_mutable_block({})));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this complicated?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to make it difficult to misuse signed_block. We want packed_block data of signed_block to always be populated. Could create a method of signed_block that just does this, but since that would only be useful in tests I didn't.


//in this case it's not really empty since the "genesis block" is present. These tests only
// work because the default ctor of a block_header (used above) has previous 0'ed out which
Expand All @@ -54,11 +54,12 @@ struct block_log_fixture {
std::vector<char> a;
a.assign(size, fillchar);

auto p = std::make_shared<eosio::chain::signed_block>();
auto p = eosio::chain::signed_block::create_mutable_block({});
p->previous._hash[0] = fc::endian_reverse_u32(index-1);
p->header_extensions.push_back(std::make_pair<uint16_t, std::vector<char>>(0, std::vector<char>(a)));

log->append(p, p->calculate_id(), fc::raw::pack(*p));
auto sp = eosio::chain::signed_block::create_signed_block(std::move(p));
log->append(sp, sp->calculate_id(), sp->packed_signed_block());

if(index + 1 > written_data.size())
written_data.resize(index + 1);
Expand Down
Loading