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

IF: Sign and verify signature of proposed blocks #2241

Merged
merged 5 commits into from
Feb 19, 2024
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
11 changes: 5 additions & 6 deletions libraries/chain/block_header_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace eosio::chain {
// digest_type compute_finalizer_digest() const { return id; };


producer_authority block_header_state::get_scheduled_producer(block_timestamp_type t) const {
const producer_authority& block_header_state::get_scheduled_producer(block_timestamp_type t) const {
return detail::get_scheduled_producer(active_proposer_policy->proposer_schedule.producers, t);
}

Expand Down Expand Up @@ -71,7 +71,7 @@ block_header_state block_header_state::next(block_header_state_input& input) con

// header
// ------
result.header = block_header {
result.header = {
.timestamp = input.timestamp, // [greg todo] do we have to do the slot++ stuff from the legacy version?
.producer = input.producer,
.confirmed = 0,
Expand Down Expand Up @@ -177,16 +177,14 @@ block_header_state block_header_state::next(block_header_state_input& input) con
*
* Given a signed block header, generate the expected template based upon the header time,
* then validate that the provided header matches the template.
*
* If the header specifies new_producers then apply them accordingly.
*/
block_header_state block_header_state::next(const signed_block_header& h, const protocol_feature_set& pfs,
validator_t& validator) const {
block_header_state block_header_state::next(const signed_block_header& h, validator_t& validator) const {
auto producer = detail::get_scheduled_producer(active_proposer_policy->proposer_schedule.producers, h.timestamp).producer_name;

EOS_ASSERT( h.previous == block_id, unlinkable_block_exception, "previous mismatch" );
EOS_ASSERT( h.producer == producer, wrong_producer, "wrong producer specified" );
EOS_ASSERT( h.confirmed == 0, block_validate_exception, "invalid confirmed ${c}", ("c", h.confirmed) );
EOS_ASSERT( !h.new_producers, producer_schedule_exception, "Block header contains legacy producer schedule outdated by activation of WTMsig Block Signatures" );

auto exts = h.validate_and_extract_header_extensions();

Expand All @@ -197,6 +195,7 @@ block_header_state block_header_state::next(const signed_block_header& h, const
auto pfa_entry = exts.lower_bound(protocol_feature_activation::extension_id());
auto& pfa_ext = std::get<protocol_feature_activation>(pfa_entry->second);
new_protocol_feature_activations = std::move(pfa_ext.protocol_features);
validator( timestamp(), activated_protocol_features->protocol_features, new_protocol_feature_activations );
}

// retrieve instant_finality_extension data from block header extension
Expand Down
4 changes: 2 additions & 2 deletions libraries/chain/block_header_state_legacy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace eosio::chain {
return blocknums[ index ];
}

producer_authority block_header_state_legacy::get_scheduled_producer( block_timestamp_type t ) const {
const producer_authority& block_header_state_legacy::get_scheduled_producer( block_timestamp_type t ) const {
return detail::get_scheduled_producer(active_schedule.producers, t);
}

Expand All @@ -34,7 +34,7 @@ namespace eosio::chain {
(when = header.timestamp).slot++;
}

auto proauth = get_scheduled_producer(when);
const auto& proauth = get_scheduled_producer(when);

auto itr = producer_to_last_produced.find( proauth.producer_name );
if( itr != producer_to_last_produced.end() ) {
Expand Down
72 changes: 68 additions & 4 deletions libraries/chain/block_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,25 @@ namespace eosio::chain {

block_state::block_state(const block_header_state& prev, signed_block_ptr b, const protocol_feature_set& pfs,
const validator_t& validator, bool skip_validate_signee)
: block_header_state(prev.next(*b, pfs, validator))
: block_header_state(prev.next(*b, validator))
, block(std::move(b))
, strong_digest(compute_finalizer_digest())
, weak_digest(create_weak_digest(strong_digest))
, pending_qc(prev.active_finalizer_policy->finalizers.size(), prev.active_finalizer_policy->threshold, prev.active_finalizer_policy->max_weak_sum_before_weak_final())
{}
{
// ASSUMPTION FROM controller_impl::apply_block = all untrusted blocks will have their signatures pre-validated here
if( !skip_validate_signee ) {
auto sigs = detail::extract_additional_signatures(block);
const auto& valid_block_signing_authority = prev.get_scheduled_producer(timestamp()).authority;
verify_signee(sigs, valid_block_signing_authority);
}
}

block_state::block_state(const block_header_state& bhs, deque<transaction_metadata_ptr>&& trx_metas,
deque<transaction_receipt>&& trx_receipts, const std::optional<quorum_certificate>& qc)
deque<transaction_receipt>&& trx_receipts, const std::optional<quorum_certificate>& qc,
const signer_callback_type& signer, const block_signing_authority& valid_block_signing_authority)
: block_header_state(bhs)
, block(std::make_shared<signed_block>(signed_block_header{bhs.header})) // [greg todo] do we need signatures?
, block(std::make_shared<signed_block>(signed_block_header{bhs.header}))
, strong_digest(compute_finalizer_digest())
, weak_digest(create_weak_digest(strong_digest))
, pending_qc(bhs.active_finalizer_policy->finalizers.size(), bhs.active_finalizer_policy->threshold, bhs.active_finalizer_policy->max_weak_sum_before_weak_final())
Expand All @@ -32,6 +40,8 @@ block_state::block_state(const block_header_state& bhs, deque<transaction_metada
if( qc ) {
emplace_extension(block->block_extensions, quorum_certificate_extension::extension_id(), fc::raw::pack( *qc ));
}

sign(signer, valid_block_signing_authority);
}

// Used for transition from dpos to instant-finality
Expand Down Expand Up @@ -193,4 +203,58 @@ std::optional<quorum_certificate> block_state::get_best_qc() const {
return quorum_certificate{ block_num(), best_qc };
}

void inject_additional_signatures( signed_block& b, const std::vector<signature_type>& additional_signatures)
{
if (!additional_signatures.empty()) {
// as an optimization we don't copy this out into the legitimate extension structure as it serializes
// the same way as the vector of signatures
static_assert(fc::reflector<additional_block_signatures_extension>::total_member_count == 1);
static_assert(std::is_same_v<decltype(additional_block_signatures_extension::signatures), std::vector<signature_type>>);

emplace_extension(b.block_extensions, additional_block_signatures_extension::extension_id(), fc::raw::pack( additional_signatures ));
}
}

void block_state::sign(const signer_callback_type& signer, const block_signing_authority& valid_block_signing_authority ) {
auto sigs = signer( block_id );

EOS_ASSERT(!sigs.empty(), no_block_signatures, "Signer returned no signatures");
block->producer_signature = sigs.back(); // last is producer signature, rest are additional signatures to inject in the block extension
sigs.pop_back();

verify_signee(sigs, valid_block_signing_authority);
inject_additional_signatures(*block, sigs);
}

void block_state::verify_signee(const std::vector<signature_type>& additional_signatures, const block_signing_authority& valid_block_signing_authority) const {
auto num_keys_in_authority = std::visit([](const auto &a){ return a.keys.size(); }, valid_block_signing_authority);
EOS_ASSERT(1 + additional_signatures.size() <= num_keys_in_authority, wrong_signing_key,
"number of block signatures (${num_block_signatures}) exceeds number of keys (${num_keys}) in block signing authority: ${authority}",
("num_block_signatures", 1 + additional_signatures.size())
("num_keys", num_keys_in_authority)
("authority", valid_block_signing_authority)
);

std::set<public_key_type> keys;
keys.emplace(fc::crypto::public_key( block->producer_signature, block_id, true ));

for (const auto& s: additional_signatures) {
auto res = keys.emplace(s, block_id, true);
EOS_ASSERT(res.second, wrong_signing_key, "block signed by same key twice: ${key}", ("key", *res.first));
}

bool is_satisfied = false;
size_t relevant_sig_count = 0;

std::tie(is_satisfied, relevant_sig_count) = producer_authority::keys_satisfy_and_relevant(keys, valid_block_signing_authority);

EOS_ASSERT(relevant_sig_count == keys.size(), wrong_signing_key,
"block signed by unexpected key: ${signing_keys}, expected: ${authority}. ${c} != ${s}",
("signing_keys", keys)("authority", valid_block_signing_authority)("c", relevant_sig_count)("s", keys.size()));

EOS_ASSERT(is_satisfied, wrong_signing_key,
"block signatures ${signing_keys} do not satisfy the block signing authority: ${authority}",
("signing_keys", keys)("authority", valid_block_signing_authority));
}

} /// eosio::chain
9 changes: 6 additions & 3 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ struct assembled_block {
}

completed_block complete_block(const protocol_feature_set& pfs, validator_t validator,
const signer_callback_type& signer) {
const signer_callback_type& signer, const block_signing_authority& valid_block_signing_authority) {
return std::visit(overloaded{[&](assembled_block_legacy& ab) {
auto bsp = std::make_shared<block_state_legacy>(
std::move(ab.pending_block_header_state), std::move(ab.unsigned_block),
Expand All @@ -332,7 +332,8 @@ struct assembled_block {
},
[&](assembled_block_if& ab) {
auto bsp = std::make_shared<block_state>(ab.bhs, std::move(ab.trx_metas),
std::move(ab.trx_receipts), ab.qc);
std::move(ab.trx_receipts), ab.qc, signer,
valid_block_signing_authority);
return completed_block{std::move(bsp)};
}},
v);
Expand Down Expand Up @@ -4205,10 +4206,12 @@ void controller::assemble_and_complete_block( block_report& br, const signer_cal
my->assemble_block();

auto& ab = std::get<assembled_block>(my->pending->_block_stage);
const auto& valid_block_signing_authority = my->head_active_schedule_auth().get_scheduled_producer(ab.timestamp()).authority;
my->pending->_block_stage = ab.complete_block(
my->protocol_features.get_protocol_feature_set(),
[](block_timestamp_type timestamp, const flat_set<digest_type>& cur_features, const vector<digest_type>& new_features) {},
signer_callback);
signer_callback,
valid_block_signing_authority);

br = my->pending->_block_report;
}
Expand Down
11 changes: 2 additions & 9 deletions libraries/chain/include/eosio/chain/block_header_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ struct block_header_state {
const producer_authority_schedule& active_schedule_auth() const { return active_proposer_policy->proposer_schedule; }

block_header_state next(block_header_state_input& data) const;

block_header_state next(const signed_block_header& h, const protocol_feature_set& pfs, validator_t& validator) const;
block_header_state next(const signed_block_header& h, validator_t& validator) const;

// block descending from this need the provided qc in the block extension
bool is_needed(const quorum_certificate& qc) const {
Expand All @@ -83,13 +82,7 @@ struct block_header_state {

flat_set<digest_type> get_activated_protocol_features() const { return activated_protocol_features->protocol_features; }
const vector<digest_type>& get_new_protocol_feature_activations() const;
producer_authority get_scheduled_producer(block_timestamp_type t) const;
uint32_t active_schedule_version() const;
signed_block_header make_block_header(const checksum256_type& transaction_mroot,
const checksum256_type& action_mroot,
const std::optional<producer_authority_schedule>& new_producers,
vector<digest_type>&& new_protocol_feature_activations,
const protocol_feature_set& pfs) const;
const producer_authority& get_scheduled_producer(block_timestamp_type t) const;
};

using block_header_state_ptr = std::shared_ptr<block_header_state>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ struct block_header_state_legacy : public detail::block_header_state_legacy_comm

uint32_t calc_dpos_last_irreversible( account_name producer_of_next_block )const;

producer_authority get_scheduled_producer( block_timestamp_type t )const;
const producer_authority& get_scheduled_producer( block_timestamp_type t )const;
const block_id_type& previous()const { return header.previous; }
digest_type sig_digest()const;
void sign( const signer_callback_type& signer );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace eosio::chain::detail {
return block_timestamp_type{t.slot + (config::producer_repetitions - index) + config::producer_repetitions};
}

inline producer_authority get_scheduled_producer(const vector<producer_authority>& producers, block_timestamp_type t) {
inline const producer_authority& get_scheduled_producer(const vector<producer_authority>& producers, block_timestamp_type t) {
auto index = t.slot % (producers.size() * config::producer_repetitions);
index /= config::producer_repetitions;
return producers[index];
Expand Down
8 changes: 7 additions & 1 deletion libraries/chain/include/eosio/chain/block_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace eosio::chain {

using signer_callback_type = std::function<std::vector<signature_type>(const digest_type&)>;

constexpr std::array weak_bls_sig_postfix = { 'W', 'E', 'A', 'K' };
using weak_digest_t = std::array<uint8_t, sizeof(digest_type) + weak_bls_sig_postfix.size()>;

Expand Down Expand Up @@ -63,9 +65,13 @@ struct block_state : public block_header_state { // block_header_state provi
const validator_t& validator, bool skip_validate_signee);

block_state(const block_header_state& bhs, deque<transaction_metadata_ptr>&& trx_metas,
deque<transaction_receipt>&& trx_receipts, const std::optional<quorum_certificate>& qc);
deque<transaction_receipt>&& trx_receipts, const std::optional<quorum_certificate>& qc,
const signer_callback_type& signer, const block_signing_authority& valid_block_signing_authority);

explicit block_state(const block_state_legacy& bsp);

void sign(const signer_callback_type& signer, const block_signing_authority& valid_block_signing_authority);
void verify_signee(const std::vector<signature_type>& additional_signatures, const block_signing_authority& valid_block_signing_authority) const;
};

using block_state_ptr = std::shared_ptr<block_state>;
Expand Down
100 changes: 100 additions & 0 deletions unittests/protocol_feature_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,106 @@ BOOST_AUTO_TEST_CASE( get_sender_test ) { try {
);
} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_CASE( protocol_activatation_works_after_transition_to_savanna ) { try {
validating_tester c({}, {}, setup_policy::preactivate_feature_and_new_bios );

const auto& pfm = c.control->get_protocol_feature_manager();
const auto& d = pfm.get_builtin_digest(builtin_protocol_feature_t::instant_finality);
// needed for bios contract
const auto& dp = pfm.get_builtin_digest(builtin_protocol_feature_t::bls_primitives);
const auto& dw = pfm.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures);
const auto& dwk = pfm.get_builtin_digest(builtin_protocol_feature_t::webauthn_key);
c.preactivate_protocol_features( {*d, *dp, *dw, *dwk} );
c.produce_block();

c.set_bios_contract();
c.produce_block();

uint32_t lib = 0;
c.control->irreversible_block().connect([&](const block_signal_params& t) {
const auto& [ block, id ] = t;
lib = block->block_num();
});

c.produce_block();

vector<account_name> accounts = {
"alice"_n, "bob"_n, "carol"_n
};

base_tester::finalizer_policy_input policy_input = {
.finalizers = { {.name = "alice"_n, .weight = 1},
{.name = "bob"_n, .weight = 3},
{.name = "carol"_n, .weight = 5} },
.threshold = 5,
.local_finalizers = {"carol"_n}
};

// Create finalizer accounts
c.create_accounts(accounts);
c.produce_block();

// activate savanna
c.set_finalizers(policy_input);
auto block = c.produce_block(); // this block contains the header extension for the instant finality

std::optional<block_header_extension> ext = block->extract_header_extension(instant_finality_extension::extension_id());
BOOST_TEST(!!ext);
std::optional<finalizer_policy> fin_policy = std::get<instant_finality_extension>(*ext).new_finalizer_policy;
BOOST_TEST(!!fin_policy);
BOOST_TEST(fin_policy->finalizers.size() == accounts.size());

block = c.produce_block(); // savanna now active
auto fb = c.control->fetch_block_by_id(block->calculate_id());
BOOST_REQUIRE(!!fb);
BOOST_TEST(fb == block);
ext = fb->extract_header_extension(instant_finality_extension::extension_id());
BOOST_REQUIRE(ext);

auto lib_after_transition = lib;

c.produce_blocks(4);
BOOST_CHECK_GT(lib, lib_after_transition);

// verify protocol feature activation works under savanna

const auto& tester1_account = account_name("tester1");
const auto& tester2_account = account_name("tester2");
c.create_accounts( {tester1_account, tester2_account} );
c.produce_block();

BOOST_CHECK_EXCEPTION( c.set_code( tester1_account, test_contracts::get_sender_test_wasm() ),
wasm_exception,
fc_exception_message_is( "env.get_sender unresolveable" ) );

const auto& d2 = pfm.get_builtin_digest( builtin_protocol_feature_t::get_sender );
BOOST_REQUIRE( d2 );

c.preactivate_protocol_features( {*d2} );
c.produce_block();

c.set_code( tester1_account, test_contracts::get_sender_test_wasm() );
c.set_abi( tester1_account, test_contracts::get_sender_test_abi() );
c.set_code( tester2_account, test_contracts::get_sender_test_wasm() );
c.set_abi( tester2_account, test_contracts::get_sender_test_abi() );
c.produce_block();

BOOST_CHECK_EXCEPTION( c.push_action( tester1_account, "sendinline"_n, tester1_account, mutable_variant_object()
("to", tester2_account.to_string())
("expected_sender", account_name{}) ),
eosio_assert_message_exception,
eosio_assert_message_is( "sender did not match" ) );

c.push_action( tester1_account, "sendinline"_n, tester1_account, mutable_variant_object()
("to", tester2_account.to_string())
("expected_sender", tester1_account.to_string())
);

c.push_action( tester1_account, "assertsender"_n, tester1_account, mutable_variant_object()
("expected_sender", account_name{})
);
} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_CASE( ram_restrictions_test ) { try {
tester c( setup_policy::preactivate_feature_and_new_bios );

Expand Down
Loading