From 43bb2207fd1ac568b8dfd005e864a80376a1a6d5 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 10 May 2019 16:50:36 -0400 Subject: [PATCH 01/36] more interim commits converting to extension --- libraries/chain/block_header.cpp | 2 +- libraries/chain/block_header_state.cpp | 114 +++++++-- libraries/chain/block_state.cpp | 9 +- libraries/chain/controller.cpp | 50 ++-- .../include/eosio/chain/block_header.hpp | 18 +- .../eosio/chain/block_header_state.hpp | 24 +- .../chain/include/eosio/chain/block_state.hpp | 3 + .../chain/include/eosio/chain/controller.hpp | 8 +- .../eosio/chain/global_property_object.hpp | 8 +- .../include/eosio/chain/producer_schedule.hpp | 231 ++++++++++++++---- .../eosio/chain/protocol_feature_manager.hpp | 3 +- libraries/chain/include/eosio/chain/types.hpp | 4 +- libraries/chain/wasm_interface.cpp | 33 ++- libraries/fc | 2 +- libraries/testing/tester.cpp | 2 +- plugins/chain_plugin/chain_plugin.cpp | 2 +- .../state_history_serialization.hpp | 14 +- .../state_history_plugin_abi.cpp | 25 +- unittests/forked_tests.cpp | 2 +- unittests/producer_schedule_tests.cpp | 64 ++--- 20 files changed, 442 insertions(+), 176 deletions(-) diff --git a/libraries/chain/block_header.cpp b/libraries/chain/block_header.cpp index 692089dc9e6..04b41456f77 100644 --- a/libraries/chain/block_header.cpp +++ b/libraries/chain/block_header.cpp @@ -28,7 +28,7 @@ namespace eosio { namespace chain { return result; } - vector block_header::validate_and_extract_header_extensions()const { + flat_multimap block_header::validate_and_extract_header_extensions()const { using block_header_extensions_t = block_header_extension_types::block_header_extensions_t; using decompose_t = block_header_extension_types::decompose_t; diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index f7dd7aba656..29ac9d9e665 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -9,7 +9,7 @@ namespace eosio { namespace chain { return producer_to_last_produced.find(n) != producer_to_last_produced.end(); } - producer_key block_header_state::get_scheduled_producer( block_timestamp_type t )const { + producer_keys block_header_state::get_scheduled_producer( block_timestamp_type t )const { auto index = t.slot % (active_schedule.producers.size() * config::producer_repetitions); index /= config::producer_repetitions; return active_schedule.producers[index]; @@ -57,7 +57,7 @@ namespace eosio { namespace chain { result.active_schedule_version = active_schedule.version; result.prev_activated_protocol_features = activated_protocol_features; - result.block_signing_key = prokey.block_signing_key; + result.valid_block_signing_keys = prokey.block_signing_keys; result.producer = prokey.producer_name; result.blockroot_merkle = blockroot_merkle; @@ -167,8 +167,9 @@ namespace eosio { namespace chain { signed_block_header pending_block_header_state::make_block_header( const checksum256_type& transaction_mroot, const checksum256_type& action_mroot, - optional&& new_producers, - vector&& new_protocol_feature_activations + std::optional&& new_producers, + vector&& new_protocol_feature_activations, + const protocol_feature_set& pfs )const { signed_block_header h; @@ -180,23 +181,48 @@ namespace eosio { namespace chain { h.transaction_mroot = transaction_mroot; h.action_mroot = action_mroot; h.schedule_version = active_schedule_version; - h.new_producers = std::move(new_producers); if( new_protocol_feature_activations.size() > 0 ) { h.header_extensions.emplace_back( - protocol_feature_activation::extension_id(), - fc::raw::pack( protocol_feature_activation{ std::move(new_protocol_feature_activations) } ) + protocol_feature_activation::extension_id(), + fc::raw::pack( protocol_feature_activation{ std::move(new_protocol_feature_activations) } ) ); } + if (new_producers) { + auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); + const auto& protocol_features = prev_activated_protocol_features->protocol_features; + bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + + if ( wtmsig_enabled ) { + // add the header extension to update the block schedule + h.header_extensions.emplace_back( + producer_schedule_change_extension::extension_id(), + fc::raw::pack( producer_schedule_change_extension( std::move(*new_producers) ) ) + ); + } else { + legacy::producer_schedule_type downgraded_producers; + downgraded_producers.version = new_producers->version; + for (const auto &p : new_producers->producers) { + p.visit([&downgraded_producers](const auto& auth){ + EOS_ASSERT(auth.keys.size() == 1 && auth.keys.front().weight == auth.threshold, producer_schedule_exception, "multisig block signing present before enabled!"); + downgraded_producers.producers.emplace_back(legacy::producer_key{auth.producer_name, auth.keys.front().key}); + }); + } + h.new_producers = downgraded_producers; + } + } + return h; } block_header_state pending_block_header_state::_finish_next( const signed_block_header& h, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator + )&& { EOS_ASSERT( h.timestamp == timestamp, block_validate_exception, "timestamp mismatch" ); @@ -205,19 +231,48 @@ namespace eosio { namespace chain { EOS_ASSERT( h.producer == producer, wrong_producer, "wrong producer specified" ); EOS_ASSERT( h.schedule_version == active_schedule_version, producer_schedule_exception, "schedule_version in signed block is corrupted" ); + auto exts = h.validate_and_extract_header_extensions(); + + std::optional maybe_new_producer_schedule; + if( h.new_producers ) { + auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); + const auto& protocol_features = prev_activated_protocol_features->protocol_features; + bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + + EOS_ASSERT(!wtmsig_enabled, producer_schedule_exception, "Block header contains legacy producer schedule outdated by activation of WTMsig Block Signatures" ); + EOS_ASSERT( !was_pending_promoted, producer_schedule_exception, "cannot set pending producer schedule in the same block in which pending was promoted to active" ); - EOS_ASSERT( h.new_producers->version == active_schedule.version + 1, producer_schedule_exception, "wrong producer schedule version specified" ); - EOS_ASSERT( prev_pending_schedule.schedule.producers.size() == 0, producer_schedule_exception, + + const auto& new_producers = *h.new_producers; + EOS_ASSERT( new_producers.version == active_schedule.version + 1, producer_schedule_exception, "wrong producer schedule version specified" ); + EOS_ASSERT( prev_pending_schedule.schedule.producers.empty(), producer_schedule_exception, "cannot set new pending producers until last pending is confirmed" ); + + maybe_new_producer_schedule.emplace(new_producers); } - protocol_feature_activation_set_ptr new_activated_protocol_features; + if ( exts.count(producer_schedule_change_extension::extension_id()) > 0 ) { + auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); + const auto& protocol_features = prev_activated_protocol_features->protocol_features; + bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); - auto exts = h.validate_and_extract_header_extensions(); - { - if( exts.size() > 0 ) { - const auto& new_protocol_features = exts.front().get().protocol_features; + EOS_ASSERT(wtmsig_enabled, producer_schedule_exception, "Block header producer_schedule_change_extension before activation of WTMsig Block Signatures" ); + EOS_ASSERT( !was_pending_promoted, producer_schedule_exception, "cannot set pending producer schedule in the same block in which pending was promoted to active" ); + + const auto& new_producer_schedule = exts.lower_bound(producer_schedule_change_extension::extension_id())->second.get(); + + EOS_ASSERT( new_producer_schedule.version == active_schedule.version + 1, producer_schedule_exception, "wrong producer schedule version specified" ); + EOS_ASSERT( prev_pending_schedule.schedule.producers.empty(), producer_schedule_exception, + "cannot set new pending producers until last pending is confirmed" ); + + maybe_new_producer_schedule.emplace(new_producer_schedule); + } + + protocol_feature_activation_set_ptr new_activated_protocol_features; + { // handle protocol_feature_activation + if( exts.count(protocol_feature_activation::extension_id() > 0) ) { + const auto& new_protocol_features = exts.lower_bound(protocol_feature_activation::extension_id())->second.get().protocol_features; validator( timestamp, prev_activated_protocol_features->protocol_features, new_protocol_features ); new_activated_protocol_features = std::make_shared( @@ -238,9 +293,9 @@ namespace eosio { namespace chain { result.header_exts = std::move(exts); - if( h.new_producers ) { - result.pending_schedule.schedule = *h.new_producers; - result.pending_schedule.schedule_hash = digest_type::hash( *h.new_producers ); + if( maybe_new_producer_schedule ) { + result.pending_schedule.schedule = std::move(*maybe_new_producer_schedule); + result.pending_schedule.schedule_hash = digest_type::hash(result.pending_schedule.schedule); result.pending_schedule.schedule_lib_num = block_number; } else { if( was_pending_promoted ) { @@ -259,17 +314,18 @@ namespace eosio { namespace chain { block_header_state pending_block_header_state::finish_next( const signed_block_header& h, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, bool skip_validate_signee )&& { - auto result = std::move(*this)._finish_next( h, validator ); + auto result = std::move(*this)._finish_next( h, pfs, validator ); // ASSUMPTION FROM controller_impl::apply_block = all untrusted blocks will have their signatures pre-validated here if( !skip_validate_signee ) { - result.verify_signee( result.signee() ); + result.verify_signee( ); } return result; @@ -277,13 +333,14 @@ namespace eosio { namespace chain { block_header_state pending_block_header_state::finish_next( signed_block_header& h, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, const std::function& signer )&& { - auto result = std::move(*this)._finish_next( h, validator ); + auto result = std::move(*this)._finish_next( h, pfs, validator ); result.sign( signer ); h.producer_signature = result.header.producer_signature; return result; @@ -299,12 +356,13 @@ namespace eosio { namespace chain { */ block_header_state block_header_state::next( const signed_block_header& h, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, bool skip_validate_signee )const { - return next( h.timestamp, h.confirmed ).finish_next( h, validator, skip_validate_signee ); + return next( h.timestamp, h.confirmed ).finish_next( h, pfs, validator, skip_validate_signee ); } digest_type block_header_state::sig_digest()const { @@ -315,7 +373,8 @@ namespace eosio { namespace chain { void block_header_state::sign( const std::function& signer ) { auto d = sig_digest(); header.producer_signature = signer( d ); - EOS_ASSERT( block_signing_key == fc::crypto::public_key( header.producer_signature, d ), + + EOS_ASSERT( valid_block_signing_keys.find(signee()) != valid_block_signing_keys.end(), wrong_signing_key, "block is signed with unexpected key" ); } @@ -323,10 +382,11 @@ namespace eosio { namespace chain { return fc::crypto::public_key( header.producer_signature, sig_digest(), true ); } - void block_header_state::verify_signee( const public_key_type& signee )const { - EOS_ASSERT( block_signing_key == signee, wrong_signing_key, + void block_header_state::verify_signee( )const { + auto signing_key = signee(); + EOS_ASSERT( valid_block_signing_keys.find(signing_key) != valid_block_signing_keys.end(), wrong_signing_key, "block not signed by expected key", - ("block_signing_key", block_signing_key)( "signee", signee ) ); + ("signing_key", signing_key)( "valid_keys", valid_block_signing_keys ) ); } /** @@ -335,10 +395,10 @@ namespace eosio { namespace chain { const vector& block_header_state::get_new_protocol_feature_activations()const { static const vector no_activations{}; - if( header_exts.size() == 0 || !header_exts.front().contains() ) + if( header_exts.count(protocol_feature_activation::extension_id()) == 0 ) return no_activations; - return header_exts.front().get().protocol_features; + return header_exts.lower_bound(protocol_feature_activation::extension_id())->second.get().protocol_features; } } } /// namespace eosio::chain diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index 3a246038149..2c368a4b54e 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -5,24 +5,26 @@ namespace eosio { namespace chain { block_state::block_state( const block_header_state& prev, signed_block_ptr b, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, bool skip_validate_signee ) - :block_header_state( prev.next( *b, validator, skip_validate_signee ) ) + :block_header_state( prev.next( *b, pfs, validator, skip_validate_signee ) ) ,block( std::move(b) ) {} block_state::block_state( pending_block_header_state&& cur, signed_block_ptr&& b, vector&& trx_metas, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, const std::function& signer ) - :block_header_state( std::move(cur).finish_next( *b, validator, signer ) ) + :block_header_state( std::move(cur).finish_next( *b, pfs, validator, signer ) ) ,block( std::move(b) ) ,trxs( std::move(trx_metas) ) {} @@ -31,12 +33,13 @@ namespace eosio { namespace chain { block_state::block_state( pending_block_header_state&& cur, const signed_block_ptr& b, vector&& trx_metas, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, bool skip_validate_signee ) - :block_header_state( std::move(cur).finish_next( *b, validator, skip_validate_signee ) ) + :block_header_state( std::move(cur).finish_next( *b, pfs, validator, skip_validate_signee ) ) ,block( b ) ,trxs( std::move(trx_metas) ) {} diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 3b211d8bd9e..22c31ad18a7 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -111,13 +111,13 @@ struct building_block { ,_new_protocol_feature_activations( new_protocol_feature_activations ) {} - pending_block_header_state _pending_block_header_state; - optional _new_pending_producer_schedule; - vector _new_protocol_feature_activations; - size_t _num_new_protocol_features_that_have_activated = 0; - vector _pending_trx_metas; - vector _pending_trx_receipts; - vector _actions; + pending_block_header_state _pending_block_header_state; + optional _new_pending_producer_schedule; + vector _new_protocol_feature_activations; + size_t _num_new_protocol_features_that_have_activated = 0; + vector _pending_trx_metas; + vector _pending_trx_receipts; + vector _actions; }; struct assembled_block { @@ -427,7 +427,7 @@ struct controller_impl { */ void initialize_blockchain_state() { wlog( "Initializing new blockchain with genesis state" ); - producer_schedule_type initial_schedule{ 0, {{config::system_account_name, conf.genesis.initial_key}} }; + producer_authority_schedule initial_schedule{ 0, {producer_authority_v0{config::system_account_name, 1, {{conf.genesis.initial_key, 1}} } } }; block_header_state genheader; genheader.active_schedule = initial_schedule; @@ -1474,7 +1474,7 @@ struct controller_impl { ilog( "promoting proposed schedule (set in block ${proposed_num}) to pending; current block: ${n} lib: ${lib} schedule: ${schedule} ", ("proposed_num", *gpo.proposed_schedule_block_num)("n", pbhs.block_num) ("lib", pbhs.dpos_irreversible_blocknum) - ("schedule", static_cast(gpo.proposed_schedule) ) ); + ("schedule", static_cast(gpo.proposed_schedule) ) ); } EOS_ASSERT( gpo.proposed_schedule.version == pbhs.active_schedule_version + 1, @@ -2602,13 +2602,13 @@ account_name controller::pending_block_producer()const { return my->pending->get_pending_block_header_state().producer; } -public_key_type controller::pending_block_signing_key()const { +flat_set controller::pending_block_signing_keys()const { EOS_ASSERT( my->pending, block_validate_exception, "no pending block" ); if( my->pending->_block_stage.contains() ) - return my->pending->_block_stage.get()._block_state->block_signing_key; + return my->pending->_block_stage.get()._block_state->block_signing_keys; - return my->pending->get_pending_block_header_state().block_signing_key; + return my->pending->get_pending_block_header_state().block_signing_keys; } optional controller::pending_producer_block_id()const { @@ -2724,7 +2724,7 @@ void controller::pop_block() { my->pop_block(); } -int64_t controller::set_proposed_producers( vector producers ) { +int64_t controller::set_proposed_producers( vector producers ) { const auto& gpo = get_global_properties(); auto cur_block_num = head_block_num() + 1; @@ -2741,7 +2741,7 @@ int64_t controller::set_proposed_producers( vector producers ) { return -1; // the proposed producer schedule does not change } - producer_schedule_type sch; + producer_authority_schedule sch; decltype(sch.producers.cend()) end; decltype(end) begin; @@ -2775,7 +2775,7 @@ int64_t controller::set_proposed_producers( vector producers ) { return version; } -const producer_schedule_type& controller::active_producers()const { +const producer_authority_schedule& controller::active_producers()const { if( !(my->pending) ) return my->head->active_schedule; @@ -2785,7 +2785,7 @@ const producer_schedule_type& controller::active_producers()const { return my->pending->get_pending_block_header_state().active_schedule; } -const producer_schedule_type& controller::pending_producers()const { +const producer_authority_schedule& controller::pending_producers()const { if( !(my->pending) ) return my->head->pending_schedule.schedule; @@ -2793,9 +2793,15 @@ const producer_schedule_type& controller::pending_producers()const { return my->pending->_block_stage.get()._block_state->pending_schedule.schedule; if( my->pending->_block_stage.contains() ) { - const auto& np = my->pending->_block_stage.get()._unsigned_block->new_producers; - if( np ) - return *np; + if (is_builtin_activated(builtin_protocol_feature_t::wtmsig_block_signatures)) { + auto exts = my->pending->_block_stage.get()._unsigned_block->validate_and_extract_header_extensions(); + if (exts.count(producer_schedule_change_extension::extension_id())) + return exts.lower_bound(producer_schedule_change_extension::extension_id())->second.get(); + } else { + const auto& np = my->pending->_block_stage.get()._unsigned_block->new_producers; + if( np ) + return *np; + } } const auto& bb = my->pending->_block_stage.get(); @@ -2806,12 +2812,12 @@ const producer_schedule_type& controller::pending_producers()const { return bb._pending_block_header_state.prev_pending_schedule.schedule; } -optional controller::proposed_producers()const { +optional controller::proposed_producers()const { const auto& gpo = get_global_properties(); if( !gpo.proposed_schedule_block_num.valid() ) - return optional(); + return optional(); - return gpo.proposed_schedule; + return (producer_authority_schedule)gpo.proposed_schedule; } bool controller::light_validation_allowed(bool replay_opts_disabled_by_policy) const { diff --git a/libraries/chain/include/eosio/chain/block_header.hpp b/libraries/chain/include/eosio/chain/block_header.hpp index fc751826d95..bc622b38fb6 100644 --- a/libraries/chain/include/eosio/chain/block_header.hpp +++ b/libraries/chain/include/eosio/chain/block_header.hpp @@ -16,7 +16,8 @@ namespace eosio { namespace chain { } using block_header_extension_types = detail::block_header_extension_types< - protocol_feature_activation + protocol_feature_activation, + producer_schedule_change_extension >; using block_header_extensions = block_header_extension_types::block_header_extensions_t; @@ -42,13 +43,20 @@ namespace eosio { namespace chain { checksum256_type transaction_mroot; /// mroot of cycles_summary checksum256_type action_mroot; /// mroot of all delivered action receipts - - /** The producer schedule version that should validate this block, this is used to + /** + * LEGACY SUPPORT - After enabling the wtmsig-blocks extension this field is deprecated and must be empty + * + * Prior to that activation this carries: + * + * The producer schedule version that should validate this block, this is used to * indicate that the prior block which included new_producers->version has been marked * irreversible and that it the new producer schedule takes effect this block. */ + + using new_producers_type = optional; + uint32_t schedule_version = 0; - optional new_producers; + new_producers_type new_producers; extensions_type header_extensions; @@ -59,7 +67,7 @@ namespace eosio { namespace chain { uint32_t block_num() const { return num_from_id(previous) + 1; } static uint32_t num_from_id(const block_id_type& id); - vector validate_and_extract_header_extensions()const; + flat_multimap validate_and_extract_header_extensions()const; }; diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 41e19253138..8a6eb08d653 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include namespace eosio { namespace chain { @@ -12,18 +13,18 @@ namespace detail { uint32_t block_num = 0; uint32_t dpos_proposed_irreversible_blocknum = 0; uint32_t dpos_irreversible_blocknum = 0; - producer_schedule_type active_schedule; + producer_authority_schedule active_schedule; incremental_merkle blockroot_merkle; flat_map producer_to_last_produced; flat_map producer_to_last_implied_irb; - public_key_type block_signing_key; + flat_set valid_block_signing_keys; vector confirm_count; }; struct schedule_info { uint32_t schedule_lib_num = 0; /// last irr block num digest_type schedule_hash; - producer_schedule_type schedule; + producer_authority_schedule schedule; }; } @@ -39,16 +40,19 @@ struct pending_block_header_state : public detail::block_header_state_common { signed_block_header make_block_header( const checksum256_type& transaction_mroot, const checksum256_type& action_mroot, - optional&& new_producers, - vector&& new_protocol_feature_activations )const; + std::optional&& new_producers, + vector&& new_protocol_feature_activations, + const protocol_feature_set& pfs)const; block_header_state finish_next( const signed_block_header& h, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, bool skip_validate_signee = false )&&; block_header_state finish_next( signed_block_header& h, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, @@ -56,6 +60,7 @@ struct pending_block_header_state : public detail::block_header_state_common { protected: block_header_state _finish_next( const signed_block_header& h, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator )&&; @@ -74,7 +79,7 @@ struct block_header_state : public detail::block_header_state_common { /// this data is redundant with the data stored in header, but it acts as a cache that avoids /// duplication of work - vector header_exts; + flat_multimap header_exts; block_header_state() = default; @@ -85,6 +90,7 @@ struct block_header_state : public detail::block_header_state_common { pending_block_header_state next( block_timestamp_type when, uint16_t num_prev_blocks_to_confirm )const; block_header_state next( const signed_block_header& h, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, @@ -94,12 +100,12 @@ struct block_header_state : public detail::block_header_state_common { uint32_t calc_dpos_last_irreversible( account_name producer_of_next_block )const; bool is_active_producer( account_name n )const; - producer_key get_scheduled_producer( block_timestamp_type t )const; + producer_authority get_scheduled_producer( block_timestamp_type t )const; const block_id_type& prev()const { return header.previous; } digest_type sig_digest()const; void sign( const std::function& signer ); public_key_type signee()const; - void verify_signee(const public_key_type& signee)const; + void verify_signee()const; const vector& get_new_protocol_feature_activations()const; }; @@ -116,7 +122,7 @@ FC_REFLECT( eosio::chain::detail::block_header_state_common, (blockroot_merkle) (producer_to_last_produced) (producer_to_last_implied_irb) - (block_signing_key) + (valid_block_signing_keys) (confirm_count) ) diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index e91161cf716..e31337d42ec 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -14,6 +14,7 @@ namespace eosio { namespace chain { struct block_state : public block_header_state { block_state( const block_header_state& prev, signed_block_ptr b, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, @@ -23,6 +24,7 @@ namespace eosio { namespace chain { block_state( pending_block_header_state&& cur, signed_block_ptr&& b, // unsigned block vector&& trx_metas, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, @@ -32,6 +34,7 @@ namespace eosio { namespace chain { block_state( pending_block_header_state&& cur, const signed_block_ptr& b, // signed block vector&& trx_metas, + const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index c7702d09414..daa8be2c313 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -212,9 +212,9 @@ namespace eosio { namespace chain { const vector& get_pending_trx_receipts()const; - const producer_schedule_type& active_producers()const; - const producer_schedule_type& pending_producers()const; - optional proposed_producers()const; + const producer_authority_schedule& active_producers()const; + const producer_authority_schedule& pending_producers()const; + optional proposed_producers()const; uint32_t last_irreversible_block_num() const; block_id_type last_irreversible_block_id() const; @@ -255,7 +255,7 @@ namespace eosio { namespace chain { bool is_known_unexpired_transaction( const transaction_id_type& id) const; - int64_t set_proposed_producers( vector producers ); + int64_t set_proposed_producers( vector producers ); bool light_validation_allowed(bool replay_opts_disabled_by_policy) const; bool skip_auth_check()const; diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index 14ed594c0bd..a8d0bac9cb1 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -27,10 +27,10 @@ namespace eosio { namespace chain { OBJECT_CTOR(global_property_object, (proposed_schedule)) public: - id_type id; - optional proposed_schedule_block_num; - shared_producer_schedule_type proposed_schedule; - chain_config configuration; + id_type id; + optional proposed_schedule_block_num; + shared_producer_authority_schedule proposed_schedule; + chain_config configuration; }; diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index 09528d7b809..d22a0e657e1 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -5,54 +5,203 @@ namespace eosio { namespace chain { - /** - * Used as part of the producer_schedule_type, mapps the producer name to their key. - */ - struct producer_key { - account_name producer_name; - public_key_type block_signing_key; + namespace legacy { + /** + * Used as part of the producer_schedule_type, maps the producer name to their key. + */ + struct producer_key { + account_name producer_name; + public_key_type block_signing_key; + + friend bool operator == ( const producer_key& lhs, const producer_key& rhs ) { + return tie( lhs.producer_name, lhs.block_signing_key ) == tie( rhs.producer_name, rhs.block_signing_key ); + } + friend bool operator != ( const producer_key& lhs, const producer_key& rhs ) { + return tie( lhs.producer_name, lhs.block_signing_key ) != tie( rhs.producer_name, rhs.block_signing_key ); + } + }; + + /** + * Defines both the order, account name, and signing keys of the active set of producers. + */ + struct producer_schedule_type { + uint32_t version = 0; ///< sequentially incrementing version number + vector producers; + + friend bool operator == ( const producer_schedule_type& a, const producer_schedule_type& b ) + { + if( a.version != b.version ) return false; + if ( a.producers.size() != b.producers.size() ) return false; + for( uint32_t i = 0; i < a.producers.size(); ++i ) + if( a.producers[i] != b.producers[i] ) return false; + return true; + } + + friend bool operator != ( const producer_schedule_type& a, const producer_schedule_type& b ) + { + return !(a==b); + } + }; + } + + struct producer_authority_v0 { + account_name producer_name; + uint32_t threshold; + vector keys; + + friend bool operator == ( const producer_authority_v0& lhs, const producer_authority_v0& rhs ) { + return tie( lhs.producer_name, lhs.keys ) == tie( rhs.producer_name, rhs.keys ); + } + friend bool operator != ( const producer_authority_v0& lhs, const producer_authority_v0& rhs ) { + return tie( lhs.producer_name, lhs.keys ) != tie( rhs.producer_name, rhs.keys ); + } + }; + + using producer_authority = static_variant; + + struct shared_producer_authority_v0 { + shared_producer_authority_v0( chainbase::allocator alloc ) + :keys(alloc){} + + account_name producer_name; + uint32_t threshold; + shared_vector keys; + + friend bool operator == ( const shared_producer_authority_v0& lhs, const shared_producer_authority_v0& rhs ) { + return tie( lhs.producer_name, lhs.keys ) == tie( rhs.producer_name, rhs.keys ); + } + friend bool operator != ( const shared_producer_authority_v0& lhs, const shared_producer_authority_v0& rhs ) { + return tie( lhs.producer_name, lhs.keys ) != tie( rhs.producer_name, rhs.keys ); + } + }; + + namespace detail { + template + struct shared_converter; + + template<> + struct shared_converter { + static shared_producer_authority_v0 convert (chainbase::allocator alloc, const producer_authority_v0& src) { + shared_producer_authority_v0 result(alloc); + result.producer_name = src.producer_name; + result.threshold = src.threshold; + result.keys.clear(); + result.keys.reserve(src.keys.size()); + for (const auto& k: src.keys) { + result.keys.push_back(k); + } + + return result; + } + }; + + template<> + struct shared_converter { + static producer_authority_v0 convert (const shared_producer_authority_v0& src) { + producer_authority_v0 result; + result.producer_name = src.producer_name; + result.threshold = src.threshold; + result.keys.reserve(src.keys.size()); + for (const auto& k: src.keys) { + result.keys.push_back(k); + } + + return result; + } + }; - friend bool operator == ( const producer_key& lhs, const producer_key& rhs ) { - return tie( lhs.producer_name, lhs.block_signing_key ) == tie( rhs.producer_name, rhs.block_signing_key ); + template + auto shared_convert( chainbase::allocator alloc, const T& src ) { + return shared_converter>::convert(alloc, src); } - friend bool operator != ( const producer_key& lhs, const producer_key& rhs ) { - return tie( lhs.producer_name, lhs.block_signing_key ) != tie( rhs.producer_name, rhs.block_signing_key ); + + template + auto shared_convert( const T& src ) { + return shared_converter>::convert(src); + } + } + + using shared_producer_authority = static_variant; + + struct producer_authority_schedule { + producer_authority_schedule() = default; + + /** + * Up-convert a legacy producer schedule + */ + explicit producer_authority_schedule( const legacy::producer_schedule_type& old ) + :version(old.version) + { + producers.reserve( old.producers.size() ); + for( const auto& p : old.producers ) + producers.emplace_back(producer_authority_v0{ p.producer_name, 1, {{p.block_signing_key, 1}}}); + } + + uint32_t version = 0; ///< sequentially incrementing version number + vector producers; + + friend bool operator == ( const producer_authority_schedule& a, const producer_authority_schedule& b ) + { + if( a.version != b.version ) return false; + if ( a.producers.size() != b.producers.size() ) return false; + for( uint32_t i = 0; i < a.producers.size(); ++i ) + if( ! (a.producers[i] == b.producers[i]) ) return false; + return true; + } + + friend bool operator != ( const producer_authority_schedule& a, const producer_authority_schedule& b ) + { + return !(a==b); } }; + /** - * Defines both the order, account name, and signing keys of the active set of producers. + * Block Header Extension Compatibility */ - struct producer_schedule_type { - uint32_t version = 0; ///< sequentially incrementing version number - vector producers; + struct producer_schedule_change_extension : producer_authority_schedule, fc::reflect_init { - public_key_type get_producer_key( account_name p )const { - for( const auto& i : producers ) - if( i.producer_name == p ) - return i.block_signing_key; - return public_key_type(); + static constexpr uint16_t extension_id() { return 1; } + static constexpr bool enforce_unique() { return true; } + void reflector_init() { + static_assert( fc::raw::has_feature_reflector_init_on_unpacked_reflected_types, "producer_schedule_extension expects FC to support reflector_init" ); } + + producer_schedule_change_extension() = default; + producer_schedule_change_extension(const producer_schedule_change_extension&) = default; + producer_schedule_change_extension( producer_schedule_change_extension&& ) = default; + + producer_schedule_change_extension( producer_authority_schedule&& sched ) + :producer_authority_schedule(sched) {} }; - struct shared_producer_schedule_type { - shared_producer_schedule_type( chainbase::allocator alloc ) + + + struct shared_producer_authority_schedule { + shared_producer_authority_schedule( chainbase::allocator alloc ) :producers(alloc){} - shared_producer_schedule_type& operator=( const producer_schedule_type& a ) { + shared_producer_authority_schedule& operator=( const producer_authority_schedule& a ) { version = a.version; producers.clear(); producers.reserve( a.producers.size() ); - for( const auto& p : a.producers ) - producers.push_back(p); + for( const auto& p : a.producers ) { + p.visit([this](const auto& src){ + producers.emplace_back(detail::shared_convert(producers.get_allocator(), src)); + }); + } return *this; } - operator producer_schedule_type()const { - producer_schedule_type result; + explicit operator producer_authority_schedule()const { + producer_authority_schedule result; result.version = version; result.producers.reserve(producers.size()); - for( const auto& p : producers ) - result.producers.push_back(p); + for( const auto& p : producers ) { + p.visit([&result](const auto& src){ + result.producers.emplace_back(detail::shared_convert(src)); + }); + } + return result; } @@ -62,26 +211,16 @@ namespace eosio { namespace chain { } uint32_t version = 0; ///< sequentially incrementing version number - shared_vector producers; + shared_vector producers; }; - inline bool operator == ( const producer_schedule_type& a, const producer_schedule_type& b ) - { - if( a.version != b.version ) return false; - if ( a.producers.size() != b.producers.size() ) return false; - for( uint32_t i = 0; i < a.producers.size(); ++i ) - if( a.producers[i] != b.producers[i] ) return false; - return true; - } - inline bool operator != ( const producer_schedule_type& a, const producer_schedule_type& b ) - { - return !(a==b); - } - - } } /// eosio::chain -FC_REFLECT( eosio::chain::producer_key, (producer_name)(block_signing_key) ) -FC_REFLECT( eosio::chain::producer_schedule_type, (version)(producers) ) -FC_REFLECT( eosio::chain::shared_producer_schedule_type, (version)(producers) ) +FC_REFLECT( eosio::chain::legacy::producer_key, (producer_name)(block_signing_key) ) +FC_REFLECT( eosio::chain::legacy::producer_schedule_type, (version)(producers) ) +FC_REFLECT( eosio::chain::producer_authority_v0, (producer_name)(threshold)(keys) ) +FC_REFLECT( eosio::chain::producer_authority_schedule, (version)(producers) ) +FC_REFLECT_DERIVED( eosio::chain::producer_schedule_change_extension, (eosio::chain::producer_authority_schedule), ) +FC_REFLECT( eosio::chain::shared_producer_authority_schedule, (version)(producers) ) + diff --git a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp index c2f0140433f..36189fcaa8e 100644 --- a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp +++ b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp @@ -24,7 +24,8 @@ enum class builtin_protocol_feature_t : uint32_t { only_bill_first_authorizer, forward_setcode, get_sender, - ram_restrictions + ram_restrictions, + wtmsig_block_signatures, }; struct protocol_feature_subjective_restrictions { diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index d6323fbd23d..2063e7ce3e0 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -77,6 +77,7 @@ namespace eosio { namespace chain { using fc::time_point; using fc::safe; using fc::flat_map; + using fc::flat_multimap; using fc::flat_set; using fc::static_variant; using fc::ecc::range_proof_type; @@ -87,8 +88,6 @@ namespace eosio { namespace chain { using private_key_type = fc::crypto::private_key; using signature_type = fc::crypto::signature; - struct void_t{}; - using chainbase::allocator; using shared_string = boost::interprocess::basic_string, allocator>; template @@ -374,4 +373,3 @@ namespace eosio { namespace chain { } } // eosio::chain -FC_REFLECT( eosio::chain::void_t, ) diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 6ad6a9cb131..48b33764f8e 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -177,24 +177,43 @@ class privileged_api : public context_aware_api { int64_t set_proposed_producers( array_ptr packed_producer_schedule, size_t datalen) { datastream ds( packed_producer_schedule, datalen ); - vector producers; - fc::raw::unpack(ds, producers); + vector producers; + + if ( context.control.is_builtin_activated( builtin_protocol_feature_t::one_of_n_block_signatures )) { + fc::raw::unpack(ds, producers); + + } else { + vector old_version; + fc::raw::unpack(ds, old_version); + + /* + * Up-convert the producers + */ + for ( const auto& p: old_version ) { + producers.emplace_back(producer_keys{ p.producer_name, {p.block_signing_key}}); + } + } + + EOS_ASSERT(producers.size() <= config::max_producers, wasm_execution_error, "Producer schedule exceeds the maximum producer count for this chain"); EOS_ASSERT( producers.size() > 0 - || !context.control.is_builtin_activated( - builtin_protocol_feature_t::disallow_empty_producer_schedule - ), + || !context.control.is_builtin_activated( builtin_protocol_feature_t::disallow_empty_producer_schedule ), wasm_execution_error, "Producer schedule cannot be empty" ); - EOS_ASSERT(producers.size() <= config::max_producers, wasm_execution_error, "Producer schedule exceeds the maximum producer count for this chain"); + // check that producers are unique std::set unique_producers; for (const auto& p: producers) { EOS_ASSERT( context.is_account(p.producer_name), wasm_execution_error, "producer schedule includes a nonexisting account" ); - EOS_ASSERT( p.block_signing_key.valid(), wasm_execution_error, "producer schedule includes an invalid key" ); + + for (const auto& k: p.block_signing_keys) { + EOS_ASSERT( k.valid(), wasm_execution_error, "producer schedule includes an invalid key" ); + } + unique_producers.insert(p.producer_name); } EOS_ASSERT( producers.size() == unique_producers.size(), wasm_execution_error, "duplicate producer name in producer schedule" ); + return context.control.set_proposed_producers( std::move(producers) ); } diff --git a/libraries/fc b/libraries/fc index fcc952b616e..9a008f71c05 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit fcc952b616e5968a5b0898e051e3f56fe3a43245 +Subproject commit 9a008f71c05b6f4105fb6340f13a8344d1335a5d diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index a9a442d8380..7bc83662dc3 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -310,7 +310,7 @@ namespace eosio { namespace testing { auto producer = control->head_block_state()->get_scheduled_producer( control->pending_block_time() ); private_key_type priv_key; // Check if signing private key exist in the list - auto private_key_itr = block_signing_private_keys.find( producer.block_signing_key ); + auto private_key_itr = block_signing_private_keys.find( *producer.block_signing_keys.begin() ); if( private_key_itr == block_signing_private_keys.end() ) { // If it's not found, default to active k1 key priv_key = get_private_key( producer.producer_name, "active" ); diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 6b0247afd0d..c093124ef90 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1705,7 +1705,7 @@ read_only::get_producers_result read_only::get_producers( const read_only::get_p for (auto p : db.active_producers().producers) { fc::variant row = fc::mutable_variant_object() ("owner", p.producer_name) - ("producer_key", p.block_signing_key) + ("producer_key", p.block_signing_keys) ("url", "") ("total_votes", 0.0f); diff --git a/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp b/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp index e11b7e5f4f7..40b287e77af 100644 --- a/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp +++ b/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp @@ -242,18 +242,20 @@ datastream& operator<<( } template -datastream& operator<<(datastream& ds, const history_serial_wrapper& obj) { +datastream& operator<<(datastream& ds, const history_serial_wrapper& obj) { fc::raw::pack(ds, as_type(obj.obj.producer_name.value)); - fc::raw::pack(ds, as_type(obj.obj.block_signing_key)); + fc::raw::pack(ds, as_type(obj.obj.threshold)); + history_serialize_container(ds, obj.db, + as_type>(obj.obj.keys)); return ds; } template datastream& operator<<(datastream& ds, - const history_serial_wrapper& obj) { + const history_serial_wrapper& obj) { fc::raw::pack(ds, as_type(obj.obj.version)); history_serialize_container(ds, obj.db, - as_type>(obj.obj.producers)); + as_type>(obj.obj.producers)); return ds; } @@ -283,10 +285,10 @@ datastream& operator<<(datastream& ds, const history_serial_wrapper datastream& operator<<(datastream& ds, const history_serial_wrapper& obj) { - fc::raw::pack(ds, fc::unsigned_int(0)); + fc::raw::pack(ds, fc::unsigned_int(1)); fc::raw::pack(ds, as_type>(obj.obj.proposed_schedule_block_num)); fc::raw::pack(ds, make_history_serial_wrapper( - obj.db, as_type(obj.obj.proposed_schedule))); + obj.db, as_type(obj.obj.proposed_schedule))); fc::raw::pack(ds, make_history_serial_wrapper(obj.db, as_type(obj.obj.configuration))); return ds; diff --git a/plugins/state_history_plugin/state_history_plugin_abi.cpp b/plugins/state_history_plugin/state_history_plugin_abi.cpp index 9d5324b0bbf..ff6eec427bb 100644 --- a/plugins/state_history_plugin/state_history_plugin_abi.cpp +++ b/plugins/state_history_plugin/state_history_plugin_abi.cpp @@ -311,6 +311,19 @@ extern const char* const state_history_plugin_abi = R"({ { "type": "producer_key[]", "name": "producers" } ] }, + { + "name": "producer_authority_v0", "fields": [ + { "type": "name", "name": "producer_name" }, + { "type": "uint32", "name": "weight" } + { "type": "key_weight[]", "name": "keys" } + ] + }, + { + "name": "producer_authority_schedule", "fields": [ + { "type": "uint32", "name": "version" }, + { "type": "producer_authority[]", "name": "producers" } + ] + }, { "name": "chain_config_v0", "fields": [ { "type": "uint64", "name": "max_block_net_usage" }, @@ -339,6 +352,13 @@ extern const char* const state_history_plugin_abi = R"({ { "type": "chain_config", "name": "configuration" } ] }, + { + "name": "global_property_v1", "fields": [ + { "type": "uint32?", "name": "proposed_schedule_block_num" }, + { "type": "producer_authority_schedule", "name": "proposed_schedule" }, + { "type": "chain_config", "name": "configuration" } + ] + }, { "name": "generated_transaction_v0", "fields": [ { "type": "name", "name": "sender" }, @@ -492,7 +512,7 @@ extern const char* const state_history_plugin_abi = R"({ { "name": "contract_index_double", "types": ["contract_index_double_v0"] }, { "name": "contract_index_long_double", "types": ["contract_index_long_double_v0"] }, { "name": "chain_config", "types": ["chain_config_v0"] }, - { "name": "global_property", "types": ["global_property_v0"] }, + { "name": "global_property", "types": ["global_property_v0", "global_property_v1"] }, { "name": "generated_transaction", "types": ["generated_transaction_v0"] }, { "name": "activated_protocol_feature", "types": ["activated_protocol_feature_v0"] }, { "name": "protocol_state", "types": ["protocol_state_v0"] }, @@ -504,7 +524,8 @@ extern const char* const state_history_plugin_abi = R"({ { "name": "resource_limits_state", "types": ["resource_limits_state_v0"] }, { "name": "resource_limits_ratio", "types": ["resource_limits_ratio_v0"] }, { "name": "elastic_limit_parameters", "types": ["elastic_limit_parameters_v0"] }, - { "name": "resource_limits_config", "types": ["resource_limits_config_v0"] } + { "name": "resource_limits_config", "types": ["resource_limits_config_v0"] }, + { "name": "producer_authority", "types": ["producer_authority_v0"] }, ], "tables": [ { "name": "account", "type": "account", "key_names": ["name"] }, diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index 6f8f1f29c84..5f466d5ebe9 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -371,7 +371,7 @@ BOOST_AUTO_TEST_CASE( validator_accepts_valid_blocks ) try { BOOST_CHECK_EQUAL( n2.control->head_block_id(), id ); BOOST_REQUIRE( first_block ); - first_block->verify_signee( first_block->signee() ); + first_block->verify_signee(); BOOST_CHECK_EQUAL( first_block->header.id(), first_block->block->id() ); BOOST_CHECK( first_block->header.producer_signature == first_block->block->producer_signature ); diff --git a/unittests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp index bec63376ff5..b31003b9e2d 100644 --- a/unittests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -209,14 +209,14 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_promotion_test, TESTER ) try { produce_block(); } - auto compare_schedules = [&]( const vector& a, const producer_schedule_type& b ) { + auto compare_schedules = [&]( const vector& a, const producer_authority_schedule& b ) { return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); }; auto res = set_producers( {N(alice),N(bob)} ); - vector sch1 = { - {N(alice), get_public_key(N(alice), "active")}, - {N(bob), get_public_key(N(bob), "active")} + vector sch1 = { + producer_authority_v0{N(alice), 1, {{get_public_key(N(alice), "active"), 1}}}, + producer_authority_v0{N(bob), 1, {{get_public_key(N(bob), "active"), 1}}} }; //wdump((fc::json::to_pretty_string(res))); wlog("set producer schedule to [alice,bob]"); @@ -234,10 +234,10 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_promotion_test, TESTER ) try { produce_blocks(6); res = set_producers( {N(alice),N(bob),N(carol)} ); - vector sch2 = { - {N(alice), get_public_key(N(alice), "active")}, - {N(bob), get_public_key(N(bob), "active")}, - {N(carol), get_public_key(N(carol), "active")} + vector sch2 = { + producer_authority_v0{N(alice), 1, {{get_public_key(N(alice), "active"),1}}}, + producer_authority_v0{N(bob), 1, {{get_public_key(N(bob), "active"),1}}}, + producer_authority_v0{N(carol), 1, {{get_public_key(N(carol), "active"),1}}} }; wlog("set producer schedule to [alice,bob,carol]"); BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() ); @@ -274,15 +274,15 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_reduction, tester ) try { produce_block(); } - auto compare_schedules = [&]( const vector& a, const producer_schedule_type& b ) { + auto compare_schedules = [&]( const vector& a, const producer_authority_schedule& b ) { return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); }; auto res = set_producers( {N(alice),N(bob),N(carol)} ); - vector sch1 = { - {N(alice), get_public_key(N(alice), "active")}, - {N(bob), get_public_key(N(bob), "active")}, - {N(carol), get_public_key(N(carol), "active")} + vector sch1 = { + producer_authority_v0{N(alice), 1, {{get_public_key(N(alice), "active"),1}}}, + producer_authority_v0{N(bob), 1, {{get_public_key(N(bob), "active"),1}}}, + producer_authority_v0{N(carol), 1, {{get_public_key(N(carol), "active"),1}}} }; wlog("set producer schedule to [alice,bob,carol]"); BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() ); @@ -299,9 +299,9 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_reduction, tester ) try { produce_blocks(6); res = set_producers( {N(alice),N(bob)} ); - vector sch2 = { - {N(alice), get_public_key(N(alice), "active")}, - {N(bob), get_public_key(N(bob), "active")} + vector sch2 = { + producer_authority_v0{N(alice), 1, {{ get_public_key(N(alice), "active"),1}}}, + producer_authority_v0{N(bob), 1, {{ get_public_key(N(bob), "active"),1}}} }; wlog("set producer schedule to [alice,bob]"); BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() ); @@ -336,14 +336,14 @@ BOOST_AUTO_TEST_CASE( empty_producer_schedule_has_no_effect ) try { c.produce_block(); } - auto compare_schedules = [&]( const vector& a, const producer_schedule_type& b ) { + auto compare_schedules = [&]( const vector& a, const producer_authority_schedule& b ) { return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); }; auto res = c.set_producers( {N(alice),N(bob)} ); - vector sch1 = { - {N(alice), get_public_key(N(alice), "active")}, - {N(bob), get_public_key(N(bob), "active")} + vector sch1 = { + producer_authority_v0{N(alice), 1, {{ get_public_key(N(alice), "active"),1}}}, + producer_authority_v0{N(bob), 1, {{ get_public_key(N(bob), "active"),1}}} }; wlog("set producer schedule to [alice,bob]"); BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() ); @@ -383,10 +383,10 @@ BOOST_AUTO_TEST_CASE( empty_producer_schedule_has_no_effect ) try { // Setting a new producer schedule should still use version 2 res = c.set_producers( {N(alice),N(bob),N(carol)} ); - vector sch2 = { - {N(alice), get_public_key(N(alice), "active")}, - {N(bob), get_public_key(N(bob), "active")}, - {N(carol), get_public_key(N(carol), "active")} + vector sch2 = { + producer_authority_v0{N(alice), 1, {{get_public_key(N(alice), "active"),1}}}, + producer_authority_v0{N(bob), 1, {{get_public_key(N(bob), "active"),1}}}, + producer_authority_v0{N(carol), 1, {{get_public_key(N(carol), "active"),1}}} }; wlog("set producer schedule to [alice,bob,carol]"); BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() ); @@ -413,15 +413,15 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { c.create_accounts( {N(alice),N(bob),N(carol)} ); c.produce_block(); - auto compare_schedules = [&]( const vector& a, const producer_schedule_type& b ) { + auto compare_schedules = [&]( const vector& a, const producer_authority_schedule& b ) { return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); }; auto res = c.set_producers( {N(alice),N(bob),N(carol)} ); - vector sch1 = { - {N(alice), c.get_public_key(N(alice), "active")}, - {N(bob), c.get_public_key(N(bob), "active")}, - {N(carol), c.get_public_key(N(carol), "active")} + vector sch1 = { + producer_authority_v0{N(alice), 1, {{c.get_public_key(N(alice), "active"),1}}}, + producer_authority_v0{N(bob), 1, {{c.get_public_key(N(bob), "active"),1}}}, + producer_authority_v0{N(carol), 1, {{c.get_public_key(N(carol), "active"),1}}} }; wlog("set producer schedule to [alice,bob,carol]"); BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() ); @@ -441,9 +441,9 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { produce_empty_blocks_until( c, N(carol), N(alice) ); res = c.set_producers( {N(alice),N(bob)} ); - vector sch2 = { - {N(alice), c.get_public_key(N(alice), "active")}, - {N(bob), c.get_public_key(N(bob), "active")} + vector sch2 = { + producer_authority_v0{N(alice), 1, {{c.get_public_key(N(alice), "active"),1}}}, + producer_authority_v0{N(bob), 1, {{c.get_public_key(N(bob), "active"),1}}} }; wlog("set producer schedule to [alice,bob]"); BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() ); From 78b348af5066230a47b491dfac45c59dbf92515e Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 10 May 2019 17:50:28 -0400 Subject: [PATCH 02/36] extracted producer name in producer authorities to outside the versioned authority structure, still fixing bugs and compile errors --- libraries/chain/block_header.cpp | 10 +- libraries/chain/block_header_state.cpp | 60 ++++---- .../eosio/chain/block_header_state.hpp | 4 +- .../include/eosio/chain/producer_schedule.hpp | 129 ++++++++++++------ .../state_history_serialization.hpp | 11 +- .../state_history_plugin_abi.cpp | 11 +- .../test_control_plugin.cpp | 3 +- 7 files changed, 151 insertions(+), 77 deletions(-) diff --git a/libraries/chain/block_header.cpp b/libraries/chain/block_header.cpp index 04b41456f77..36cb86004b8 100644 --- a/libraries/chain/block_header.cpp +++ b/libraries/chain/block_header.cpp @@ -35,7 +35,7 @@ namespace eosio { namespace chain { static_assert( std::is_same::value, "block_header_extensions is not setup as expected" ); - vector results; + flat_multimap results; uint16_t id_type_lower_bound = 0; @@ -47,9 +47,12 @@ namespace eosio { namespace chain { "Block header extensions are not in the correct order (ascending id types required)" ); - results.emplace_back(); + auto iter = results.emplace(std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple() + ); - auto match = decompose_t::extract( id, e.second, results.back() ); + auto match = decompose_t::extract( id, e.second, iter->second ); EOS_ASSERT( match, invalid_block_header_extension, "Block header extension with id type ${id} is not supported", ("id", id) @@ -62,6 +65,7 @@ namespace eosio { namespace chain { ); } + id_type_lower_bound = id; } diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 29ac9d9e665..70e89954257 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -9,7 +9,7 @@ namespace eosio { namespace chain { return producer_to_last_produced.find(n) != producer_to_last_produced.end(); } - producer_keys block_header_state::get_scheduled_producer( block_timestamp_type t )const { + producer_authority block_header_state::get_scheduled_producer( block_timestamp_type t )const { auto index = t.slot % (active_schedule.producers.size() * config::producer_repetitions); index /= config::producer_repetitions; return active_schedule.producers[index]; @@ -40,13 +40,13 @@ namespace eosio { namespace chain { (when = header.timestamp).slot++; } - auto prokey = get_scheduled_producer(when); + auto proauth = get_scheduled_producer(when); - auto itr = producer_to_last_produced.find( prokey.producer_name ); + auto itr = producer_to_last_produced.find( proauth.name ); if( itr != producer_to_last_produced.end() ) { EOS_ASSERT( itr->second < (block_num+1) - num_prev_blocks_to_confirm, producer_double_confirm, "producer ${prod} double-confirming known range", - ("prod", prokey.producer_name)("num", block_num+1) + ("prod", proauth.name)("num", block_num+1) ("confirmed", num_prev_blocks_to_confirm)("last_produced", itr->second) ); } @@ -57,8 +57,8 @@ namespace eosio { namespace chain { result.active_schedule_version = active_schedule.version; result.prev_activated_protocol_features = activated_protocol_features; - result.valid_block_signing_keys = prokey.block_signing_keys; - result.producer = prokey.producer_name; + result.valid_block_signing_authority = proauth.authority; + result.producer = proauth.name; result.blockroot_merkle = blockroot_merkle; result.blockroot_merkle.append( id ); @@ -108,7 +108,7 @@ namespace eosio { namespace chain { } result.dpos_proposed_irreversible_blocknum = new_dpos_proposed_irreversible_blocknum; - result.dpos_irreversible_blocknum = calc_dpos_last_irreversible( prokey.producer_name ); + result.dpos_irreversible_blocknum = calc_dpos_last_irreversible( proauth.name ); result.prev_pending_schedule = pending_schedule; @@ -120,32 +120,32 @@ namespace eosio { namespace chain { flat_map new_producer_to_last_produced; for( const auto& pro : result.active_schedule.producers ) { - if( pro.producer_name == prokey.producer_name ) { - new_producer_to_last_produced[pro.producer_name] = result.block_num; + if( pro.name == proauth.name ) { + new_producer_to_last_produced[pro.name] = result.block_num; } else { - auto existing = producer_to_last_produced.find( pro.producer_name ); + auto existing = producer_to_last_produced.find( pro.name ); if( existing != producer_to_last_produced.end() ) { - new_producer_to_last_produced[pro.producer_name] = existing->second; + new_producer_to_last_produced[pro.name] = existing->second; } else { - new_producer_to_last_produced[pro.producer_name] = result.dpos_irreversible_blocknum; + new_producer_to_last_produced[pro.name] = result.dpos_irreversible_blocknum; } } } - new_producer_to_last_produced[prokey.producer_name] = result.block_num; + new_producer_to_last_produced[proauth.name] = result.block_num; result.producer_to_last_produced = std::move( new_producer_to_last_produced ); flat_map new_producer_to_last_implied_irb; for( const auto& pro : result.active_schedule.producers ) { - if( pro.producer_name == prokey.producer_name ) { - new_producer_to_last_implied_irb[pro.producer_name] = dpos_proposed_irreversible_blocknum; + if( pro.name == proauth.name ) { + new_producer_to_last_implied_irb[pro.name] = dpos_proposed_irreversible_blocknum; } else { - auto existing = producer_to_last_implied_irb.find( pro.producer_name ); + auto existing = producer_to_last_implied_irb.find( pro.name ); if( existing != producer_to_last_implied_irb.end() ) { - new_producer_to_last_implied_irb[pro.producer_name] = existing->second; + new_producer_to_last_implied_irb[pro.name] = existing->second; } else { - new_producer_to_last_implied_irb[pro.producer_name] = result.dpos_irreversible_blocknum; + new_producer_to_last_implied_irb[pro.name] = result.dpos_irreversible_blocknum; } } } @@ -156,9 +156,9 @@ namespace eosio { namespace chain { } else { result.active_schedule = active_schedule; result.producer_to_last_produced = producer_to_last_produced; - result.producer_to_last_produced[prokey.producer_name] = block_num; + result.producer_to_last_produced[proauth.name] = block_num; result.producer_to_last_implied_irb = producer_to_last_implied_irb; - result.producer_to_last_implied_irb[prokey.producer_name] = dpos_proposed_irreversible_blocknum; + result.producer_to_last_implied_irb[proauth.name] = dpos_proposed_irreversible_blocknum; } return result; @@ -204,9 +204,9 @@ namespace eosio { namespace chain { legacy::producer_schedule_type downgraded_producers; downgraded_producers.version = new_producers->version; for (const auto &p : new_producers->producers) { - p.visit([&downgraded_producers](const auto& auth){ + p.authority.visit([&downgraded_producers, &p](const auto& auth){ EOS_ASSERT(auth.keys.size() == 1 && auth.keys.front().weight == auth.threshold, producer_schedule_exception, "multisig block signing present before enabled!"); - downgraded_producers.producers.emplace_back(legacy::producer_key{auth.producer_name, auth.keys.front().key}); + downgraded_producers.producers.emplace_back(legacy::producer_key{p.name, auth.keys.front().key}); }); } h.new_producers = downgraded_producers; @@ -370,12 +370,19 @@ namespace eosio { namespace chain { return digest_type::hash( std::make_pair(header_bmroot, pending_schedule.schedule_hash) ); } + static bool key_is_relevant( const block_signing_authority& authority, const public_key_type& key ) { + return authority.visit([&key](const auto &a) { + return std::find_if(a.keys.begin(), a.keys.end(), [&key](const auto& k ){ + return k.key == key; + }) != a.keys.end(); + }); + } + void block_header_state::sign( const std::function& signer ) { auto d = sig_digest(); header.producer_signature = signer( d ); - EOS_ASSERT( valid_block_signing_keys.find(signee()) != valid_block_signing_keys.end(), - wrong_signing_key, "block is signed with unexpected key" ); + EOS_ASSERT( key_is_relevant(valid_block_signing_authority, signee()), wrong_signing_key, "block is signed with unexpected key" ); } public_key_type block_header_state::signee()const { @@ -384,9 +391,10 @@ namespace eosio { namespace chain { void block_header_state::verify_signee( )const { auto signing_key = signee(); - EOS_ASSERT( valid_block_signing_keys.find(signing_key) != valid_block_signing_keys.end(), wrong_signing_key, + + EOS_ASSERT( key_is_relevant(valid_block_signing_authority, signing_key), wrong_signing_key, "block not signed by expected key", - ("signing_key", signing_key)( "valid_keys", valid_block_signing_keys ) ); + ("signing_key", signing_key)( "valid_keys", valid_block_signing_authority ) ); } /** diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 8a6eb08d653..46e8145bf57 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -17,7 +17,7 @@ namespace detail { incremental_merkle blockroot_merkle; flat_map producer_to_last_produced; flat_map producer_to_last_implied_irb; - flat_set valid_block_signing_keys; + block_signing_authority valid_block_signing_authority; vector confirm_count; }; @@ -122,7 +122,7 @@ FC_REFLECT( eosio::chain::detail::block_header_state_common, (blockroot_merkle) (producer_to_last_produced) (producer_to_last_implied_irb) - (valid_block_signing_keys) + (valid_block_signing_authority) (confirm_count) ) diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index d22a0e657e1..d79bd127b69 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace eosio { namespace chain { @@ -44,34 +45,62 @@ namespace eosio { namespace chain { }; } - struct producer_authority_v0 { - account_name producer_name; + /** + * block signing authority version 0 + * this authority allows for a weighted threshold multi-sig per-producer + */ + struct block_signing_authority_v0 { uint32_t threshold; vector keys; - friend bool operator == ( const producer_authority_v0& lhs, const producer_authority_v0& rhs ) { - return tie( lhs.producer_name, lhs.keys ) == tie( rhs.producer_name, rhs.keys ); + friend bool operator == ( const block_signing_authority_v0& lhs, const block_signing_authority_v0& rhs ) { + return tie( lhs.threshold, lhs.keys ) == tie( rhs.threshold, rhs.keys ); } - friend bool operator != ( const producer_authority_v0& lhs, const producer_authority_v0& rhs ) { - return tie( lhs.producer_name, lhs.keys ) != tie( rhs.producer_name, rhs.keys ); + friend bool operator != ( const block_signing_authority_v0& lhs, const block_signing_authority_v0& rhs ) { + return tie( lhs.threshold, lhs.keys ) != tie( rhs.threshold, rhs.keys ); } }; - using producer_authority = static_variant; + using block_signing_authority = static_variant; + + struct producer_authority { + name name; + block_signing_authority authority; + + friend bool operator == ( const producer_authority& lhs, const producer_authority& rhs ) { + return tie( lhs.name, lhs.authority ) == tie( rhs.name, rhs.authority ); + } + friend bool operator != ( const producer_authority& lhs, const producer_authority& rhs ) { + return tie( lhs.name, lhs.authority ) != tie( rhs.name, rhs.authority ); + } + }; - struct shared_producer_authority_v0 { - shared_producer_authority_v0( chainbase::allocator alloc ) + struct shared_block_signing_authority_v0 { + shared_block_signing_authority_v0( chainbase::allocator alloc ) :keys(alloc){} - account_name producer_name; uint32_t threshold; shared_vector keys; - friend bool operator == ( const shared_producer_authority_v0& lhs, const shared_producer_authority_v0& rhs ) { - return tie( lhs.producer_name, lhs.keys ) == tie( rhs.producer_name, rhs.keys ); + friend bool operator == ( const shared_block_signing_authority_v0& lhs, const shared_block_signing_authority_v0& rhs ) { + return tie( lhs.threshold, lhs.keys ) == tie( rhs.threshold, rhs.keys ); + } + friend bool operator != ( const shared_block_signing_authority_v0& lhs, const shared_block_signing_authority_v0& rhs ) { + return tie( lhs.threshold, lhs.keys ) != tie( rhs.threshold, rhs.keys ); + } + }; + + using shared_block_signing_authority = static_variant; + + struct shared_producer_authority { + name name; + shared_block_signing_authority authority; + + friend bool operator == ( const shared_producer_authority& lhs, const shared_producer_authority& rhs ) { + return tie( lhs.name, lhs.authority ) == tie( rhs.name, rhs.authority ); } - friend bool operator != ( const shared_producer_authority_v0& lhs, const shared_producer_authority_v0& rhs ) { - return tie( lhs.producer_name, lhs.keys ) != tie( rhs.producer_name, rhs.keys ); + friend bool operator != ( const shared_producer_authority& lhs, const shared_producer_authority& rhs ) { + return tie( lhs.name, lhs.authority ) != tie( rhs.name, rhs.authority ); } }; @@ -79,11 +108,20 @@ namespace eosio { namespace chain { template struct shared_converter; + template + auto shared_convert( chainbase::allocator alloc, const T& src ) { + return shared_converter>::convert(alloc, src); + } + + template + auto shared_convert( const T& src ) { + return shared_converter>::convert(src); + } + template<> - struct shared_converter { - static shared_producer_authority_v0 convert (chainbase::allocator alloc, const producer_authority_v0& src) { - shared_producer_authority_v0 result(alloc); - result.producer_name = src.producer_name; + struct shared_converter { + static shared_block_signing_authority_v0 convert (chainbase::allocator alloc, const block_signing_authority_v0& src) { + shared_block_signing_authority_v0 result(alloc); result.threshold = src.threshold; result.keys.clear(); result.keys.reserve(src.keys.size()); @@ -96,10 +134,9 @@ namespace eosio { namespace chain { }; template<> - struct shared_converter { - static producer_authority_v0 convert (const shared_producer_authority_v0& src) { - producer_authority_v0 result; - result.producer_name = src.producer_name; + struct shared_converter { + static block_signing_authority_v0 convert (const shared_block_signing_authority_v0& src) { + block_signing_authority_v0 result; result.threshold = src.threshold; result.keys.reserve(src.keys.size()); for (const auto& k: src.keys) { @@ -110,18 +147,33 @@ namespace eosio { namespace chain { } }; - template - auto shared_convert( chainbase::allocator alloc, const T& src ) { - return shared_converter>::convert(alloc, src); - } + template<> + struct shared_converter { + static shared_producer_authority convert (chainbase::allocator alloc, const producer_authority& src) { + shared_producer_authority result; + result.name = src.name; + result.authority = src.authority.visit([&result, &alloc](const auto& a) { + return shared_convert(alloc, a); + }); - template - auto shared_convert( const T& src ) { - return shared_converter>::convert(src); - } - } + return result; + } - using shared_producer_authority = static_variant; + }; + + template<> + struct shared_converter { + static producer_authority convert (const shared_producer_authority& src) { + producer_authority result; + result.name = src.name; + result.authority = src.authority.visit([&result](const auto& a) { + return shared_convert(a); + }); + + return result; + } + }; + } struct producer_authority_schedule { producer_authority_schedule() = default; @@ -134,7 +186,7 @@ namespace eosio { namespace chain { { producers.reserve( old.producers.size() ); for( const auto& p : old.producers ) - producers.emplace_back(producer_authority_v0{ p.producer_name, 1, {{p.block_signing_key, 1}}}); + producers.emplace_back(producer_authority{ p.producer_name, block_signing_authority_v0{ 1, {{p.block_signing_key, 1}} } }); } uint32_t version = 0; ///< sequentially incrementing version number @@ -185,9 +237,7 @@ namespace eosio { namespace chain { producers.clear(); producers.reserve( a.producers.size() ); for( const auto& p : a.producers ) { - p.visit([this](const auto& src){ - producers.emplace_back(detail::shared_convert(producers.get_allocator(), src)); - }); + producers.emplace_back(detail::shared_convert(producers.get_allocator(), p)); } return *this; } @@ -197,9 +247,7 @@ namespace eosio { namespace chain { result.version = version; result.producers.reserve(producers.size()); for( const auto& p : producers ) { - p.visit([&result](const auto& src){ - result.producers.emplace_back(detail::shared_convert(src)); - }); + result.producers.emplace_back(detail::shared_convert(p)); } return result; @@ -219,7 +267,8 @@ namespace eosio { namespace chain { FC_REFLECT( eosio::chain::legacy::producer_key, (producer_name)(block_signing_key) ) FC_REFLECT( eosio::chain::legacy::producer_schedule_type, (version)(producers) ) -FC_REFLECT( eosio::chain::producer_authority_v0, (producer_name)(threshold)(keys) ) +FC_REFLECT( eosio::chain::block_signing_authority_v0, (threshold)(keys)) +FC_REFLECT( eosio::chain::producer_authority, (name)(authority) ) FC_REFLECT( eosio::chain::producer_authority_schedule, (version)(producers) ) FC_REFLECT_DERIVED( eosio::chain::producer_schedule_change_extension, (eosio::chain::producer_authority_schedule), ) FC_REFLECT( eosio::chain::shared_producer_authority_schedule, (version)(producers) ) diff --git a/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp b/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp index 40b287e77af..bdbd36276a0 100644 --- a/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp +++ b/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp @@ -242,11 +242,18 @@ datastream& operator<<( } template -datastream& operator<<(datastream& ds, const history_serial_wrapper& obj) { - fc::raw::pack(ds, as_type(obj.obj.producer_name.value)); +datastream& operator<<(datastream& ds, + const history_serial_wrapper& obj) { fc::raw::pack(ds, as_type(obj.obj.threshold)); history_serialize_container(ds, obj.db, as_type>(obj.obj.keys)); + +} + +template +datastream& operator<<(datastream& ds, const history_serial_wrapper& obj) { + fc::raw::pack(ds, as_type(obj.obj.name.value)); + fc::raw::pack(ds, as_type(obj.obj.authority)); return ds; } diff --git a/plugins/state_history_plugin/state_history_plugin_abi.cpp b/plugins/state_history_plugin/state_history_plugin_abi.cpp index ff6eec427bb..a18d15968bd 100644 --- a/plugins/state_history_plugin/state_history_plugin_abi.cpp +++ b/plugins/state_history_plugin/state_history_plugin_abi.cpp @@ -312,12 +312,17 @@ extern const char* const state_history_plugin_abi = R"({ ] }, { - "name": "producer_authority_v0", "fields": [ - { "type": "name", "name": "producer_name" }, + "name": "block_signing_authority_v0", "fields": [ { "type": "uint32", "name": "weight" } { "type": "key_weight[]", "name": "keys" } ] }, + { + "name": "producer_authority", "fields": [ + { "type": "name", "name": "name" }, + { "type": "block_signing_authority", "name": "authority" } + ] + }, { "name": "producer_authority_schedule", "fields": [ { "type": "uint32", "name": "version" }, @@ -525,7 +530,7 @@ extern const char* const state_history_plugin_abi = R"({ { "name": "resource_limits_ratio", "types": ["resource_limits_ratio_v0"] }, { "name": "elastic_limit_parameters", "types": ["elastic_limit_parameters_v0"] }, { "name": "resource_limits_config", "types": ["resource_limits_config_v0"] }, - { "name": "producer_authority", "types": ["producer_authority_v0"] }, + { "name": "block_signing_authority", "types": ["block_signing_authority_v0"] }, ], "tables": [ { "name": "account", "type": "account", "key_names": ["name"] }, diff --git a/plugins/test_control_plugin/test_control_plugin.cpp b/plugins/test_control_plugin/test_control_plugin.cpp index dbde59853ef..3c26344c8e5 100644 --- a/plugins/test_control_plugin/test_control_plugin.cpp +++ b/plugins/test_control_plugin/test_control_plugin.cpp @@ -64,7 +64,8 @@ void test_control_plugin_impl::accepted_block(const chain::block_state_ptr& bsp) void test_control_plugin_impl::process_next_block_state(const chain::block_state_ptr& bsp) { const auto block_time = _chain.head_block_time() + fc::microseconds(chain::config::block_interval_us); - const auto producer_name = bsp->get_scheduled_producer(block_time).producer_name; + const auto& producer_authority = bsp->get_scheduled_producer(block_time); + const auto producer_name = producer_authority.visit([](const auto& a){ return a.producer_name; }); if (_producer != account_name()) ilog("producer ${cprod}, looking for ${lprod}", ("cprod", producer_name.to_string())("lprod", _producer.to_string())); From 98abd10c61804a281f4cc25b852c23be1fe702f5 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 10 May 2019 18:15:28 -0400 Subject: [PATCH 03/36] more fixing up stuff: wasm interface --- libraries/chain/apply_context.cpp | 2 +- .../chain/include/eosio/chain/controller.hpp | 2 +- libraries/chain/wasm_interface.cpp | 30 +++++++++++++------ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index dc7687cf05c..79022ca1dec 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -583,7 +583,7 @@ vector apply_context::get_active_producers() const { vector accounts; accounts.reserve( ap.producers.size() ); for(const auto& producer : ap.producers ) - accounts.push_back(producer.producer_name); + accounts.push_back(producer.name); return accounts; } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index daa8be2c313..d9606e5dd0b 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -255,7 +255,7 @@ namespace eosio { namespace chain { bool is_known_unexpired_transaction( const transaction_id_type& id) const; - int64_t set_proposed_producers( vector producers ); + int64_t set_proposed_producers( vector producers ); bool light_validation_allowed(bool replay_opts_disabled_by_policy) const; bool skip_auth_check()const; diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 48b33764f8e..292845128b6 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -177,20 +177,20 @@ class privileged_api : public context_aware_api { int64_t set_proposed_producers( array_ptr packed_producer_schedule, size_t datalen) { datastream ds( packed_producer_schedule, datalen ); - vector producers; + vector producers; - if ( context.control.is_builtin_activated( builtin_protocol_feature_t::one_of_n_block_signatures )) { + if ( context.control.is_builtin_activated( builtin_protocol_feature_t::wtmsig_block_signatures )) { fc::raw::unpack(ds, producers); } else { - vector old_version; + vector old_version; fc::raw::unpack(ds, old_version); /* * Up-convert the producers */ for ( const auto& p: old_version ) { - producers.emplace_back(producer_keys{ p.producer_name, {p.block_signing_key}}); + producers.emplace_back(producer_authority{ p.producer_name, block_signing_authority_v0{ 1, {{p.block_signing_key, 1}} } } ); } } @@ -204,13 +204,25 @@ class privileged_api : public context_aware_api { // check that producers are unique std::set unique_producers; for (const auto& p: producers) { - EOS_ASSERT( context.is_account(p.producer_name), wasm_execution_error, "producer schedule includes a nonexisting account" ); + EOS_ASSERT( context.is_account(p.name), wasm_execution_error, "producer schedule includes a nonexisting account" ); + + p.authority.visit([](const auto& a) { + uint32_t sum_weights = 0; + for (const auto& kw: a.keys ) { + EOS_ASSERT( kw.key.valid(), wasm_execution_error, "producer schedule includes an invalid key" ); + + if (std::numeric_limits::max() - sum_weights <= kw.weight) { + sum_weights = std::numeric_limits::max(); + } else { + sum_weights += kw.weight; + } + } + + EOS_ASSERT( sum_weights >= a.threshold, wasm_execution_error, "producer schedule includes an unsatisfiable authority" ); + }); - for (const auto& k: p.block_signing_keys) { - EOS_ASSERT( k.valid(), wasm_execution_error, "producer schedule includes an invalid key" ); - } - unique_producers.insert(p.producer_name); + unique_producers.insert(p.name); } EOS_ASSERT( producers.size() == unique_producers.size(), wasm_execution_error, "duplicate producer name in producer schedule" ); From c35b9449cf34b2d3e372ff20e3cdfcb4dfe6fb37 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Mon, 13 May 2019 17:45:37 -0400 Subject: [PATCH 04/36] more fixes, into tester. need to go back and add new intrinsic --- libraries/chain/block_header_state.cpp | 4 +- libraries/chain/controller.cpp | 34 +++++++---- libraries/chain/fork_database.cpp | 4 +- .../eosio/chain/block_header_state.hpp | 2 +- .../chain/include/eosio/chain/controller.hpp | 8 +-- .../eosio/chain/global_property_object.hpp | 29 +++++++++ .../include/eosio/chain/producer_schedule.hpp | 60 ++++++++++++++++--- libraries/chain/include/eosio/chain/types.hpp | 3 + .../testing/include/eosio/testing/tester.hpp | 5 +- libraries/testing/tester.cpp | 9 ++- 10 files changed, 122 insertions(+), 36 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 70e89954257..b1626f657c0 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -167,7 +167,7 @@ namespace eosio { namespace chain { signed_block_header pending_block_header_state::make_block_header( const checksum256_type& transaction_mroot, const checksum256_type& action_mroot, - std::optional&& new_producers, + const optional& new_producers, vector&& new_protocol_feature_activations, const protocol_feature_set& pfs )const @@ -198,7 +198,7 @@ namespace eosio { namespace chain { // add the header extension to update the block schedule h.header_extensions.emplace_back( producer_schedule_change_extension::extension_id(), - fc::raw::pack( producer_schedule_change_extension( std::move(*new_producers) ) ) + fc::raw::pack( producer_schedule_change_extension( *new_producers ) ) ); } else { legacy::producer_schedule_type downgraded_producers; diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 22c31ad18a7..c1e3df7e50f 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -125,6 +125,9 @@ struct assembled_block { pending_block_header_state _pending_block_header_state; vector _trx_metas; signed_block_ptr _unsigned_block; + + // if the _unsigned_block pre-dates block-signing authorities this may be present. + optional _new_producer_authority_cache; }; struct completed_block { @@ -427,7 +430,7 @@ struct controller_impl { */ void initialize_blockchain_state() { wlog( "Initializing new blockchain with genesis state" ); - producer_authority_schedule initial_schedule{ 0, {producer_authority_v0{config::system_account_name, 1, {{conf.genesis.initial_key, 1}} } } }; + producer_authority_schedule initial_schedule = { 0, { producer_authority{config::system_account_name, block_signing_authority_v0{ 1, {{conf.genesis.initial_key, 1}} } } } }; block_header_state genheader; genheader.active_schedule = initial_schedule; @@ -1537,8 +1540,9 @@ struct controller_impl { auto block_ptr = std::make_shared( pbhs.make_block_header( calculate_trx_merkle(), calculate_action_merkle(), - std::move( bb._new_pending_producer_schedule ), - std::move( bb._new_protocol_feature_activations ) + bb._new_pending_producer_schedule, + std::move( bb._new_protocol_feature_activations ), + protocol_features.get_protocol_feature_set() ) ); block_ptr->transactions = std::move( bb._pending_trx_receipts ); @@ -1566,7 +1570,8 @@ struct controller_impl { id, std::move( bb._pending_block_header_state ), std::move( bb._pending_trx_metas ), - std::move( block_ptr ) + std::move( block_ptr ), + std::move( bb._new_pending_producer_schedule ) }; } FC_CAPTURE_AND_RETHROW() } /// finalize_block @@ -1746,6 +1751,7 @@ struct controller_impl { std::move( ab._pending_block_header_state ), b, std::move( ab._trx_metas ), + protocol_features.get_protocol_feature_set(), []( block_timestamp_type timestamp, const flat_set& cur_features, const vector& new_features ) @@ -1782,6 +1788,7 @@ struct controller_impl { return std::make_shared( *prev, move( b ), + control->protocol_features.get_protocol_feature_set(), [control]( block_timestamp_type timestamp, const flat_set& cur_features, const vector& new_features ) @@ -1837,6 +1844,7 @@ struct controller_impl { auto bsp = std::make_shared( *head, b, + protocol_features.get_protocol_feature_set(), [this]( block_timestamp_type timestamp, const flat_set& cur_features, const vector& new_features ) @@ -1982,7 +1990,7 @@ struct controller_impl { auto update_permission = [&]( auto& permission, auto threshold ) { auto auth = authority( threshold, {}, {}); for( auto& p : producers ) { - auth.accounts.push_back({{p.producer_name, config::active_name}, 1}); + auth.accounts.push_back({{p.name, config::active_name}, 1}); } if( static_cast(permission.auth) != auth ) { // TODO: use a more efficient way to check that authority has not changed @@ -2442,6 +2450,7 @@ block_state_ptr controller::finalize_block( const std::functionprotocol_features.get_protocol_feature_set(), []( block_timestamp_type timestamp, const flat_set& cur_features, const vector& new_features ) @@ -2602,13 +2611,13 @@ account_name controller::pending_block_producer()const { return my->pending->get_pending_block_header_state().producer; } -flat_set controller::pending_block_signing_keys()const { +const block_signing_authority& controller::pending_block_signing_authority()const { EOS_ASSERT( my->pending, block_validate_exception, "no pending block" ); if( my->pending->_block_stage.contains() ) - return my->pending->_block_stage.get()._block_state->block_signing_keys; + return my->pending->_block_stage.get()._block_state->valid_block_signing_authority; - return my->pending->get_pending_block_header_state().block_signing_keys; + return my->pending->get_pending_block_header_state().valid_block_signing_authority; } optional controller::pending_producer_block_id()const { @@ -2785,7 +2794,7 @@ const producer_authority_schedule& controller::active_producers()const { return my->pending->get_pending_block_header_state().active_schedule; } -const producer_authority_schedule& controller::pending_producers()const { +const producer_authority_schedule& controller::pending_producers()const { if( !(my->pending) ) return my->head->pending_schedule.schedule; @@ -2798,9 +2807,10 @@ const producer_authority_schedule& controller::pending_producers()const { if (exts.count(producer_schedule_change_extension::extension_id())) return exts.lower_bound(producer_schedule_change_extension::extension_id())->second.get(); } else { - const auto& np = my->pending->_block_stage.get()._unsigned_block->new_producers; - if( np ) - return *np; + const auto& new_prods_cache = my->pending->_block_stage.get()._new_producer_authority_cache; + if( new_prods_cache ) { + return *new_prods_cache; + } } } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 1fe7459dfae..611a46732e4 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -314,8 +314,8 @@ namespace eosio { namespace chain { try { const auto& exts = n->header_exts; - if( exts.size() > 0 ) { - const auto& new_protocol_features = exts.front().get().protocol_features; + if( exts.count(protocol_feature_activation::extension_id()) > 0 ) { + const auto& new_protocol_features = exts.lower_bound(protocol_feature_activation::extension_id())->second.get().protocol_features; validator( n->header.timestamp, prev_bh->activated_protocol_features->protocol_features, new_protocol_features ); } } EOS_RETHROW_EXCEPTIONS( fork_database_exception, "serialized fork database is incompatible with configured protocol features" ) diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 46e8145bf57..02de9adad44 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -40,7 +40,7 @@ struct pending_block_header_state : public detail::block_header_state_common { signed_block_header make_block_header( const checksum256_type& transaction_mroot, const checksum256_type& action_mroot, - std::optional&& new_producers, + const optional& new_producers, vector&& new_protocol_feature_activations, const protocol_feature_set& pfs)const; diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index d9606e5dd0b..7611a91d261 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -205,10 +205,10 @@ namespace eosio { namespace chain { time_point fork_db_pending_head_block_time()const; account_name fork_db_pending_head_block_producer()const; - time_point pending_block_time()const; - account_name pending_block_producer()const; - public_key_type pending_block_signing_key()const; - optional pending_producer_block_id()const; + time_point pending_block_time()const; + account_name pending_block_producer()const; + const block_signing_authority& pending_block_signing_authority()const; + optional pending_producer_block_id()const; const vector& get_pending_trx_receipts()const; diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index a8d0bac9cb1..56281da86c3 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "multi_index_includes.hpp" @@ -43,6 +44,30 @@ namespace eosio { namespace chain { > >; + struct snapshot_global_property_object { + optional proposed_schedule_block_num; + producer_authority_schedule proposed_schedule; + chain_config configuration; + }; + + namespace detail { + template<> + struct snapshot_row_traits { + using value_type = global_property_object; + using snapshot_type = snapshot_global_property_object; + + static snapshot_global_property_object to_snapshot_row( const global_property_object& value, const chainbase::database& ) { + return {value.proposed_schedule_block_num, (producer_authority_schedule)value.proposed_schedule, value.configuration}; + } + + static void from_snapshot_row( snapshot_global_property_object&& row, global_property_object& value, chainbase::database& ) { + value.proposed_schedule_block_num = row.proposed_schedule_block_num; + value.proposed_schedule = row.proposed_schedule; + value.configuration = row.configuration; + } + }; + } + /** * @class dynamic_global_property_object * @brief Maintains global state information that frequently change @@ -76,6 +101,10 @@ FC_REFLECT(eosio::chain::global_property_object, (proposed_schedule_block_num)(proposed_schedule)(configuration) ) +FC_REFLECT(eosio::chain::snapshot_global_property_object, + (proposed_schedule_block_num)(proposed_schedule)(configuration) + ) + FC_REFLECT(eosio::chain::dynamic_global_property_object, (global_action_sequence) ) diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index d79bd127b69..46801564099 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace eosio { namespace chain { @@ -76,6 +77,11 @@ namespace eosio { namespace chain { }; struct shared_block_signing_authority_v0 { + shared_block_signing_authority_v0( const shared_block_signing_authority_v0& ) = default; + shared_block_signing_authority_v0( shared_block_signing_authority_v0&& ) = default; + shared_block_signing_authority_v0& operator= ( shared_block_signing_authority_v0 && ) = default; + shared_block_signing_authority_v0& operator= ( const shared_block_signing_authority_v0 & ) = default; + shared_block_signing_authority_v0( chainbase::allocator alloc ) :keys(alloc){} @@ -93,8 +99,19 @@ namespace eosio { namespace chain { using shared_block_signing_authority = static_variant; struct shared_producer_authority { - name name; - shared_block_signing_authority authority; + shared_producer_authority() = default; + shared_producer_authority( const shared_producer_authority& ) = default; + shared_producer_authority( shared_producer_authority&& ) = default; + shared_producer_authority& operator= ( shared_producer_authority && ) = default; + shared_producer_authority& operator= ( const shared_producer_authority & ) = default; + + shared_producer_authority( const name& name, shared_block_signing_authority&& authority ) + :name(name) + ,authority(std::forward(authority)) + {} + + name name; + shared_block_signing_authority authority; friend bool operator == ( const shared_producer_authority& lhs, const shared_producer_authority& rhs ) { return tie( lhs.name, lhs.authority ) == tie( rhs.name, rhs.authority ); @@ -150,13 +167,11 @@ namespace eosio { namespace chain { template<> struct shared_converter { static shared_producer_authority convert (chainbase::allocator alloc, const producer_authority& src) { - shared_producer_authority result; - result.name = src.name; - result.authority = src.authority.visit([&result, &alloc](const auto& a) { + auto authority = src.authority.visit([&alloc](const auto& a) { return shared_convert(alloc, a); }); - return result; + return shared_producer_authority(src.name, std::move(authority)); } }; @@ -166,7 +181,7 @@ namespace eosio { namespace chain { static producer_authority convert (const shared_producer_authority& src) { producer_authority result; result.name = src.name; - result.authority = src.authority.visit([&result](const auto& a) { + result.authority = src.authority.visit([](const auto& a) { return shared_convert(a); }); @@ -189,6 +204,11 @@ namespace eosio { namespace chain { producers.emplace_back(producer_authority{ p.producer_name, block_signing_authority_v0{ 1, {{p.block_signing_key, 1}} } }); } + producer_authority_schedule( uint32_t version, std::initializer_list producers ) + :version(version) + ,producers(producers) + {} + uint32_t version = 0; ///< sequentially incrementing version number vector producers; @@ -222,7 +242,7 @@ namespace eosio { namespace chain { producer_schedule_change_extension(const producer_schedule_change_extension&) = default; producer_schedule_change_extension( producer_schedule_change_extension&& ) = default; - producer_schedule_change_extension( producer_authority_schedule&& sched ) + producer_schedule_change_extension( const producer_authority_schedule& sched ) :producer_authority_schedule(sched) {} }; @@ -232,6 +252,12 @@ namespace eosio { namespace chain { shared_producer_authority_schedule( chainbase::allocator alloc ) :producers(alloc){} + shared_producer_authority_schedule( const shared_producer_authority_schedule& ) = default; + shared_producer_authority_schedule( shared_producer_authority_schedule&& ) = default; + shared_producer_authority_schedule& operator= ( shared_producer_authority_schedule && ) = default; + shared_producer_authority_schedule& operator= ( const shared_producer_authority_schedule & ) = default; + + shared_producer_authority_schedule& operator=( const producer_authority_schedule& a ) { version = a.version; producers.clear(); @@ -262,6 +288,21 @@ namespace eosio { namespace chain { shared_vector producers; }; + inline bool operator == ( const producer_authority& pa, const shared_producer_authority& pb ) + { + if(pa.name != pb.name) return false; + if(pa.authority.which() != pb.authority.which()) return false; + + bool authority_matches = pa.authority.visit([&pb]( const auto& lhs ){ + return pb.authority.visit( [&lhs](const auto& rhs ) { + if (lhs.threshold != rhs.threshold) return false; + return std::equal(lhs.keys.cbegin(), lhs.keys.cend(), rhs.keys.cbegin(), rhs.keys.cend()); + }); + }); + + if (!authority_matches) return false; + return true; + } } } /// eosio::chain @@ -271,5 +312,8 @@ FC_REFLECT( eosio::chain::block_signing_authority_v0, (threshold)(keys)) FC_REFLECT( eosio::chain::producer_authority, (name)(authority) ) FC_REFLECT( eosio::chain::producer_authority_schedule, (version)(producers) ) FC_REFLECT_DERIVED( eosio::chain::producer_schedule_change_extension, (eosio::chain::producer_authority_schedule), ) + +FC_REFLECT( eosio::chain::shared_block_signing_authority_v0, (threshold)(keys)) +FC_REFLECT( eosio::chain::shared_producer_authority, (name)(authority) ) FC_REFLECT( eosio::chain::shared_producer_authority_schedule, (version)(producers) ) diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index 2063e7ce3e0..bb1cf10d843 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -88,6 +88,8 @@ namespace eosio { namespace chain { using private_key_type = fc::crypto::private_key; using signature_type = fc::crypto::signature; + struct void_t{}; + using chainbase::allocator; using shared_string = boost::interprocess::basic_string, allocator>; template @@ -373,3 +375,4 @@ namespace eosio { namespace chain { } } // eosio::chain +FC_REFLECT_EMPTY( eosio::chain::void_t ) diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index cda857ff2c2..fef4d5e2042 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -169,8 +169,9 @@ namespace eosio { namespace testing { void set_before_preactivate_bios_contract(); void set_bios_contract(); - vector get_producer_keys( const vector& producer_names )const; - transaction_trace_ptr set_producers(const vector& producer_names); + + vector get_producer_authorities( const vector& producer_names )const; + transaction_trace_ptr set_producers(const vector& producer_names); void link_authority( account_name account, account_name code, permission_name req, action_name type = "" ); void unlink_authority( account_name account, account_name code, action_name type = "" ); diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index 7bc83662dc3..3281059360d 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -951,18 +951,17 @@ namespace eosio { namespace testing { } - vector base_tester::get_producer_keys( const vector& producer_names )const { + vector base_tester::get_producer_authorities( const vector& producer_names )const { // Create producer schedule - vector schedule; + vector schedule; for (auto& producer_name: producer_names) { - producer_key pk = { producer_name, get_public_key( producer_name, "active" )}; - schedule.emplace_back(pk); + schedule.emplace_back(producer_authority{ producer_name, block_signing_authority_v0{1, {{ get_public_key( producer_name, "active" ), 1}} } }); } return schedule; } transaction_trace_ptr base_tester::set_producers(const vector& producer_names) { - auto schedule = get_producer_keys( producer_names ); + auto schedule = get_producer_authorities( producer_names ); return push_action( config::system_account_name, N(setprods), config::system_account_name, fc::mutable_variant_object()("schedule", schedule)); From 304fe31f7b0385cfc790067a3c547f7b9e278fb8 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 16 May 2019 11:36:38 -0400 Subject: [PATCH 05/36] So many fixes. renamed `name` back to `producer_name`. split intrinsic to solve ABI versioning and contract upgrade issues. TODO: implement intrinsic changes, implement mult-sig signed blocks, fix tests etc --- libraries/chain/apply_context.cpp | 2 +- libraries/chain/block_header_state.cpp | 56 +- libraries/chain/block_state.cpp | 2 +- libraries/chain/controller.cpp | 4 +- .../eosio/chain/block_header_state.hpp | 6 +- .../chain/include/eosio/chain/block_state.hpp | 2 +- .../chain/include/eosio/chain/controller.hpp | 4 +- .../include/eosio/chain/producer_schedule.hpp | 42 +- libraries/chain/wasm_interface.cpp | 4 +- libraries/fc | 2 +- .../testing/include/eosio/testing/tester.hpp | 3 +- libraries/testing/tester.cpp | 28 +- plugins/chain_plugin/chain_plugin.cpp | 2 +- .../eosio/producer_plugin/producer_plugin.hpp | 1 - plugins/producer_plugin/producer_plugin.cpp | 58 +- .../state_history_serialization.hpp | 2 +- .../state_history_plugin_abi.cpp | 2 +- .../test_control_plugin.cpp | 2 +- unittests/bootseq_tests.cpp | 20 +- unittests/contracts.hpp.in | 3 +- unittests/contracts/CMakeLists.txt | 1 + unittests/contracts/eosio.bios/eosio.bios.abi | 56 +- .../contracts/eosio.bios/eosio.bios.wasm | Bin 17777 -> 23359 bytes .../eosio.bios/eosio.bios.abi | 551 ++++++++++++++++++ .../eosio.bios/eosio.bios.wasm | Bin 0 -> 17777 bytes unittests/forked_tests.cpp | 10 +- unittests/producer_schedule_tests.cpp | 44 +- unittests/protocol_feature_tests.cpp | 2 +- 28 files changed, 740 insertions(+), 169 deletions(-) create mode 100644 unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/eosio.bios.abi create mode 100755 unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/eosio.bios.wasm diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index 79022ca1dec..dc7687cf05c 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -583,7 +583,7 @@ vector apply_context::get_active_producers() const { vector accounts; accounts.reserve( ap.producers.size() ); for(const auto& producer : ap.producers ) - accounts.push_back(producer.name); + accounts.push_back(producer.producer_name); return accounts; } diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index b1626f657c0..dd2e3b9fa07 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -42,11 +42,11 @@ namespace eosio { namespace chain { auto proauth = get_scheduled_producer(when); - auto itr = producer_to_last_produced.find( proauth.name ); + auto itr = producer_to_last_produced.find( proauth.producer_name ); if( itr != producer_to_last_produced.end() ) { EOS_ASSERT( itr->second < (block_num+1) - num_prev_blocks_to_confirm, producer_double_confirm, "producer ${prod} double-confirming known range", - ("prod", proauth.name)("num", block_num+1) + ("prod", proauth.producer_name)("num", block_num+1) ("confirmed", num_prev_blocks_to_confirm)("last_produced", itr->second) ); } @@ -58,7 +58,7 @@ namespace eosio { namespace chain { result.prev_activated_protocol_features = activated_protocol_features; result.valid_block_signing_authority = proauth.authority; - result.producer = proauth.name; + result.producer = proauth.producer_name; result.blockroot_merkle = blockroot_merkle; result.blockroot_merkle.append( id ); @@ -108,7 +108,7 @@ namespace eosio { namespace chain { } result.dpos_proposed_irreversible_blocknum = new_dpos_proposed_irreversible_blocknum; - result.dpos_irreversible_blocknum = calc_dpos_last_irreversible( proauth.name ); + result.dpos_irreversible_blocknum = calc_dpos_last_irreversible( proauth.producer_name ); result.prev_pending_schedule = pending_schedule; @@ -120,32 +120,32 @@ namespace eosio { namespace chain { flat_map new_producer_to_last_produced; for( const auto& pro : result.active_schedule.producers ) { - if( pro.name == proauth.name ) { - new_producer_to_last_produced[pro.name] = result.block_num; + if( pro.producer_name == proauth.producer_name ) { + new_producer_to_last_produced[pro.producer_name] = result.block_num; } else { - auto existing = producer_to_last_produced.find( pro.name ); + auto existing = producer_to_last_produced.find( pro.producer_name ); if( existing != producer_to_last_produced.end() ) { - new_producer_to_last_produced[pro.name] = existing->second; + new_producer_to_last_produced[pro.producer_name] = existing->second; } else { - new_producer_to_last_produced[pro.name] = result.dpos_irreversible_blocknum; + new_producer_to_last_produced[pro.producer_name] = result.dpos_irreversible_blocknum; } } } - new_producer_to_last_produced[proauth.name] = result.block_num; + new_producer_to_last_produced[proauth.producer_name] = result.block_num; result.producer_to_last_produced = std::move( new_producer_to_last_produced ); flat_map new_producer_to_last_implied_irb; for( const auto& pro : result.active_schedule.producers ) { - if( pro.name == proauth.name ) { - new_producer_to_last_implied_irb[pro.name] = dpos_proposed_irreversible_blocknum; + if( pro.producer_name == proauth.producer_name ) { + new_producer_to_last_implied_irb[pro.producer_name] = dpos_proposed_irreversible_blocknum; } else { - auto existing = producer_to_last_implied_irb.find( pro.name ); + auto existing = producer_to_last_implied_irb.find( pro.producer_name ); if( existing != producer_to_last_implied_irb.end() ) { - new_producer_to_last_implied_irb[pro.name] = existing->second; + new_producer_to_last_implied_irb[pro.producer_name] = existing->second; } else { - new_producer_to_last_implied_irb[pro.name] = result.dpos_irreversible_blocknum; + new_producer_to_last_implied_irb[pro.producer_name] = result.dpos_irreversible_blocknum; } } } @@ -156,9 +156,9 @@ namespace eosio { namespace chain { } else { result.active_schedule = active_schedule; result.producer_to_last_produced = producer_to_last_produced; - result.producer_to_last_produced[proauth.name] = block_num; + result.producer_to_last_produced[proauth.producer_name] = block_num; result.producer_to_last_implied_irb = producer_to_last_implied_irb; - result.producer_to_last_implied_irb[proauth.name] = dpos_proposed_irreversible_blocknum; + result.producer_to_last_implied_irb[proauth.producer_name] = dpos_proposed_irreversible_blocknum; } return result; @@ -206,7 +206,7 @@ namespace eosio { namespace chain { for (const auto &p : new_producers->producers) { p.authority.visit([&downgraded_producers, &p](const auto& auth){ EOS_ASSERT(auth.keys.size() == 1 && auth.keys.front().weight == auth.threshold, producer_schedule_exception, "multisig block signing present before enabled!"); - downgraded_producers.producers.emplace_back(legacy::producer_key{p.name, auth.keys.front().key}); + downgraded_producers.producers.emplace_back(legacy::producer_key{p.producer_name, auth.keys.front().key}); }); } h.new_producers = downgraded_producers; @@ -337,7 +337,7 @@ namespace eosio { namespace chain { const std::function&, const vector& )>& validator, - const std::function& signer + const signer_callback_type& signer )&& { auto result = std::move(*this)._finish_next( h, pfs, validator ); @@ -370,19 +370,13 @@ namespace eosio { namespace chain { return digest_type::hash( std::make_pair(header_bmroot, pending_schedule.schedule_hash) ); } - static bool key_is_relevant( const block_signing_authority& authority, const public_key_type& key ) { - return authority.visit([&key](const auto &a) { - return std::find_if(a.keys.begin(), a.keys.end(), [&key](const auto& k ){ - return k.key == key; - }) != a.keys.end(); - }); - } - - void block_header_state::sign( const std::function& signer ) { + void block_header_state::sign( const signer_callback_type& signer ) { auto d = sig_digest(); - header.producer_signature = signer( d ); + // TODO: modify block to allow multiple sigs + auto sigs = signer( d ); + header.producer_signature = sigs.front(); - EOS_ASSERT( key_is_relevant(valid_block_signing_authority, signee()), wrong_signing_key, "block is signed with unexpected key" ); + EOS_ASSERT( producer_authority::key_is_relevant(signee(), valid_block_signing_authority), wrong_signing_key, "block is signed with unexpected key" ); } public_key_type block_header_state::signee()const { @@ -392,7 +386,7 @@ namespace eosio { namespace chain { void block_header_state::verify_signee( )const { auto signing_key = signee(); - EOS_ASSERT( key_is_relevant(valid_block_signing_authority, signing_key), wrong_signing_key, + EOS_ASSERT( producer_authority::key_is_relevant(signing_key, valid_block_signing_authority), wrong_signing_key, "block not signed by expected key", ("signing_key", signing_key)( "valid_keys", valid_block_signing_authority ) ); } diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index 2c368a4b54e..cb8c1a2e5d1 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -22,7 +22,7 @@ namespace eosio { namespace chain { const std::function&, const vector& )>& validator, - const std::function& signer + const signer_callback_type& signer ) :block_header_state( std::move(cur).finish_next( *b, pfs, validator, signer ) ) ,block( std::move(b) ) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index c1e3df7e50f..e43c7f11256 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1990,7 +1990,7 @@ struct controller_impl { auto update_permission = [&]( auto& permission, auto threshold ) { auto auth = authority( threshold, {}, {}); for( auto& p : producers ) { - auth.accounts.push_back({{p.name, config::active_name}, 1}); + auth.accounts.push_back({{p.producer_name, config::active_name}, 1}); } if( static_cast(permission.auth) != auth ) { // TODO: use a more efficient way to check that authority has not changed @@ -2439,7 +2439,7 @@ void controller::start_block( block_timestamp_type when, block_status::incomplete, optional() ); } -block_state_ptr controller::finalize_block( const std::function& signer_callback ) { +block_state_ptr controller::finalize_block( const signer_callback_type& signer_callback ) { validate_db_available_size(); my->finalize_block(); diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 02de9adad44..6b3deca5dfe 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -6,6 +6,8 @@ namespace eosio { namespace chain { +using signer_callback_type = std::function(const digest_type&)>; + struct block_header_state; namespace detail { @@ -56,7 +58,7 @@ struct pending_block_header_state : public detail::block_header_state_common { const std::function&, const vector& )>& validator, - const std::function& signer )&&; + const signer_callback_type& signer )&&; protected: block_header_state _finish_next( const signed_block_header& h, @@ -103,7 +105,7 @@ struct block_header_state : public detail::block_header_state_common { producer_authority get_scheduled_producer( block_timestamp_type t )const; const block_id_type& prev()const { return header.previous; } digest_type sig_digest()const; - void sign( const std::function& signer ); + void sign( const signer_callback_type& signer ); public_key_type signee()const; void verify_signee()const; diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index e31337d42ec..812176a5c14 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -28,7 +28,7 @@ namespace eosio { namespace chain { const std::function&, const vector& )>& validator, - const std::function& signer + const signer_callback_type& signer ); block_state( pending_block_header_state&& cur, diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 7611a91d261..d06096888d8 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -151,8 +151,8 @@ namespace eosio { namespace chain { */ transaction_trace_ptr push_scheduled_transaction( const transaction_id_type& scheduled, fc::time_point deadline, uint32_t billed_cpu_time_us = 0 ); - block_state_ptr finalize_block( const std::function& signer_callback ); - void sign_block( const std::function& signer_callback ); + block_state_ptr finalize_block( const signer_callback_type& signer_callback ); + void sign_block( const signer_callback_type& signer_callback ); void commit_block(); void pop_block(); diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index 46801564099..ffe249e535c 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -54,6 +54,12 @@ namespace eosio { namespace chain { uint32_t threshold; vector keys; + bool key_is_relevant( const public_key_type& key ) const { + return std::find_if(keys.begin(), keys.end(), [&key](const auto& kw){ + return kw.key == key; + }) != keys.end(); + } + friend bool operator == ( const block_signing_authority_v0& lhs, const block_signing_authority_v0& rhs ) { return tie( lhs.threshold, lhs.keys ) == tie( rhs.threshold, rhs.keys ); } @@ -65,14 +71,24 @@ namespace eosio { namespace chain { using block_signing_authority = static_variant; struct producer_authority { - name name; + name producer_name; block_signing_authority authority; + static bool key_is_relevant( const public_key_type& key, const block_signing_authority& authority ) { + return authority.visit([&key](const auto &a){ + return a.key_is_relevant(key); + }); + } + + bool key_is_relevant( const public_key_type& key ) const { + return key_is_relevant(key, authority); + } + friend bool operator == ( const producer_authority& lhs, const producer_authority& rhs ) { - return tie( lhs.name, lhs.authority ) == tie( rhs.name, rhs.authority ); + return tie( lhs.producer_name, lhs.authority ) == tie( rhs.producer_name, rhs.authority ); } friend bool operator != ( const producer_authority& lhs, const producer_authority& rhs ) { - return tie( lhs.name, lhs.authority ) != tie( rhs.name, rhs.authority ); + return tie( lhs.producer_name, lhs.authority ) != tie( rhs.producer_name, rhs.authority ); } }; @@ -105,19 +121,19 @@ namespace eosio { namespace chain { shared_producer_authority& operator= ( shared_producer_authority && ) = default; shared_producer_authority& operator= ( const shared_producer_authority & ) = default; - shared_producer_authority( const name& name, shared_block_signing_authority&& authority ) - :name(name) + shared_producer_authority( const name& producer_name, shared_block_signing_authority&& authority ) + :producer_name(producer_name) ,authority(std::forward(authority)) {} - name name; + name producer_name; shared_block_signing_authority authority; friend bool operator == ( const shared_producer_authority& lhs, const shared_producer_authority& rhs ) { - return tie( lhs.name, lhs.authority ) == tie( rhs.name, rhs.authority ); + return tie( lhs.producer_name, lhs.authority ) == tie( rhs.producer_name, rhs.authority ); } friend bool operator != ( const shared_producer_authority& lhs, const shared_producer_authority& rhs ) { - return tie( lhs.name, lhs.authority ) != tie( rhs.name, rhs.authority ); + return tie( lhs.producer_name, lhs.authority ) != tie( rhs.producer_name, rhs.authority ); } }; @@ -171,7 +187,7 @@ namespace eosio { namespace chain { return shared_convert(alloc, a); }); - return shared_producer_authority(src.name, std::move(authority)); + return shared_producer_authority(src.producer_name, std::move(authority)); } }; @@ -180,7 +196,7 @@ namespace eosio { namespace chain { struct shared_converter { static producer_authority convert (const shared_producer_authority& src) { producer_authority result; - result.name = src.name; + result.producer_name = src.producer_name; result.authority = src.authority.visit([](const auto& a) { return shared_convert(a); }); @@ -290,7 +306,7 @@ namespace eosio { namespace chain { inline bool operator == ( const producer_authority& pa, const shared_producer_authority& pb ) { - if(pa.name != pb.name) return false; + if(pa.producer_name != pb.producer_name) return false; if(pa.authority.which() != pb.authority.which()) return false; bool authority_matches = pa.authority.visit([&pb]( const auto& lhs ){ @@ -309,11 +325,11 @@ namespace eosio { namespace chain { FC_REFLECT( eosio::chain::legacy::producer_key, (producer_name)(block_signing_key) ) FC_REFLECT( eosio::chain::legacy::producer_schedule_type, (version)(producers) ) FC_REFLECT( eosio::chain::block_signing_authority_v0, (threshold)(keys)) -FC_REFLECT( eosio::chain::producer_authority, (name)(authority) ) +FC_REFLECT( eosio::chain::producer_authority, (producer_name)(authority) ) FC_REFLECT( eosio::chain::producer_authority_schedule, (version)(producers) ) FC_REFLECT_DERIVED( eosio::chain::producer_schedule_change_extension, (eosio::chain::producer_authority_schedule), ) FC_REFLECT( eosio::chain::shared_block_signing_authority_v0, (threshold)(keys)) -FC_REFLECT( eosio::chain::shared_producer_authority, (name)(authority) ) +FC_REFLECT( eosio::chain::shared_producer_authority, (producer_name)(authority) ) FC_REFLECT( eosio::chain::shared_producer_authority_schedule, (version)(producers) ) diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 292845128b6..55981a533b0 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -204,7 +204,7 @@ class privileged_api : public context_aware_api { // check that producers are unique std::set unique_producers; for (const auto& p: producers) { - EOS_ASSERT( context.is_account(p.name), wasm_execution_error, "producer schedule includes a nonexisting account" ); + EOS_ASSERT( context.is_account(p.producer_name), wasm_execution_error, "producer schedule includes a nonexisting account" ); p.authority.visit([](const auto& a) { uint32_t sum_weights = 0; @@ -222,7 +222,7 @@ class privileged_api : public context_aware_api { }); - unique_producers.insert(p.name); + unique_producers.insert(p.producer_name); } EOS_ASSERT( producers.size() == unique_producers.size(), wasm_execution_error, "duplicate producer name in producer schedule" ); diff --git a/libraries/fc b/libraries/fc index 9a008f71c05..c83e208bce9 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 9a008f71c05b6f4105fb6340f13a8344d1335a5d +Subproject commit c83e208bce9f86e92421056e82b92283145884e4 diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index fef4d5e2042..9ade5bf9971 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -56,7 +56,7 @@ namespace boost { namespace test_tools { namespace tt_detail { } } } namespace eosio { namespace testing { - enum class setup_policy { + enum class setup_policy { none, old_bios_only, preactivate_feature_only, @@ -168,6 +168,7 @@ namespace eosio { namespace testing { } void set_before_preactivate_bios_contract(); + void set_before_producer_authority_bios_contract(); void set_bios_contract(); vector get_producer_authorities( const vector& producer_names )const; diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index 3281059360d..bbd3dd2fe1e 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -178,7 +178,7 @@ namespace eosio { namespace testing { case setup_policy::preactivate_feature_and_new_bios: { schedule_preactivate_protocol_feature(); produce_block(); - set_bios_contract(); + set_before_producer_authority_bios_contract(); break; } case setup_policy::full: { @@ -308,18 +308,19 @@ namespace eosio { namespace testing { FC_ASSERT( control->is_building_block(), "must first start a block before it can be finished" ); auto producer = control->head_block_state()->get_scheduled_producer( control->pending_block_time() ); - private_key_type priv_key; - // Check if signing private key exist in the list - auto private_key_itr = block_signing_private_keys.find( *producer.block_signing_keys.begin() ); - if( private_key_itr == block_signing_private_keys.end() ) { - // If it's not found, default to active k1 key - priv_key = get_private_key( producer.producer_name, "active" ); - } else { - priv_key = private_key_itr->second; - } + private_key_type priv_key = producer.authority.visit([this, &producer](const block_signing_authority_v0& a){ + for (const auto& k: a.keys) { + auto private_key_itr = block_signing_private_keys.find( k.key ); + if (private_key_itr != block_signing_private_keys.end()) { + return private_key_itr->second; + } + } + + return get_private_key( producer.producer_name, "active" ); + }); control->finalize_block( [&]( digest_type d ) { - return priv_key.sign(d); + return std::vector{priv_key.sign(d)}; } ); control->commit_block(); @@ -945,6 +946,11 @@ namespace eosio { namespace testing { set_abi(config::system_account_name, contracts::before_preactivate_eosio_bios_abi().data()); } + void base_tester::set_before_producer_authority_bios_contract() { + set_code(config::system_account_name, contracts::before_producer_authority_eosio_bios_wasm()); + set_abi(config::system_account_name, contracts::before_producer_authority_eosio_bios_abi().data()); + } + void base_tester::set_bios_contract() { set_code(config::system_account_name, contracts::eosio_bios_wasm()); set_abi(config::system_account_name, contracts::eosio_bios_abi().data()); diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index c093124ef90..4daac8f3ac2 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1705,7 +1705,7 @@ read_only::get_producers_result read_only::get_producers( const read_only::get_p for (auto p : db.active_producers().producers) { fc::variant row = fc::mutable_variant_object() ("owner", p.producer_name) - ("producer_key", p.block_signing_keys) + ("producer_authority", p.authority) ("url", "") ("total_votes", 0.0f); diff --git a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp index 5d1335407ca..839de9ac01d 100644 --- a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp +++ b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp @@ -114,7 +114,6 @@ class producer_plugin : public appbase::plugin { get_account_ram_corrections_result get_account_ram_corrections( const get_account_ram_corrections_params& params ) const; - signal confirmed_block; private: std::shared_ptr my; }; diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 044caa2757e..307dc13d9b8 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -264,28 +264,6 @@ class producer_plugin_impl : public std::enable_shared_from_thisheader.producer ) { - auto itr = std::find_if( active_producer_to_signing_key.begin(), active_producer_to_signing_key.end(), - [&](const producer_key& k){ return k.producer_name == producer; } ); - if( itr != active_producer_to_signing_key.end() ) { - auto private_key_itr = _signature_providers.find( itr->block_signing_key ); - if( private_key_itr != _signature_providers.end() ) { - auto d = bsp->sig_digest(); - auto sig = private_key_itr->second( d ); - _last_signed_block_time = bsp->header.timestamp; - _last_signed_block_num = bsp->block_num; - - // ilog( "${n} confirmed", ("n",name(producer)) ); - _self->confirmed_block( { bsp->id, d, producer, sig } ); - } - } - } - } ) ); - // since the watermark has to be set before a block is created, we are looking into the future to // determine the new schedule to identify producers that have become active chain::controller& chain = chain_plug->chain(); @@ -1312,7 +1290,11 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { // Not our turn const auto& scheduled_producer = hbs->get_scheduled_producer(block_time); auto currrent_watermark_itr = _producer_watermarks.find(scheduled_producer.producer_name); - auto signature_provider_itr = _signature_providers.find(scheduled_producer.block_signing_key); + + auto num_relevant_signatures = std::count_if(_signature_providers.begin(), _signature_providers.end(), [&scheduled_producer](const auto& p){ + return scheduled_producer.key_is_relevant(p.first); + }); + auto irreversible_block_age = get_irreversible_block_age(); // If the next block production opportunity is in the present or future, we're synced. @@ -1320,8 +1302,8 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { _pending_block_mode = pending_block_mode::speculating; } else if( _producers.find(scheduled_producer.producer_name) == _producers.end()) { _pending_block_mode = pending_block_mode::speculating; - } else if (signature_provider_itr == _signature_providers.end()) { - elog("Not producing block because I don't have the private key for ${scheduled_key}", ("scheduled_key", scheduled_producer.block_signing_key)); + } else if (num_relevant_signatures == 0) { + elog("Not producing block because I don't have any private keys relevant to authority: ${authority}", ("authority", scheduled_producer.authority)); _pending_block_mode = pending_block_mode::speculating; } else if ( _pause_production ) { elog("Not producing block because production is explicitly paused"); @@ -1410,11 +1392,11 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { if( chain.is_building_block() ) { auto pending_block_time = chain.pending_block_time(); - auto pending_block_signing_key = chain.pending_block_signing_key(); + auto pending_block_signing_authority = chain.pending_block_signing_authority(); const fc::time_point preprocess_deadline = calculate_block_deadline(block_time); - if (_pending_block_mode == pending_block_mode::producing && pending_block_signing_key != scheduled_producer.block_signing_key) { - elog("Block Signing Key is not expected value, reverting to speculative mode! [expected: \"${expected}\", actual: \"${actual\"", ("expected", scheduled_producer.block_signing_key)("actual", pending_block_signing_key)); + if (_pending_block_mode == pending_block_mode::producing && pending_block_signing_authority != scheduled_producer.authority) { + elog("Block Signing Key is not expected value, reverting to speculative mode! [expected: \"${expected}\", actual: \"${actual\"", ("expected", scheduled_producer.authority)("actual", pending_block_signing_authority)); _pending_block_mode = pending_block_mode::speculating; } @@ -1850,9 +1832,14 @@ void producer_plugin_impl::produce_block() { chain::controller& chain = chain_plug->chain(); const auto& hbs = chain.head_block_state(); EOS_ASSERT(chain.is_building_block(), missing_pending_block_state, "pending_block_state does not exist but it should, another plugin may have corrupted it"); - auto signature_provider_itr = _signature_providers.find( chain.pending_block_signing_key() ); - EOS_ASSERT(signature_provider_itr != _signature_providers.end(), producer_priv_key_not_found, "Attempting to produce a block for which we don't have the private key"); + + const auto& auth = chain.pending_block_signing_authority(); + auto num_relevant_signatures = std::count_if(_signature_providers.begin(), _signature_providers.end(), [&auth](const auto& p){ + return producer_authority::key_is_relevant(p.first, auth); + }); + + EOS_ASSERT(num_relevant_signatures > 0, producer_priv_key_not_found, "Attempting to produce a block for which we don't have any relevant private keys"); if (_protocol_features_signaled) { _protocol_features_to_activate.clear(); // clear _protocol_features_to_activate as it is already set in pending_block @@ -1862,7 +1849,16 @@ void producer_plugin_impl::produce_block() { //idump( (fc::time_point::now() - chain.pending_block_time()) ); chain.finalize_block( [&]( const digest_type& d ) { auto debug_logger = maybe_make_debug_time_logger(); - return signature_provider_itr->second(d); + vector sigs; + sigs.reserve(num_relevant_signatures); + + // sign with all relevant public keys + for (const auto& p : _signature_providers) { + if (producer_authority::key_is_relevant(p.first, auth)) { + sigs.emplace_back(p.second(d)); + } + } + return sigs; } ); chain.commit_block(); diff --git a/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp b/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp index bdbd36276a0..cb9ea2b601a 100644 --- a/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp +++ b/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp @@ -252,7 +252,7 @@ datastream& operator<<(datastream& ds, template datastream& operator<<(datastream& ds, const history_serial_wrapper& obj) { - fc::raw::pack(ds, as_type(obj.obj.name.value)); + fc::raw::pack(ds, as_type(obj.obj.producer_name.value)); fc::raw::pack(ds, as_type(obj.obj.authority)); return ds; } diff --git a/plugins/state_history_plugin/state_history_plugin_abi.cpp b/plugins/state_history_plugin/state_history_plugin_abi.cpp index a18d15968bd..aa9c1fdc1be 100644 --- a/plugins/state_history_plugin/state_history_plugin_abi.cpp +++ b/plugins/state_history_plugin/state_history_plugin_abi.cpp @@ -319,7 +319,7 @@ extern const char* const state_history_plugin_abi = R"({ }, { "name": "producer_authority", "fields": [ - { "type": "name", "name": "name" }, + { "type": "name", "name": "producer_name" }, { "type": "block_signing_authority", "name": "authority" } ] }, diff --git a/plugins/test_control_plugin/test_control_plugin.cpp b/plugins/test_control_plugin/test_control_plugin.cpp index 3c26344c8e5..16d2b7d7c8a 100644 --- a/plugins/test_control_plugin/test_control_plugin.cpp +++ b/plugins/test_control_plugin/test_control_plugin.cpp @@ -65,7 +65,7 @@ void test_control_plugin_impl::accepted_block(const chain::block_state_ptr& bsp) void test_control_plugin_impl::process_next_block_state(const chain::block_state_ptr& bsp) { const auto block_time = _chain.head_block_time() + fc::microseconds(chain::config::block_interval_us); const auto& producer_authority = bsp->get_scheduled_producer(block_time); - const auto producer_name = producer_authority.visit([](const auto& a){ return a.producer_name; }); + const auto producer_name = producer_authority.producer_name; if (_producer != account_name()) ilog("producer ${cprod}, looking for ${lprod}", ("cprod", producer_name.to_string())("lprod", _producer.to_string())); diff --git a/unittests/bootseq_tests.cpp b/unittests/bootseq_tests.cpp index 561a04622cc..58c9b59f754 100644 --- a/unittests/bootseq_tests.cpp +++ b/unittests/bootseq_tests.cpp @@ -301,16 +301,16 @@ BOOST_FIXTURE_TEST_CASE( bootseq_test, bootseq_tester ) { produce_blocks_for_n_rounds(2); // 2 rounds since new producer schedule is set when the first block of next round is irreversible active_schedule = control->head_block_state()->active_schedule; BOOST_REQUIRE(active_schedule.producers.size() == 21); - BOOST_TEST(active_schedule.producers.at(0).producer_name == "proda"); - BOOST_TEST(active_schedule.producers.at(1).producer_name == "prodb"); - BOOST_TEST(active_schedule.producers.at(2).producer_name == "prodc"); - BOOST_TEST(active_schedule.producers.at(3).producer_name == "prodd"); - BOOST_TEST(active_schedule.producers.at(4).producer_name == "prode"); - BOOST_TEST(active_schedule.producers.at(5).producer_name == "prodf"); - BOOST_TEST(active_schedule.producers.at(6).producer_name == "prodg"); - BOOST_TEST(active_schedule.producers.at(7).producer_name == "prodh"); - BOOST_TEST(active_schedule.producers.at(8).producer_name == "prodi"); - BOOST_TEST(active_schedule.producers.at(9).producer_name == "prodj"); + BOOST_TEST(active_schedule.producers.at( 0).producer_name == "proda"); + BOOST_TEST(active_schedule.producers.at( 1).producer_name == "prodb"); + BOOST_TEST(active_schedule.producers.at( 2).producer_name == "prodc"); + BOOST_TEST(active_schedule.producers.at( 3).producer_name == "prodd"); + BOOST_TEST(active_schedule.producers.at( 4).producer_name == "prode"); + BOOST_TEST(active_schedule.producers.at( 5).producer_name == "prodf"); + BOOST_TEST(active_schedule.producers.at( 6).producer_name == "prodg"); + BOOST_TEST(active_schedule.producers.at( 7).producer_name == "prodh"); + BOOST_TEST(active_schedule.producers.at( 8).producer_name == "prodi"); + BOOST_TEST(active_schedule.producers.at( 9).producer_name == "prodj"); BOOST_TEST(active_schedule.producers.at(10).producer_name == "prodk"); BOOST_TEST(active_schedule.producers.at(11).producer_name == "prodl"); BOOST_TEST(active_schedule.producers.at(12).producer_name == "prodm"); diff --git a/unittests/contracts.hpp.in b/unittests/contracts.hpp.in index efafa1846c8..3611de0deec 100644 --- a/unittests/contracts.hpp.in +++ b/unittests/contracts.hpp.in @@ -36,7 +36,8 @@ namespace eosio { MAKE_READ_WASM_ABI(eosio_token, eosio.token, contracts) MAKE_READ_WASM_ABI(eosio_wrap, eosio.wrap, contracts) - MAKE_READ_WASM_ABI(before_preactivate_eosio_bios, eosio.bios, contracts/old_versions/v1.6.0-rc3) + MAKE_READ_WASM_ABI(before_producer_authority_eosio_bios, eosio.bios, contracts/old_versions/v1.7.0-develop-preactivate_feature) + MAKE_READ_WASM_ABI(before_preactivate_eosio_bios, eosio.bios, contracts/old_versions/v1.6.0-rc3) // Contracts in `eos/unittests/unittests/test-contracts' directory MAKE_READ_WASM_ABI(asserter, asserter, test-contracts) diff --git a/unittests/contracts/CMakeLists.txt b/unittests/contracts/CMakeLists.txt index f64c79de062..1c1c3003901 100644 --- a/unittests/contracts/CMakeLists.txt +++ b/unittests/contracts/CMakeLists.txt @@ -8,3 +8,4 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/eosio.token/ DESTINATION ${CMAKE_CURRENT_B file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/eosio.wrap/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/eosio.wrap/) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/old_versions/v1.6.0-rc3/eosio.bios/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/old_versions/v1.6.0-rc3/eosio.bios/) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/) diff --git a/unittests/contracts/eosio.bios/eosio.bios.abi b/unittests/contracts/eosio.bios/eosio.bios.abi index 01f62c976f5..0cf509deb60 100644 --- a/unittests/contracts/eosio.bios/eosio.bios.abi +++ b/unittests/contracts/eosio.bios/eosio.bios.abi @@ -1,7 +1,12 @@ { "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", "version": "eosio::abi/1.1", - "types": [], + "types": [ + { + "new_type_name": "block_signing_authority", + "type": "variant_block_signing_authority_v0" + } + ], "structs": [ { "name": "abi_hash", @@ -252,7 +257,7 @@ ] }, { - "name": "producer_key", + "name": "producer_authority", "base": "", "fields": [ { @@ -260,8 +265,8 @@ "type": "name" }, { - "name": "block_signing_key", - "type": "public_key" + "name": "block_signing_authority", + "type": "block_signing_authority" } ] }, @@ -391,7 +396,7 @@ "fields": [ { "name": "schedule", - "type": "producer_key[]" + "type": "producer_authority[]" } ] }, @@ -454,87 +459,87 @@ { "name": "activate", "type": "activate", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "canceldelay", "type": "canceldelay", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "deleteauth", "type": "deleteauth", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "linkauth", "type": "linkauth", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "newaccount", "type": "newaccount", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "onerror", "type": "onerror", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "reqactivated", "type": "reqactivated", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "reqauth", "type": "reqauth", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "setabi", "type": "setabi", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "setalimits", "type": "setalimits", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "setcode", "type": "setcode", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "setglimits", "type": "setglimits", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "setparams", "type": "setparams", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "setpriv", "type": "setpriv", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "setprods", "type": "setprods", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "unlinkauth", "type": "unlinkauth", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" }, { "name": "updateauth", "type": "updateauth", - "ricardian_contract": "" + "ricardian_contract": "---\ntitle: TITLE\nsummary: SUMMARY\nicon: ICON\n---\n\nBODY" } ], "tables": [ @@ -547,5 +552,10 @@ } ], "ricardian_clauses": [], - "variants": [] + "variants": [ + { + "name": "variant_block_signing_authority_v0", + "types": ["block_signing_authority_v0"] + } + ] } \ No newline at end of file diff --git a/unittests/contracts/eosio.bios/eosio.bios.wasm b/unittests/contracts/eosio.bios/eosio.bios.wasm index 968bd1529dc23933a35712bdabe2697d37553c99..e5950df279b25b8eedf41019ff7137c29347e55b 100755 GIT binary patch literal 23359 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 LG|&FL=iL7VpOItP delta 4559 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 Jv%Z~S?7zOel`H@N diff --git a/unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/eosio.bios.abi b/unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/eosio.bios.abi new file mode 100644 index 00000000000..01f62c976f5 --- /dev/null +++ b/unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/eosio.bios.abi @@ -0,0 +1,551 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.1", + "types": [], + "structs": [ + { + "name": "abi_hash", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "hash", + "type": "checksum256" + } + ] + }, + { + "name": "activate", + "base": "", + "fields": [ + { + "name": "feature_digest", + "type": "checksum256" + } + ] + }, + { + "name": "authority", + "base": "", + "fields": [ + { + "name": "threshold", + "type": "uint32" + }, + { + "name": "keys", + "type": "key_weight[]" + }, + { + "name": "accounts", + "type": "permission_level_weight[]" + }, + { + "name": "waits", + "type": "wait_weight[]" + } + ] + }, + { + "name": "blockchain_parameters", + "base": "", + "fields": [ + { + "name": "max_block_net_usage", + "type": "uint64" + }, + { + "name": "target_block_net_usage_pct", + "type": "uint32" + }, + { + "name": "max_transaction_net_usage", + "type": "uint32" + }, + { + "name": "base_per_transaction_net_usage", + "type": "uint32" + }, + { + "name": "net_usage_leeway", + "type": "uint32" + }, + { + "name": "context_free_discount_net_usage_num", + "type": "uint32" + }, + { + "name": "context_free_discount_net_usage_den", + "type": "uint32" + }, + { + "name": "max_block_cpu_usage", + "type": "uint32" + }, + { + "name": "target_block_cpu_usage_pct", + "type": "uint32" + }, + { + "name": "max_transaction_cpu_usage", + "type": "uint32" + }, + { + "name": "min_transaction_cpu_usage", + "type": "uint32" + }, + { + "name": "max_transaction_lifetime", + "type": "uint32" + }, + { + "name": "deferred_trx_expiration_window", + "type": "uint32" + }, + { + "name": "max_transaction_delay", + "type": "uint32" + }, + { + "name": "max_inline_action_size", + "type": "uint32" + }, + { + "name": "max_inline_action_depth", + "type": "uint16" + }, + { + "name": "max_authority_depth", + "type": "uint16" + } + ] + }, + { + "name": "canceldelay", + "base": "", + "fields": [ + { + "name": "canceling_auth", + "type": "permission_level" + }, + { + "name": "trx_id", + "type": "checksum256" + } + ] + }, + { + "name": "deleteauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "permission", + "type": "name" + } + ] + }, + { + "name": "key_weight", + "base": "", + "fields": [ + { + "name": "key", + "type": "public_key" + }, + { + "name": "weight", + "type": "uint16" + } + ] + }, + { + "name": "linkauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "code", + "type": "name" + }, + { + "name": "type", + "type": "name" + }, + { + "name": "requirement", + "type": "name" + } + ] + }, + { + "name": "newaccount", + "base": "", + "fields": [ + { + "name": "creator", + "type": "name" + }, + { + "name": "name", + "type": "name" + }, + { + "name": "owner", + "type": "authority" + }, + { + "name": "active", + "type": "authority" + } + ] + }, + { + "name": "onerror", + "base": "", + "fields": [ + { + "name": "sender_id", + "type": "uint128" + }, + { + "name": "sent_trx", + "type": "bytes" + } + ] + }, + { + "name": "permission_level", + "base": "", + "fields": [ + { + "name": "actor", + "type": "name" + }, + { + "name": "permission", + "type": "name" + } + ] + }, + { + "name": "permission_level_weight", + "base": "", + "fields": [ + { + "name": "permission", + "type": "permission_level" + }, + { + "name": "weight", + "type": "uint16" + } + ] + }, + { + "name": "producer_key", + "base": "", + "fields": [ + { + "name": "producer_name", + "type": "name" + }, + { + "name": "block_signing_key", + "type": "public_key" + } + ] + }, + { + "name": "reqactivated", + "base": "", + "fields": [ + { + "name": "feature_digest", + "type": "checksum256" + } + ] + }, + { + "name": "reqauth", + "base": "", + "fields": [ + { + "name": "from", + "type": "name" + } + ] + }, + { + "name": "setabi", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "abi", + "type": "bytes" + } + ] + }, + { + "name": "setalimits", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "ram_bytes", + "type": "int64" + }, + { + "name": "net_weight", + "type": "int64" + }, + { + "name": "cpu_weight", + "type": "int64" + } + ] + }, + { + "name": "setcode", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "vmtype", + "type": "uint8" + }, + { + "name": "vmversion", + "type": "uint8" + }, + { + "name": "code", + "type": "bytes" + } + ] + }, + { + "name": "setglimits", + "base": "", + "fields": [ + { + "name": "ram", + "type": "uint64" + }, + { + "name": "net", + "type": "uint64" + }, + { + "name": "cpu", + "type": "uint64" + } + ] + }, + { + "name": "setparams", + "base": "", + "fields": [ + { + "name": "params", + "type": "blockchain_parameters" + } + ] + }, + { + "name": "setpriv", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "is_priv", + "type": "uint8" + } + ] + }, + { + "name": "setprods", + "base": "", + "fields": [ + { + "name": "schedule", + "type": "producer_key[]" + } + ] + }, + { + "name": "unlinkauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "code", + "type": "name" + }, + { + "name": "type", + "type": "name" + } + ] + }, + { + "name": "updateauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "permission", + "type": "name" + }, + { + "name": "parent", + "type": "name" + }, + { + "name": "auth", + "type": "authority" + } + ] + }, + { + "name": "wait_weight", + "base": "", + "fields": [ + { + "name": "wait_sec", + "type": "uint32" + }, + { + "name": "weight", + "type": "uint16" + } + ] + } + ], + "actions": [ + { + "name": "activate", + "type": "activate", + "ricardian_contract": "" + }, + { + "name": "canceldelay", + "type": "canceldelay", + "ricardian_contract": "" + }, + { + "name": "deleteauth", + "type": "deleteauth", + "ricardian_contract": "" + }, + { + "name": "linkauth", + "type": "linkauth", + "ricardian_contract": "" + }, + { + "name": "newaccount", + "type": "newaccount", + "ricardian_contract": "" + }, + { + "name": "onerror", + "type": "onerror", + "ricardian_contract": "" + }, + { + "name": "reqactivated", + "type": "reqactivated", + "ricardian_contract": "" + }, + { + "name": "reqauth", + "type": "reqauth", + "ricardian_contract": "" + }, + { + "name": "setabi", + "type": "setabi", + "ricardian_contract": "" + }, + { + "name": "setalimits", + "type": "setalimits", + "ricardian_contract": "" + }, + { + "name": "setcode", + "type": "setcode", + "ricardian_contract": "" + }, + { + "name": "setglimits", + "type": "setglimits", + "ricardian_contract": "" + }, + { + "name": "setparams", + "type": "setparams", + "ricardian_contract": "" + }, + { + "name": "setpriv", + "type": "setpriv", + "ricardian_contract": "" + }, + { + "name": "setprods", + "type": "setprods", + "ricardian_contract": "" + }, + { + "name": "unlinkauth", + "type": "unlinkauth", + "ricardian_contract": "" + }, + { + "name": "updateauth", + "type": "updateauth", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "abihash", + "type": "abi_hash", + "index_type": "i64", + "key_names": [], + "key_types": [] + } + ], + "ricardian_clauses": [], + "variants": [] +} \ No newline at end of file diff --git a/unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/eosio.bios.wasm b/unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/eosio.bios.wasm new file mode 100755 index 0000000000000000000000000000000000000000..968bd1529dc23933a35712bdabe2697d37553c99 GIT binary patch literal 17777 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 literal 0 HcmV?d00001 diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index 5f466d5ebe9..61e389b0de3 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -28,11 +28,7 @@ BOOST_AUTO_TEST_CASE( irrblock ) try { c.produce_blocks(10); auto r = c.create_accounts( {N(dan),N(sam),N(pam),N(scott)} ); auto res = c.set_producers( {N(dan),N(sam),N(pam),N(scott)} ); - vector sch = { {N(dan),get_public_key(N(dan), "active")}, - {N(sam),get_public_key(N(sam), "active")}, - {N(scott),get_public_key(N(scott), "active")}, - {N(pam),get_public_key(N(pam), "active")} - }; + wlog("set producer schedule to [dan,sam,pam]"); c.produce_blocks(50); @@ -143,9 +139,7 @@ BOOST_AUTO_TEST_CASE( forking ) try { wdump((fc::json::to_pretty_string(r))); c.produce_block(); auto res = c.set_producers( {N(dan),N(sam),N(pam)} ); - vector sch = { {N(dan),get_public_key(N(dan), "active")}, - {N(sam),get_public_key(N(sam), "active")}, - {N(pam),get_public_key(N(pam), "active")}}; + wdump((fc::json::to_pretty_string(res))); wlog("set producer schedule to [dan,sam,pam]"); c.produce_blocks(30); diff --git a/unittests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp index b31003b9e2d..4dbc28808a5 100644 --- a/unittests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -22,13 +22,13 @@ using mvo = fc::mutable_variant_object; BOOST_AUTO_TEST_SUITE(producer_schedule_tests) // Calculate expected producer given the schedule and slot number - account_name get_expected_producer(const vector& schedule, const uint64_t slot) { + account_name get_expected_producer(const vector& schedule, const uint64_t slot) { const auto& index = (slot % (schedule.size() * config::producer_repetitions)) / config::producer_repetitions; return schedule.at(index).producer_name; }; // Check if two schedule is equal - bool is_schedule_equal(const vector& first, const vector& second) { + bool is_schedule_equal(const vector& first, const vector& second) { bool is_equal = first.size() == second.size(); for (uint32_t i = 0; i < first.size(); i++) { is_equal = is_equal && first.at(i) == second.at(i); @@ -215,8 +215,8 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_promotion_test, TESTER ) try { auto res = set_producers( {N(alice),N(bob)} ); vector sch1 = { - producer_authority_v0{N(alice), 1, {{get_public_key(N(alice), "active"), 1}}}, - producer_authority_v0{N(bob), 1, {{get_public_key(N(bob), "active"), 1}}} + producer_authority{N(alice), block_signing_authority_v0{1, {{get_public_key(N(alice), "active"), 1}}}}, + producer_authority{N(bob), block_signing_authority_v0{1, {{get_public_key(N(bob), "active"), 1}}}} }; //wdump((fc::json::to_pretty_string(res))); wlog("set producer schedule to [alice,bob]"); @@ -235,9 +235,9 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_promotion_test, TESTER ) try { res = set_producers( {N(alice),N(bob),N(carol)} ); vector sch2 = { - producer_authority_v0{N(alice), 1, {{get_public_key(N(alice), "active"),1}}}, - producer_authority_v0{N(bob), 1, {{get_public_key(N(bob), "active"),1}}}, - producer_authority_v0{N(carol), 1, {{get_public_key(N(carol), "active"),1}}} + producer_authority{N(alice), block_signing_authority_v0{1, {{get_public_key(N(alice), "active"),1}}}}, + producer_authority{N(bob), block_signing_authority_v0{1, {{get_public_key(N(bob), "active"),1}}}}, + producer_authority{N(carol), block_signing_authority_v0{1, {{get_public_key(N(carol), "active"),1}}}} }; wlog("set producer schedule to [alice,bob,carol]"); BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() ); @@ -280,9 +280,9 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_reduction, tester ) try { auto res = set_producers( {N(alice),N(bob),N(carol)} ); vector sch1 = { - producer_authority_v0{N(alice), 1, {{get_public_key(N(alice), "active"),1}}}, - producer_authority_v0{N(bob), 1, {{get_public_key(N(bob), "active"),1}}}, - producer_authority_v0{N(carol), 1, {{get_public_key(N(carol), "active"),1}}} + producer_authority{N(alice), block_signing_authority_v0{ 1, {{get_public_key(N(alice), "active"),1}}}}, + producer_authority{N(bob), block_signing_authority_v0{ 1, {{get_public_key(N(bob), "active"),1}}}}, + producer_authority{N(carol), block_signing_authority_v0{ 1, {{get_public_key(N(carol), "active"),1}}}} }; wlog("set producer schedule to [alice,bob,carol]"); BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() ); @@ -300,8 +300,8 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_reduction, tester ) try { res = set_producers( {N(alice),N(bob)} ); vector sch2 = { - producer_authority_v0{N(alice), 1, {{ get_public_key(N(alice), "active"),1}}}, - producer_authority_v0{N(bob), 1, {{ get_public_key(N(bob), "active"),1}}} + producer_authority{N(alice), block_signing_authority_v0{ 1, {{ get_public_key(N(alice), "active"),1}}}}, + producer_authority{N(bob), block_signing_authority_v0{ 1, {{ get_public_key(N(bob), "active"),1}}}} }; wlog("set producer schedule to [alice,bob]"); BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() ); @@ -342,8 +342,8 @@ BOOST_AUTO_TEST_CASE( empty_producer_schedule_has_no_effect ) try { auto res = c.set_producers( {N(alice),N(bob)} ); vector sch1 = { - producer_authority_v0{N(alice), 1, {{ get_public_key(N(alice), "active"),1}}}, - producer_authority_v0{N(bob), 1, {{ get_public_key(N(bob), "active"),1}}} + producer_authority{N(alice), block_signing_authority_v0{ 1, {{ get_public_key(N(alice), "active"),1}}}}, + producer_authority{N(bob), block_signing_authority_v0{ 1, {{ get_public_key(N(bob), "active"),1}}}} }; wlog("set producer schedule to [alice,bob]"); BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() ); @@ -384,9 +384,9 @@ BOOST_AUTO_TEST_CASE( empty_producer_schedule_has_no_effect ) try { // Setting a new producer schedule should still use version 2 res = c.set_producers( {N(alice),N(bob),N(carol)} ); vector sch2 = { - producer_authority_v0{N(alice), 1, {{get_public_key(N(alice), "active"),1}}}, - producer_authority_v0{N(bob), 1, {{get_public_key(N(bob), "active"),1}}}, - producer_authority_v0{N(carol), 1, {{get_public_key(N(carol), "active"),1}}} + producer_authority{N(alice), block_signing_authority_v0{ 1, {{get_public_key(N(alice), "active"),1}}}}, + producer_authority{N(bob), block_signing_authority_v0{ 1, {{get_public_key(N(bob), "active"),1}}}}, + producer_authority{N(carol), block_signing_authority_v0{ 1, {{get_public_key(N(carol), "active"),1}}}} }; wlog("set producer schedule to [alice,bob,carol]"); BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() ); @@ -419,9 +419,9 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { auto res = c.set_producers( {N(alice),N(bob),N(carol)} ); vector sch1 = { - producer_authority_v0{N(alice), 1, {{c.get_public_key(N(alice), "active"),1}}}, - producer_authority_v0{N(bob), 1, {{c.get_public_key(N(bob), "active"),1}}}, - producer_authority_v0{N(carol), 1, {{c.get_public_key(N(carol), "active"),1}}} + producer_authority{N(alice), block_signing_authority_v0{ 1, {{c.get_public_key(N(alice), "active"),1}}}}, + producer_authority{N(bob), block_signing_authority_v0{ 1, {{c.get_public_key(N(bob), "active"),1}}}}, + producer_authority{N(carol), block_signing_authority_v0{ 1, {{c.get_public_key(N(carol), "active"),1}}}} }; wlog("set producer schedule to [alice,bob,carol]"); BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() ); @@ -442,8 +442,8 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { res = c.set_producers( {N(alice),N(bob)} ); vector sch2 = { - producer_authority_v0{N(alice), 1, {{c.get_public_key(N(alice), "active"),1}}}, - producer_authority_v0{N(bob), 1, {{c.get_public_key(N(bob), "active"),1}}} + producer_authority{N(alice), block_signing_authority_v0{ 1, {{c.get_public_key(N(alice), "active"),1}}}}, + producer_authority{N(bob), block_signing_authority_v0{ 1, {{c.get_public_key(N(bob), "active"),1}}}} }; wlog("set producer schedule to [alice,bob]"); BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() ); diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp index cd20e6cd74f..fae902ab157 100644 --- a/unittests/protocol_feature_tests.cpp +++ b/unittests/protocol_feature_tests.cpp @@ -777,7 +777,7 @@ BOOST_AUTO_TEST_CASE( disallow_empty_producer_schedule_test ) { try { c.create_accounts( producer_names ); c.set_producers( producer_names ); c.produce_blocks(2); - const auto& schedule = c.get_producer_keys( producer_names ); + const auto& schedule = c.get_producer_authorities( producer_names ); BOOST_CHECK( std::equal( schedule.begin(), schedule.end(), c.control->active_producers().producers.begin()) ); } FC_LOG_AND_RETHROW() } From a965f0dcb8c9f49b3be5699ca9ca7b878fd613cf Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 16 May 2019 16:49:08 -0400 Subject: [PATCH 06/36] add new intrinsic, restore old intrinsic --- libraries/chain/controller.cpp | 9 +++++ libraries/chain/wasm_interface.cpp | 55 +++++++++++++++++++----------- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index e43c7f11256..84b5703b503 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3089,6 +3089,15 @@ void controller_impl::on_activation +void controller_impl::on_activation() { + db.modify( db.get(), [&]( auto& ps ) { + add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "set_proposed_producers_ex" ); + } ); +} + + + /// End of protocol feature activation handlers } } /// eosio::chain diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 55981a533b0..b426d5543a3 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -175,25 +175,7 @@ class privileged_api : public context_aware_api { context.control.get_resource_limits_manager().get_account_limits( account, ram_bytes, net_weight, cpu_weight); } - int64_t set_proposed_producers( array_ptr packed_producer_schedule, size_t datalen) { - datastream ds( packed_producer_schedule, datalen ); - vector producers; - - if ( context.control.is_builtin_activated( builtin_protocol_feature_t::wtmsig_block_signatures )) { - fc::raw::unpack(ds, producers); - - } else { - vector old_version; - fc::raw::unpack(ds, old_version); - - /* - * Up-convert the producers - */ - for ( const auto& p: old_version ) { - producers.emplace_back(producer_authority{ p.producer_name, block_signing_authority_v0{ 1, {{p.block_signing_key, 1}} } } ); - } - } - + int64_t set_proposed_producers_common( vector && producers ) { EOS_ASSERT(producers.size() <= config::max_producers, wasm_execution_error, "Producer schedule exceeds the maximum producer count for this chain"); EOS_ASSERT( producers.size() > 0 || !context.control.is_builtin_activated( builtin_protocol_feature_t::disallow_empty_producer_schedule ), @@ -226,7 +208,39 @@ class privileged_api : public context_aware_api { } EOS_ASSERT( producers.size() == unique_producers.size(), wasm_execution_error, "duplicate producer name in producer schedule" ); - return context.control.set_proposed_producers( std::move(producers) ); + return context.control.set_proposed_producers( std::forward>(producers) ); + } + + int64_t set_proposed_producers( array_ptr packed_producer_schedule, size_t datalen) { + datastream ds( packed_producer_schedule, datalen ); + vector producers; + +// if ( context.control.is_builtin_activated( builtin_protocol_feature_t::wtmsig_block_signatures )) { + vector old_version; + fc::raw::unpack(ds, old_version); + + /* + * Up-convert the producers + */ + for ( const auto& p: old_version ) { + producers.emplace_back(producer_authority{ p.producer_name, block_signing_authority_v0{ 1, {{p.block_signing_key, 1}} } } ); + } + + return set_proposed_producers_common(std::move(producers)); + } + + int64_t set_proposed_producers_ex( uint64_t packed_producer_format, array_ptr packed_producer_schedule, size_t datalen) { + if (packed_producer_format == 0) { + return set_proposed_producers(packed_producer_schedule, datalen); + } else if (packed_producer_format == 1) { + datastream ds( packed_producer_schedule, datalen ); + vector producers; + + fc::raw::unpack(ds, producers); + return set_proposed_producers_common(std::move(producers)); + } else { + EOS_THROW(wasm_execution_error, "Producer schedule is in an unknown format!"); + } } uint32_t get_blockchain_parameters_packed( array_ptr packed_blockchain_parameters, size_t buffer_size) { @@ -1784,6 +1798,7 @@ REGISTER_INTRINSICS(privileged_api, (get_resource_limits, void(int64_t,int,int,int) ) (set_resource_limits, void(int64_t,int64_t,int64_t,int64_t) ) (set_proposed_producers, int64_t(int,int) ) + (set_proposed_producers_ex, int64_t(int64_t, int, int) ) (get_blockchain_parameters_packed, int(int, int) ) (set_blockchain_parameters_packed, void(int,int) ) (is_privileged, int(int64_t) ) From 55cdeacea061152bc1862ac4ceabc61c6f69824d Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 17 May 2019 10:30:13 -0400 Subject: [PATCH 07/36] add additional plumbing to get protocol feature activating, fix full tester setup to use the interim bios until features are activated --- libraries/chain/controller.cpp | 1 + libraries/chain/protocol_feature_manager.cpp | 23 ++++++++++++++++++++ libraries/testing/tester.cpp | 3 ++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 84b5703b503..59f9fff2769 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -315,6 +315,7 @@ struct controller_impl { set_activation_handler(); set_activation_handler(); set_activation_handler(); + set_activation_handler(); self.irreversible_block.connect([this](const block_state_ptr& bsp) { wasmif.current_lib(bsp->block_num); diff --git a/libraries/chain/protocol_feature_manager.cpp b/libraries/chain/protocol_feature_manager.cpp index ca0457ce11d..f6befbbd2c8 100644 --- a/libraries/chain/protocol_feature_manager.cpp +++ b/libraries/chain/protocol_feature_manager.cpp @@ -154,6 +154,29 @@ is not allowed to schedule a deferred transaction in which the RAM costs are pai unless that account authorized the action; but is allowed to execute database operations that increase RAM usage of an account other than the receiver as long as either the account authorized the action or the action's net effect on RAM usage for the account is to not increase it. +*/ + {} + } ) + ( builtin_protocol_feature_t::wtmsig_block_signatures, builtin_protocol_feature_spec{ + "WTMSIG_BLOCK_SIGNATURES", + fc::variant("46d65101388a44ebdbbb152978ac1879e34785a2d57ad050196826734370b995").as(), + // SHA256 hash of the raw message below within the comment delimiters (do not modify message below). +/* +Builtin protocol feature: WTMSIG_BLOCK_SIGNATURES + +Allows producers to specify a subset of our auhority structures as the method for signing blocks. + +A valid block header: +is no longer allowed to have a non-empty `new_producers` field; +must announce new producer schedules using a block header extension with ID `1` + +A valid signed block: +must continue to have exactly one signature in its `signatures` field; +and may have additional signatures in a block extension with ID `2` + +Privileged Contracts: +may continue to use `set_proposed_producers` as they have; +may use a new `set_proposed_producers_ex` intrinsic to access extended features. */ {} } ) diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index bbd3dd2fe1e..6f44d718d3a 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -184,9 +184,10 @@ namespace eosio { namespace testing { case setup_policy::full: { schedule_preactivate_protocol_feature(); produce_block(); - set_bios_contract(); + set_before_producer_authority_bios_contract(); preactivate_all_builtin_protocol_features(); produce_block(); + set_bios_contract(); break; } case setup_policy::none: From 32b92969499ba48fd4f0f9141a0f518482fe2ab6 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 17 May 2019 16:35:33 -0400 Subject: [PATCH 08/36] update with new fc commits that fix variant comparisons --- libraries/fc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fc b/libraries/fc index c83e208bce9..bb9ffe1b5cc 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit c83e208bce9f86e92421056e82b92283145884e4 +Subproject commit bb9ffe1b5cc7b14807f0e275f8bae024a8fb66d3 From d6e77c84ce53c366a350606c3a141adff7633133 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Mon, 20 May 2019 09:53:12 -0400 Subject: [PATCH 09/36] update to integrated fc changes --- libraries/fc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fc b/libraries/fc index bb9ffe1b5cc..fea7e02ae29 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit bb9ffe1b5cc7b14807f0e275f8bae024a8fb66d3 +Subproject commit fea7e02ae297c21fcd7e96a35fb1331dcde90e0d From 51cce2ac8b942852ac6e2c67d036808056a85a43 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Mon, 20 May 2019 15:24:43 -0400 Subject: [PATCH 10/36] fix ABI problems --- .../state_history_plugin_abi.cpp | 2 +- unittests/contracts/eosio.bios/eosio.bios.abi | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/state_history_plugin/state_history_plugin_abi.cpp b/plugins/state_history_plugin/state_history_plugin_abi.cpp index aa9c1fdc1be..338dc867852 100644 --- a/plugins/state_history_plugin/state_history_plugin_abi.cpp +++ b/plugins/state_history_plugin/state_history_plugin_abi.cpp @@ -313,7 +313,7 @@ extern const char* const state_history_plugin_abi = R"({ }, { "name": "block_signing_authority_v0", "fields": [ - { "type": "uint32", "name": "weight" } + { "type": "uint32", "name": "weight" }, { "type": "key_weight[]", "name": "keys" } ] }, diff --git a/unittests/contracts/eosio.bios/eosio.bios.abi b/unittests/contracts/eosio.bios/eosio.bios.abi index 0cf509deb60..9c9a46eda6f 100644 --- a/unittests/contracts/eosio.bios/eosio.bios.abi +++ b/unittests/contracts/eosio.bios/eosio.bios.abi @@ -54,6 +54,20 @@ } ] }, + { + "name": "block_signing_authority_v0", + "base": "", + "fields": [ + { + "name": "threshold", + "type": "uint32" + }, + { + "name": "keys", + "type": "key_weight[]" + } + ] + }, { "name": "blockchain_parameters", "base": "", @@ -265,7 +279,7 @@ "type": "name" }, { - "name": "block_signing_authority", + "name": "authority", "type": "block_signing_authority" } ] From a57656c5fe93252a69fc0297516f330a4bd3276a Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Mon, 20 May 2019 17:05:42 -0400 Subject: [PATCH 11/36] fix ABI and tester compatibility with variant differences --- libraries/chain/CMakeLists.txt | 1 + .../include/eosio/chain/producer_schedule.hpp | 14 +++++++++++ libraries/chain/producer_schedule.cpp | 25 +++++++++++++++++++ libraries/testing/tester.cpp | 9 ++++++- 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 libraries/chain/producer_schedule.cpp diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 254d462c5ed..7ce955ae6a7 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -49,6 +49,7 @@ add_library( eosio_chain protocol_state_object.cpp protocol_feature_activation.cpp protocol_feature_manager.cpp + producer_schedule.cpp genesis_intrinsics.cpp whitelisted_intrinsics.cpp thread_utils.cpp diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index ffe249e535c..b2970d9d581 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -84,6 +84,20 @@ namespace eosio { namespace chain { return key_is_relevant(key, authority); } + /** + * ABI's for contracts expect variants to be serialized as a 2 entry array of + * [type-name, value]. + * + * This is incompatible with standard FC rules for + * static_variants which produce + * + * [ordinal, value] + * + * this method produces an appropriate variant for contracts where the authority field + * is correctly formatted + */ + fc::variant get_abi_variant() const; + friend bool operator == ( const producer_authority& lhs, const producer_authority& rhs ) { return tie( lhs.producer_name, lhs.authority ) == tie( rhs.producer_name, rhs.authority ); } diff --git a/libraries/chain/producer_schedule.cpp b/libraries/chain/producer_schedule.cpp new file mode 100644 index 00000000000..151e9e7e200 --- /dev/null +++ b/libraries/chain/producer_schedule.cpp @@ -0,0 +1,25 @@ +#include + +namespace eosio { namespace chain { + +fc::variant producer_authority::get_abi_variant() const { + auto authority_variant = authority.visit([](const auto& a){ + fc::variant value; + fc::to_variant(a, value); + + std::string full_type_name = fc::get_typename>::name(); + auto last_colon = full_type_name.rfind(":"); + std::string type_name = last_colon == std::string::npos ? std::move(full_type_name): full_type_name.substr(last_colon + 1); + + return fc::variants { + fc::variant(std::move(type_name)), + std::move(value) + }; + }); + + return fc::mutable_variant_object() + ("producer_name", producer_name) + ("authority", std::move(authority_variant)); +} + +} } /// eosio::chain \ No newline at end of file diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index 6f44d718d3a..bc8a6bc6726 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -970,8 +970,15 @@ namespace eosio { namespace testing { transaction_trace_ptr base_tester::set_producers(const vector& producer_names) { auto schedule = get_producer_authorities( producer_names ); + // FC reflection does not create variants that are compatible with ABI 1.1 so we manually translate. + fc::variants schedule_variant; + schedule_variant.reserve(schedule.size()); + for( const auto& e: schedule ) { + schedule_variant.emplace_back(e.get_abi_variant()); + } + return push_action( config::system_account_name, N(setprods), config::system_account_name, - fc::mutable_variant_object()("schedule", schedule)); + fc::mutable_variant_object()("schedule", schedule_variant)); } const table_id_object* base_tester::find_table( name code, name scope, name table ) { From ac0f6209ee00f0b007473cdb0bfee6297d845c77 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Mon, 20 May 2019 17:44:49 -0400 Subject: [PATCH 12/36] fix more tests that relied on pre-authority producer schedules intentionally --- .../testing/include/eosio/testing/tester.hpp | 1 + libraries/testing/tester.cpp | 18 ++++++++++++++++++ unittests/producer_schedule_tests.cpp | 6 +++--- unittests/protocol_feature_tests.cpp | 16 ++++++++-------- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index 9ade5bf9971..444972c10a5 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -173,6 +173,7 @@ namespace eosio { namespace testing { vector get_producer_authorities( const vector& producer_names )const; transaction_trace_ptr set_producers(const vector& producer_names); + transaction_trace_ptr set_producers_legacy(const vector& producer_names); void link_authority( account_name account, account_name code, permission_name req, action_name type = "" ); void unlink_authority( account_name account, account_name code, action_name type = "" ); diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index bc8a6bc6726..d9799d62966 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -981,6 +981,24 @@ namespace eosio { namespace testing { fc::mutable_variant_object()("schedule", schedule_variant)); } + transaction_trace_ptr base_tester::set_producers_legacy(const vector& producer_names) { + auto schedule = get_producer_authorities( producer_names ); + // down-rank to old version + + vector legacy_keys; + legacy_keys.reserve(schedule.size()); + for (const auto &p : schedule) { + p.authority.visit([&legacy_keys, &p](const auto& auth){ + legacy_keys.emplace_back(legacy::producer_key{p.producer_name, auth.keys.front().key}); + }); + } + + return push_action( config::system_account_name, N(setprods), config::system_account_name, + fc::mutable_variant_object()("schedule", legacy_keys)); + + } + + const table_id_object* base_tester::find_table( name code, name scope, name table ) { auto tid = control->db().find(boost::make_tuple(code, scope, table)); return tid; diff --git a/unittests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp index 4dbc28808a5..7d8a20f959b 100644 --- a/unittests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -340,7 +340,7 @@ BOOST_AUTO_TEST_CASE( empty_producer_schedule_has_no_effect ) try { return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); }; - auto res = c.set_producers( {N(alice),N(bob)} ); + auto res = c.set_producers_legacy( {N(alice),N(bob)} ); vector sch1 = { producer_authority{N(alice), block_signing_authority_v0{ 1, {{ get_public_key(N(alice), "active"),1}}}}, producer_authority{N(bob), block_signing_authority_v0{ 1, {{ get_public_key(N(bob), "active"),1}}}} @@ -362,7 +362,7 @@ BOOST_AUTO_TEST_CASE( empty_producer_schedule_has_no_effect ) try { BOOST_CHECK_EQUAL( true, compare_schedules( sch1, c.control->active_producers() ) ); c.produce_blocks(6); - res = c.set_producers( {} ); + res = c.set_producers_legacy( {} ); wlog("set producer schedule to []"); BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() ); BOOST_CHECK_EQUAL( c.control->proposed_producers()->producers.size(), 0u ); @@ -382,7 +382,7 @@ BOOST_AUTO_TEST_CASE( empty_producer_schedule_has_no_effect ) try { BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u ); // Setting a new producer schedule should still use version 2 - res = c.set_producers( {N(alice),N(bob),N(carol)} ); + res = c.set_producers_legacy( {N(alice),N(bob),N(carol)} ); vector sch2 = { producer_authority{N(alice), block_signing_authority_v0{ 1, {{get_public_key(N(alice), "active"),1}}}}, producer_authority{N(bob), block_signing_authority_v0{ 1, {{get_public_key(N(bob), "active"),1}}}}, diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp index fae902ab157..ea1b2459548 100644 --- a/unittests/protocol_feature_tests.cpp +++ b/unittests/protocol_feature_tests.cpp @@ -52,8 +52,8 @@ BOOST_AUTO_TEST_CASE( activate_preactivate_feature ) try { c.produce_block(); // Now the latest bios contract can be set. - c.set_code( config::system_account_name, contracts::eosio_bios_wasm() ); - c.set_abi( config::system_account_name, contracts::eosio_bios_abi().data() ); + c.set_code( config::system_account_name, contracts::before_producer_authority_eosio_bios_wasm() ); + c.set_abi( config::system_account_name, contracts::before_producer_authority_eosio_bios_abi().data() ); c.produce_block(); @@ -332,7 +332,7 @@ BOOST_AUTO_TEST_CASE( subjective_restrictions_test ) try { // Second, test subjective_restrictions on feature that need to be activated WITH preactivation (ONLY_LINK_TO_EXISTING_PERMISSION) - c.set_bios_contract(); + c.set_before_producer_authority_bios_contract(); c.produce_block(); custom_subjective_restrictions = { @@ -763,7 +763,7 @@ BOOST_AUTO_TEST_CASE( disallow_empty_producer_schedule_test ) { try { BOOST_REQUIRE( d ); // Before activation, it is allowed to set empty producer schedule - c.set_producers( {} ); + c.set_producers_legacy( {} ); // After activation, it should not be allowed c.preactivate_protocol_features( {*d} ); @@ -775,7 +775,7 @@ BOOST_AUTO_TEST_CASE( disallow_empty_producer_schedule_test ) { try { // Setting non empty producer schedule should still be fine vector producer_names = {N(alice),N(bob),N(carol)}; c.create_accounts( producer_names ); - c.set_producers( producer_names ); + c.set_producers_legacy( producer_names ); c.produce_blocks(2); const auto& schedule = c.get_producer_authorities( producer_names ); BOOST_CHECK( std::equal( schedule.begin(), schedule.end(), c.control->active_producers().producers.begin()) ); @@ -969,7 +969,7 @@ BOOST_AUTO_TEST_CASE( forward_setcode_test ) { try { const auto& pfm = c.control->get_protocol_feature_manager(); const auto& d = pfm.get_builtin_digest( builtin_protocol_feature_t::forward_setcode ); BOOST_REQUIRE( d ); - c.set_bios_contract(); + c.set_before_producer_authority_bios_contract(); c.preactivate_protocol_features( {*d} ); c.produce_block(); c.set_code( config::system_account_name, contracts::reject_all_wasm() ); @@ -985,7 +985,7 @@ BOOST_AUTO_TEST_CASE( forward_setcode_test ) { try { tester c2(setup_policy::none); push_blocks( c, c2 ); // make a backup of the chain to enable testing further conditions. - c.set_bios_contract(); // To allow pushing further actions for setting up the other part of the test. + c.set_before_producer_authority_bios_contract(); // To allow pushing further actions for setting up the other part of the test. c.create_account( N(rejectall) ); c.produce_block(); // The existence of the rejectall account will make the reject_all contract reject all actions with no exception. @@ -1005,7 +1005,7 @@ BOOST_AUTO_TEST_CASE( forward_setcode_test ) { try { // However, it should still be possible to set the bios contract because the WASM on eosio is called after the // native setcode function completes. - c2.set_bios_contract(); + c2.set_before_producer_authority_bios_contract(); c2.produce_block(); } FC_LOG_AND_RETHROW() } From b90a0d2cf1b2c6cfdfdedccae4f9b6f6d18c9cfc Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Tue, 21 May 2019 10:50:49 -0400 Subject: [PATCH 13/36] work on multiple sigs --- libraries/chain/block.cpp | 64 +++++++++++++++++++ libraries/chain/block_header_state.cpp | 58 ++++++++++++++--- libraries/chain/include/eosio/chain/block.hpp | 41 +++++++++++- .../eosio/chain/block_header_state.hpp | 14 ++-- .../chain/include/eosio/chain/exceptions.hpp | 9 ++- .../include/eosio/chain/producer_schedule.hpp | 14 ++++ 6 files changed, 180 insertions(+), 20 deletions(-) create mode 100644 libraries/chain/block.cpp diff --git a/libraries/chain/block.cpp b/libraries/chain/block.cpp new file mode 100644 index 00000000000..cb9ed80cd07 --- /dev/null +++ b/libraries/chain/block.cpp @@ -0,0 +1,64 @@ +#include + +namespace eosio { namespace chain { + void additional_block_signatures_extension::reflector_init() { + static_assert( fc::raw::has_feature_reflector_init_on_unpacked_reflected_types, + "additional_block_signatures_extension expects FC to support reflector_init" ); + + EOS_ASSERT( signatures.size() > 0, ill_additional_block_signatures_extension, + "Additional block signatures extension must contain at least one signature", + ); + + set s; + + for( const auto& s : signatures ) { + auto res = s.insert( s ); + EOS_ASSERT( res.second, ill_additional_block_signatures_extension, + "Signature ${s} was repeated in the additional block signatures extension", + ("s", s) + ); + } + } + + flat_multimap block::validate_and_extract_extensions()const { + using decompose_t = block_extension_types::decompose_t; + + flat_multimap results; + + uint16_t id_type_lower_bound = 0; + + for( size_t i = 0; i < block_extensions.size(); ++i ) { + const auto& e = block_extensions[i]; + auto id = e.first; + + EOS_ASSERT( id >= id_type_lower_bound, invalid_block_extension, + "Block extensions are not in the correct order (ascending id types required)" + ); + + auto iter = results.emplace(std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple() + ); + + auto match = decompose_t::extract( id, e.second, iter->second ); + EOS_ASSERT( match, invalid_block_extension, + "Block extension with id type ${id} is not supported", + ("id", id) + ); + + if( match->enforce_unique ) { + EOS_ASSERT( i == 0 || id > id_type_lower_bound, invalid_block_header_extension, + "Block extension with id type ${id} is not allowed to repeat", + ("id", id) + ); + } + + + id_type_lower_bound = id; + } + + return results; + + } + +} } /// namespace eosio::chain \ No newline at end of file diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index dd2e3b9fa07..cd940827add 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -314,6 +314,7 @@ namespace eosio { namespace chain { block_header_state pending_block_header_state::finish_next( const signed_block_header& h, + const vector& additional_signatures, const protocol_feature_set& pfs, const std::function&, @@ -323,6 +324,15 @@ namespace eosio { namespace chain { { auto result = std::move(*this)._finish_next( h, pfs, validator ); + if( !additional_signatures.empty() ) { + auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); + const auto& protocol_features = prev_activated_protocol_features->protocol_features; + bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + + EOS_ASSERT(wtmsig_enabled, producer_schedule_exception, "Block contains multiple signatures before WTMsig block signatures are enabled" ); + result.additional_signatures = additional_signatures; + } + // ASSUMPTION FROM controller_impl::apply_block = all untrusted blocks will have their signatures pre-validated here if( !skip_validate_signee ) { result.verify_signee( ); @@ -343,6 +353,15 @@ namespace eosio { namespace chain { auto result = std::move(*this)._finish_next( h, pfs, validator ); result.sign( signer ); h.producer_signature = result.header.producer_signature; + + if( !result.additional_signatures.empty() ) { + auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); + const auto& protocol_features = prev_activated_protocol_features->protocol_features; + bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + + EOS_ASSERT(wtmsig_enabled, producer_schedule_exception, "Block was signed with multiple signatures before WTMsig block signatures are enabled" ); + } + return result; } @@ -356,13 +375,14 @@ namespace eosio { namespace chain { */ block_header_state block_header_state::next( const signed_block_header& h, + const vector& _additional_signatures, const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, bool skip_validate_signee )const { - return next( h.timestamp, h.confirmed ).finish_next( h, pfs, validator, skip_validate_signee ); + return next( h.timestamp, h.confirmed ).finish_next( h, _additional_signatures, pfs, validator, skip_validate_signee ); } digest_type block_header_state::sig_digest()const { @@ -374,21 +394,39 @@ namespace eosio { namespace chain { auto d = sig_digest(); // TODO: modify block to allow multiple sigs auto sigs = signer( d ); - header.producer_signature = sigs.front(); - EOS_ASSERT( producer_authority::key_is_relevant(signee(), valid_block_signing_authority), wrong_signing_key, "block is signed with unexpected key" ); - } + EOS_ASSERT(!sigs.empty(), no_block_signatures, "Signer returned no signatures"); + header.producer_signature = sigs.back(); + sigs.pop_back(); + + additional_signatures = std::move(sigs); - public_key_type block_header_state::signee()const { - return fc::crypto::public_key( header.producer_signature, sig_digest(), true ); + verify_signee(); } void block_header_state::verify_signee( )const { - auto signing_key = signee(); + flat_set keys; + keys.reserve(1 + additional_signatures.size()); + keys.emplace(fc::crypto::public_key( header.producer_signature, sig_digest(), true )); + + for (const auto& s: additional_signatures) { + auto key = fc::crypto::public_key( s, sig_digest(), true ); + EOS_ASSERT(keys.find(key) != keys.end(), wrong_signing_key, + "block signed by same key twice", + ("key", key)); + + keys.emplace(std::move(key)); + } + + for (const auto& k: keys) { + EOS_ASSERT(producer_authority::key_is_relevant(k, valid_block_signing_authority), wrong_signing_key, + "block signed by unexpected key", + ("signing_key", k)("valid_keys", valid_block_signing_authority)); + } - EOS_ASSERT( producer_authority::key_is_relevant(signing_key, valid_block_signing_authority), wrong_signing_key, - "block not signed by expected key", - ("signing_key", signing_key)( "valid_keys", valid_block_signing_authority ) ); + EOS_ASSERT(producer_authority::keys_satisfy(keys, valid_block_signing_authority), wrong_signing_key, + "block signatures do not satisfy the block signing authority", + ("keys", keys)("authority", valid_block_signing_authority)); } /** diff --git a/libraries/chain/include/eosio/chain/block.hpp b/libraries/chain/include/eosio/chain/block.hpp index eee489e2961..6229c6760c0 100644 --- a/libraries/chain/include/eosio/chain/block.hpp +++ b/libraries/chain/include/eosio/chain/block.hpp @@ -51,13 +51,45 @@ namespace eosio { namespace chain { } }; + struct additional_block_signatures_extension : fc::reflect_init { + static constexpr uint16_t extension_id() { return 2; } + static constexpr bool enforce_unique() { return true; } + + additional_block_signatures_extension() = default; + + additional_block_signatures_extension( const vector& signatures ) + :signatures( signatures ) + {} + + additional_block_signatures_extension( vector&& signatures ) + :signatures( std::move(signatures) ) + {} + + void reflector_init(); + + vector signatures; + }; + + namespace detail { + template + struct block_extension_types { + using block_extension_t = fc::static_variant< Ts... >; + using decompose_t = decompose< Ts... >; + }; + } + + using block_extension_types = detail::block_extension_types< + additional_block_signatures_extension + >; + + using block_extension = block_extension_types::block_extension_t; /** */ - struct signed_block : public signed_block_header { - private: + struct signed_block : public signed_block_header{ + private: signed_block( const signed_block& ) = default; - public: + public: signed_block() = default; explicit signed_block( const signed_block_header& h ):signed_block_header(h){} signed_block( signed_block&& ) = default; @@ -66,6 +98,8 @@ namespace eosio { namespace chain { vector transactions; /// new or generated transactions extensions_type block_extensions; + + flat_multimap validate_and_extract_extensions()const; }; using signed_block_ptr = std::shared_ptr; @@ -83,4 +117,5 @@ FC_REFLECT_ENUM( eosio::chain::transaction_receipt::status_enum, FC_REFLECT(eosio::chain::transaction_receipt_header, (status)(cpu_usage_us)(net_usage_words) ) FC_REFLECT_DERIVED(eosio::chain::transaction_receipt, (eosio::chain::transaction_receipt_header), (trx) ) +FC_REFLECT(eosio::chain::additional_block_signatures_extension, (signatures)); FC_REFLECT_DERIVED(eosio::chain::signed_block, (eosio::chain::signed_block_header), (transactions)(block_extensions) ) diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 6b3deca5dfe..9fb7bbf2d67 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -47,6 +47,7 @@ struct pending_block_header_state : public detail::block_header_state_common { const protocol_feature_set& pfs)const; block_header_state finish_next( const signed_block_header& h, + const vector& additional_signatures, const protocol_feature_set& pfs, const std::function&, @@ -78,6 +79,7 @@ struct block_header_state : public detail::block_header_state_common { signed_block_header header; detail::schedule_info pending_schedule; protocol_feature_activation_set_ptr activated_protocol_features; + vector additional_signatures; /// this data is redundant with the data stored in header, but it acts as a cache that avoids /// duplication of work @@ -92,6 +94,7 @@ struct block_header_state : public detail::block_header_state_common { pending_block_header_state next( block_timestamp_type when, uint16_t num_prev_blocks_to_confirm )const; block_header_state next( const signed_block_header& h, + const vector& additional_signatures, const protocol_feature_set& pfs, const std::function&, @@ -102,12 +105,11 @@ struct block_header_state : public detail::block_header_state_common { uint32_t calc_dpos_last_irreversible( account_name producer_of_next_block )const; bool is_active_producer( account_name n )const; - producer_authority get_scheduled_producer( block_timestamp_type t )const; - const block_id_type& prev()const { return header.previous; } - digest_type sig_digest()const; - void sign( const signer_callback_type& signer ); - public_key_type signee()const; - void verify_signee()const; + producer_authority get_scheduled_producer( block_timestamp_type t )const; + const block_id_type& prev()const { return header.previous; } + digest_type sig_digest()const; + void sign( const signer_callback_type& signer ); + void verify_signee()const; const vector& get_new_protocol_feature_activations()const; }; diff --git a/libraries/chain/include/eosio/chain/exceptions.hpp b/libraries/chain/include/eosio/chain/exceptions.hpp index 28ff33b21db..3d571762725 100644 --- a/libraries/chain/include/eosio/chain/exceptions.hpp +++ b/libraries/chain/include/eosio/chain/exceptions.hpp @@ -204,7 +204,10 @@ namespace eosio { namespace chain { 3030010, "Invalid block header extension" ) FC_DECLARE_DERIVED_EXCEPTION( ill_formed_protocol_feature_activation, block_validate_exception, 3030011, "Block includes an ill-formed protocol feature activation extension" ) - + FC_DECLARE_DERIVED_EXCEPTION( invalid_block_extension, block_validate_exception, + 3030012, "Invalid block extension" ) + FC_DECLARE_DERIVED_EXCEPTION( ill_formed_additional_block_signatures_extension, block_validate_exception, + 3030013, "Block includes an ill-formed additional block signature extension" ) FC_DECLARE_DERIVED_EXCEPTION( transaction_exception, chain_exception, @@ -519,6 +522,10 @@ namespace eosio { namespace chain { 3170009, "Snapshot Finalization Exception" ) FC_DECLARE_DERIVED_EXCEPTION( invalid_protocol_features_to_activate, producer_exception, 3170010, "The protocol features to be activated were not valid" ) + FC_DECLARE_DERIVED_EXCEPTION( no_block_signatures, producer_exception, + 3170011, "The signer returned no valid block signatures" ) + FC_DECLARE_DERIVED_EXCEPTION( unsupported_multiple_block_signatures, producer_exception, + 3170011, "The signer returned multiple signatures but that is not supported" ) FC_DECLARE_DERIVED_EXCEPTION( reversible_blocks_exception, chain_exception, 3180000, "Reversible Blocks exception" ) diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index b2970d9d581..d2cf682fd6b 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -60,6 +60,10 @@ namespace eosio { namespace chain { }) != keys.end(); } + bool keys_satisfy( const flat_set& keys ) const { + return true; + } + friend bool operator == ( const block_signing_authority_v0& lhs, const block_signing_authority_v0& rhs ) { return tie( lhs.threshold, lhs.keys ) == tie( rhs.threshold, rhs.keys ); } @@ -84,6 +88,16 @@ namespace eosio { namespace chain { return key_is_relevant(key, authority); } + static bool keys_satisfy( const flat_set& keys, const block_signing_authority& authority ) { + return authority.visit([&keys](const auto &a){ + return a.keys_satisfy(keys); + }); + } + + bool keys_satisfy( const flat_set& keys ) const { + return keys_satisfy(keys, authority); + } + /** * ABI's for contracts expect variants to be serialized as a 2 entry array of * [type-name, value]. From 5f0a800598cbc535b252c101c10ea09eee7715a1 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 22 May 2019 14:53:30 -0400 Subject: [PATCH 14/36] support for multiple signatures on blocks via block level extension --- libraries/chain/CMakeLists.txt | 1 + libraries/chain/block.cpp | 12 +- libraries/chain/block_header_state.cpp | 7 +- libraries/chain/block_state.cpp | 117 +++++++++++++++++- .../eosio/chain/block_header_state.hpp | 5 +- .../include/eosio/chain/producer_schedule.hpp | 14 ++- 6 files changed, 139 insertions(+), 17 deletions(-) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 7ce955ae6a7..9ddadd5e8de 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -10,6 +10,7 @@ add_library( eosio_chain merkle.cpp name.cpp transaction.cpp + block.cpp block_header.cpp block_header_state.cpp block_state.cpp diff --git a/libraries/chain/block.cpp b/libraries/chain/block.cpp index cb9ed80cd07..91c93f6c359 100644 --- a/libraries/chain/block.cpp +++ b/libraries/chain/block.cpp @@ -5,25 +5,25 @@ namespace eosio { namespace chain { static_assert( fc::raw::has_feature_reflector_init_on_unpacked_reflected_types, "additional_block_signatures_extension expects FC to support reflector_init" ); - EOS_ASSERT( signatures.size() > 0, ill_additional_block_signatures_extension, + EOS_ASSERT( signatures.size() > 0, ill_formed_additional_block_signatures_extension, "Additional block signatures extension must contain at least one signature", ); - set s; + set unique_sigs; for( const auto& s : signatures ) { - auto res = s.insert( s ); - EOS_ASSERT( res.second, ill_additional_block_signatures_extension, + auto res = unique_sigs.insert( s ); + EOS_ASSERT( res.second, ill_formed_additional_block_signatures_extension, "Signature ${s} was repeated in the additional block signatures extension", ("s", s) ); } } - flat_multimap block::validate_and_extract_extensions()const { + flat_multimap signed_block::validate_and_extract_extensions()const { using decompose_t = block_extension_types::decompose_t; - flat_multimap results; + flat_multimap results; uint16_t id_type_lower_bound = 0; diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index cd940827add..65c2d8df546 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -314,7 +314,7 @@ namespace eosio { namespace chain { block_header_state pending_block_header_state::finish_next( const signed_block_header& h, - const vector& additional_signatures, + vector&& additional_signatures, const protocol_feature_set& pfs, const std::function&, @@ -375,14 +375,14 @@ namespace eosio { namespace chain { */ block_header_state block_header_state::next( const signed_block_header& h, - const vector& _additional_signatures, + vector&& _additional_signatures, const protocol_feature_set& pfs, const std::function&, const vector& )>& validator, bool skip_validate_signee )const { - return next( h.timestamp, h.confirmed ).finish_next( h, _additional_signatures, pfs, validator, skip_validate_signee ); + return next( h.timestamp, h.confirmed ).finish_next( h, std::move(_additional_signatures), pfs, validator, skip_validate_signee ); } digest_type block_header_state::sig_digest()const { @@ -392,7 +392,6 @@ namespace eosio { namespace chain { void block_header_state::sign( const signer_callback_type& signer ) { auto d = sig_digest(); - // TODO: modify block to allow multiple sigs auto sigs = signer( d ); EOS_ASSERT(!sigs.empty(), no_block_signatures, "Signer returned no signatures"); diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index cb8c1a2e5d1..8f51ffde7b2 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -3,6 +3,117 @@ namespace eosio { namespace chain { + namespace { + constexpr auto additional_sigs_eid = additional_block_signatures_extension::extension_id(); + + /** + * Given a complete signed block, extract the validated additional signatures if present; + * + * @param b complete signed block + * @param pfs protocol feature set for digest access + * @param pfa activated protocol feature set to determine if extensions are allowed + * @return the list of additional signatures + * @throws if additional signatures are present before being supported by protocol feature activations + */ + vector extract_additional_signatures( const signed_block_ptr& b, + const protocol_feature_set& pfs, + const protocol_feature_activation_set_ptr& pfa ) + { + auto exts = b->validate_and_extract_extensions(); + + if ( pfa && exts.count(additional_sigs_eid) > 0 ) { + const auto& protocol_features = pfa->protocol_features; + auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); + bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + + EOS_ASSERT(wtmsig_enabled, block_validate_exception, + "Block contained additional_block_signatures_extension before activation of WTMsig Block Signatures"); + + auto& additional_sigs = exts.lower_bound(additional_sigs_eid)->second.get(); + + return std::move(additional_sigs.signatures); + } + + return {}; + } + + /** + * Given a pending block header state, wrap the promotion to a block header state such that additional signatures + * can be allowed based on activations *prior* to the promoted block and properly injected into the signed block + * that is previously constructed and mutated by the promotion + * + * This cleans up lifetime issues involved with accessing activated protocol features and moving from the + * pending block header state + * + * @param cur the pending block header state to promote + * @param b the signed block that will receive signatures during this process + * @param pfs protocol feature set for digest access + * @param extras all the remaining parameters that pass through + * @return the block header state + * @throws if the block was signed with multiple signatures before the extension is allowed + */ + + template + block_header_state inject_additional_signatures( pending_block_header_state&& cur, + signed_block& b, + const protocol_feature_set& pfs, + Extras&& ... extras ) + { + const auto& pfa = cur.prev_activated_protocol_features; + bool wtmsig_enabled = false; + + if (pfa) { + const auto& protocol_features = pfa->protocol_features; + auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); + wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + } + + block_header_state result = std::move(cur).finish_next(b, pfs, std::forward(extras)...); + + if (!result.additional_signatures.empty()) { + EOS_ASSERT(wtmsig_enabled, block_validate_exception, + "Block has multiple signatures before activation of WTMsig Block Signatures"); + + // 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::total_member_count == 1); + static_assert(std::is_same_v>); + + b.block_extensions.emplace_back( + additional_sigs_eid, + fc::raw::pack( result.additional_signatures ) + ); + } + + return result; + } + + /** + * Given a pending block header state, wrap the promotion to a block header state such that additional signatures + * can be extraced from the provided complete signed block and passed into the block header state promotion + * + * This cleans up lifetime issues involved with accessing activated protocol features and moving from the + * pending block header state + * + * @param cur the pending block header state to promote + * @param b the signed block complete with extensions and signatures + * @param pfs protocol feature set for digest access + * @param extras all the remaining parameters that pass through + * @return the block header state + */ + template + block_header_state promote_pending(pending_block_header_state&& cur, + const signed_block_ptr& b, + const protocol_feature_set& pfs, + Extras&& ... extras) + { + + auto additional_signatures = extract_additional_signatures(b, pfs, cur.prev_activated_protocol_features); + return std::move(cur).finish_next( *b, std::move(additional_signatures), pfs, std::forward(extras)... ); + } + + } + block_state::block_state( const block_header_state& prev, signed_block_ptr b, const protocol_feature_set& pfs, @@ -11,7 +122,7 @@ namespace eosio { namespace chain { const vector& )>& validator, bool skip_validate_signee ) - :block_header_state( prev.next( *b, pfs, validator, skip_validate_signee ) ) + :block_header_state( prev.next( *b, extract_additional_signatures(b, pfs, prev.activated_protocol_features), pfs, validator, skip_validate_signee ) ) ,block( std::move(b) ) {} @@ -24,7 +135,7 @@ namespace eosio { namespace chain { const vector& )>& validator, const signer_callback_type& signer ) - :block_header_state( std::move(cur).finish_next( *b, pfs, validator, signer ) ) + :block_header_state( inject_additional_signatures( std::move(cur), *b, pfs, validator, signer ) ) ,block( std::move(b) ) ,trxs( std::move(trx_metas) ) {} @@ -39,7 +150,7 @@ namespace eosio { namespace chain { const vector& )>& validator, bool skip_validate_signee ) - :block_header_state( std::move(cur).finish_next( *b, pfs, validator, skip_validate_signee ) ) + :block_header_state( promote_pending(std::move(cur), b, pfs, validator, skip_validate_signee ) ) ,block( b ) ,trxs( std::move(trx_metas) ) {} diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 9fb7bbf2d67..8723edfd17e 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -47,7 +47,7 @@ struct pending_block_header_state : public detail::block_header_state_common { const protocol_feature_set& pfs)const; block_header_state finish_next( const signed_block_header& h, - const vector& additional_signatures, + vector&& additional_signatures, const protocol_feature_set& pfs, const std::function&, @@ -94,7 +94,7 @@ struct block_header_state : public detail::block_header_state_common { pending_block_header_state next( block_timestamp_type when, uint16_t num_prev_blocks_to_confirm )const; block_header_state next( const signed_block_header& h, - const vector& additional_signatures, + vector&& additional_signatures, const protocol_feature_set& pfs, const std::function&, @@ -141,4 +141,5 @@ FC_REFLECT_DERIVED( eosio::chain::block_header_state, (eosio::chain::detail::bl (header) (pending_schedule) (activated_protocol_features) + (additional_signatures) ) diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index d2cf682fd6b..9ca24d70e6d 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -60,8 +60,18 @@ namespace eosio { namespace chain { }) != keys.end(); } - bool keys_satisfy( const flat_set& keys ) const { - return true; + bool keys_satisfy( const flat_set& presented_keys ) const { + uint32_t total_weight = 0; + for (const auto& kw : keys) { + const auto& iter = presented_keys.find(kw.key); + if (iter != presented_keys.end()) { + total_weight += kw.weight; + } + + if (total_weight >= threshold) + return true; + } + return false; } friend bool operator == ( const block_signing_authority_v0& lhs, const block_signing_authority_v0& rhs ) { From 3cbbca2d58614999a0959fffe383cfcf0d73b603 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 22 May 2019 16:36:16 -0400 Subject: [PATCH 15/36] update expected exception message --- unittests/forked_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index 61e389b0de3..80d7b34646f 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -274,7 +274,7 @@ BOOST_AUTO_TEST_CASE( forking ) try { c.control->abort_block(); BOOST_REQUIRE_EXCEPTION(c.control->push_block( bad_block_bs ), fc::exception, [] (const fc::exception &ex)->bool { - return ex.to_detail_string().find("block not signed by expected key") != std::string::npos; + return ex.to_detail_string().find("block signed by unexpected key") != std::string::npos; }); } FC_LOG_AND_RETHROW() From b88bbda91ddbb4bf08e4888e7987b59573e34d13 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 22 May 2019 18:33:02 -0400 Subject: [PATCH 16/36] fix some bad accesses, remove the block extension check, add test cases for wtmsig block production, augment tester to accomodate --- libraries/chain/block_header_state.cpp | 19 +-- libraries/chain/controller.cpp | 1 - .../testing/include/eosio/testing/tester.hpp | 1 + libraries/testing/tester.cpp | 30 +++-- unittests/producer_schedule_tests.cpp | 109 ++++++++++++++++++ 5 files changed, 142 insertions(+), 18 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 65c2d8df546..744bf6a2251 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -322,15 +322,18 @@ namespace eosio { namespace chain { bool skip_validate_signee )&& { - auto result = std::move(*this)._finish_next( h, pfs, validator ); - if( !additional_signatures.empty() ) { auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); const auto& protocol_features = prev_activated_protocol_features->protocol_features; bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); EOS_ASSERT(wtmsig_enabled, producer_schedule_exception, "Block contains multiple signatures before WTMsig block signatures are enabled" ); - result.additional_signatures = additional_signatures; + } + + auto result = std::move(*this)._finish_next( h, pfs, validator ); + + if( !additional_signatures.empty() ) { + result.additional_signatures = std::move(additional_signatures); } // ASSUMPTION FROM controller_impl::apply_block = all untrusted blocks will have their signatures pre-validated here @@ -350,15 +353,15 @@ namespace eosio { namespace chain { const signer_callback_type& signer )&& { + auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); + const auto& protocol_features = prev_activated_protocol_features->protocol_features; + bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + auto result = std::move(*this)._finish_next( h, pfs, validator ); result.sign( signer ); h.producer_signature = result.header.producer_signature; if( !result.additional_signatures.empty() ) { - auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); - const auto& protocol_features = prev_activated_protocol_features->protocol_features; - bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); - EOS_ASSERT(wtmsig_enabled, producer_schedule_exception, "Block was signed with multiple signatures before WTMsig block signatures are enabled" ); } @@ -410,7 +413,7 @@ namespace eosio { namespace chain { for (const auto& s: additional_signatures) { auto key = fc::crypto::public_key( s, sig_digest(), true ); - EOS_ASSERT(keys.find(key) != keys.end(), wrong_signing_key, + EOS_ASSERT(keys.find(key) == keys.end(), wrong_signing_key, "block signed by same key twice", ("key", key)); diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 59f9fff2769..898ffed55e3 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1688,7 +1688,6 @@ struct controller_impl { const signed_block_ptr& b = bsp->block; const auto& new_protocol_feature_activations = bsp->get_new_protocol_feature_activations(); - EOS_ASSERT( b->block_extensions.size() == 0, block_validate_exception, "no supported block extensions" ); auto producer_block_id = b->id(); start_block( b->timestamp, b->confirmed, new_protocol_feature_activations, s, producer_block_id); diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index 444972c10a5..1b3c2b7daa0 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -173,6 +173,7 @@ namespace eosio { namespace testing { vector get_producer_authorities( const vector& producer_names )const; transaction_trace_ptr set_producers(const vector& producer_names); + transaction_trace_ptr set_producer_schedule(const vector& schedule); transaction_trace_ptr set_producers_legacy(const vector& producer_names); void link_authority( account_name account, account_name code, permission_name req, action_name type = "" ); diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index d9799d62966..d2287e26e88 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -309,19 +309,26 @@ namespace eosio { namespace testing { FC_ASSERT( control->is_building_block(), "must first start a block before it can be finished" ); auto producer = control->head_block_state()->get_scheduled_producer( control->pending_block_time() ); - private_key_type priv_key = producer.authority.visit([this, &producer](const block_signing_authority_v0& a){ - for (const auto& k: a.keys) { - auto private_key_itr = block_signing_private_keys.find( k.key ); - if (private_key_itr != block_signing_private_keys.end()) { - return private_key_itr->second; - } + vector signing_keys; + + for ( const auto& bsk : block_signing_private_keys ) { + if (producer.key_is_relevant(bsk.first)) { + signing_keys.push_back( bsk.second ); } + } - return get_private_key( producer.producer_name, "active" ); - }); + // if the "active" key is relevant or no other keys were found, add the "active" private key + if( signing_keys.empty() || producer.key_is_relevant(get_public_key( producer.producer_name, "active") ) ) { + signing_keys.emplace_back( get_private_key( producer.producer_name, "active") ); + } control->finalize_block( [&]( digest_type d ) { - return std::vector{priv_key.sign(d)}; + std::vector result; + result.reserve(signing_keys.size()); + for (const auto& k: signing_keys) + result.emplace_back(k.sign(d)); + + return result; } ); control->commit_block(); @@ -970,6 +977,10 @@ namespace eosio { namespace testing { transaction_trace_ptr base_tester::set_producers(const vector& producer_names) { auto schedule = get_producer_authorities( producer_names ); + return set_producer_schedule(schedule); + } + + transaction_trace_ptr base_tester::set_producer_schedule(const vector& schedule ) { // FC reflection does not create variants that are compatible with ABI 1.1 so we manually translate. fc::variants schedule_variant; schedule_variant.reserve(schedule.size()); @@ -979,6 +990,7 @@ namespace eosio { namespace testing { return push_action( config::system_account_name, N(setprods), config::system_account_name, fc::mutable_variant_object()("schedule", schedule_variant)); + } transaction_trace_ptr base_tester::set_producers_legacy(const vector& producer_names) { diff --git a/unittests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp index 7d8a20f959b..e74bf22e40b 100644 --- a/unittests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -502,4 +502,113 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( producer_one_of_n_test, TESTER ) try { + create_accounts( {N(alice),N(bob)} ); + while (control->head_block_num() < 3) { + produce_block(); + } + + auto compare_schedules = [&]( const vector& a, const producer_authority_schedule& b ) { + return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); + }; + + vector sch1 = { + producer_authority{N(alice), block_signing_authority_v0{1, {{get_public_key(N(alice), "bs1"), 1}, {get_public_key(N(alice), "bs2"), 1}}}}, + producer_authority{N(bob), block_signing_authority_v0{1, {{get_public_key(N(bob), "bs1"), 1}, {get_public_key(N(bob), "bs2"), 1}}}} + }; + + auto res = set_producer_schedule( sch1 ); + block_signing_private_keys.emplace(get_public_key(N(alice), "bs1"), get_private_key(N(alice), "bs1")); + block_signing_private_keys.emplace(get_public_key(N(bob), "bs1"), get_private_key(N(bob), "bs1")); + + //wdump((fc::json::to_pretty_string(res))); + wlog("set producer schedule to [alice,bob]"); + BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *control->proposed_producers() ) ); + BOOST_CHECK_EQUAL( control->pending_producers().version, 0u ); + produce_block(); // Starts new block which promotes the proposed schedule to pending + BOOST_CHECK_EQUAL( control->pending_producers().version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->pending_producers() ) ); + BOOST_CHECK_EQUAL( control->active_producers().version, 0u ); + produce_block(); + produce_block(); // Starts new block which promotes the pending schedule to active + BOOST_CHECK_EQUAL( control->active_producers().version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->active_producers() ) ); + produce_blocks(18); + + // fast forward until the first block of alices round + BOOST_REQUIRE_EQUAL( control->head_block_producer(), N(bob) ); + BOOST_REQUIRE_EQUAL( control->pending_block_producer(), N(alice) ); + + // produce with backup key by listing that in block signing keys + block_signing_private_keys.clear(); + block_signing_private_keys.emplace(get_public_key(N(alice), "bs2"), get_private_key(N(alice), "bs2")); + block_signing_private_keys.emplace(get_public_key(N(bob), "bs2"), get_private_key(N(bob), "bs2")); + + produce_blocks(config::producer_repetitions, false); + + // check that its bobs turn + BOOST_REQUIRE_EQUAL( control->head_block_producer(), N(alice) ); + BOOST_REQUIRE_EQUAL( control->pending_block_producer(), N(bob) ); + + produce_blocks(config::producer_repetitions, false); + + BOOST_REQUIRE_EQUAL( validate(), true ); +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( producer_m_of_n_test, TESTER ) try { + create_accounts( {N(alice),N(bob)} ); + while (control->head_block_num() < 3) { + produce_block(); + } + + auto compare_schedules = [&]( const vector& a, const producer_authority_schedule& b ) { + return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); + }; + + vector sch1 = { + producer_authority{N(alice), block_signing_authority_v0{2, {{get_public_key(N(alice), "bs1"), 1}, {get_public_key(N(alice), "bs2"), 1}}}}, + producer_authority{N(bob), block_signing_authority_v0{2, {{get_public_key(N(bob), "bs1"), 1}, {get_public_key(N(bob), "bs2"), 1}}}} + }; + + auto res = set_producer_schedule( sch1 ); + block_signing_private_keys.emplace(get_public_key(N(alice), "bs1"), get_private_key(N(alice), "bs1")); + block_signing_private_keys.emplace(get_public_key(N(alice), "bs2"), get_private_key(N(alice), "bs2")); + block_signing_private_keys.emplace(get_public_key(N(bob), "bs1"), get_private_key(N(bob), "bs1")); + block_signing_private_keys.emplace(get_public_key(N(bob), "bs2"), get_private_key(N(bob), "bs2")); + + //wdump((fc::json::to_pretty_string(res))); + wlog("set producer schedule to [alice,bob]"); + BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *control->proposed_producers() ) ); + BOOST_CHECK_EQUAL( control->pending_producers().version, 0u ); + produce_block(); // Starts new block which promotes the proposed schedule to pending + BOOST_CHECK_EQUAL( control->pending_producers().version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->pending_producers() ) ); + BOOST_CHECK_EQUAL( control->active_producers().version, 0u ); + produce_block(); + produce_block(); // Starts new block which promotes the pending schedule to active + BOOST_CHECK_EQUAL( control->active_producers().version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->active_producers() ) ); + produce_blocks(18); + + // fast forward until the first block of alices round + BOOST_REQUIRE_EQUAL( control->head_block_producer(), N(bob) ); + BOOST_REQUIRE_EQUAL( control->pending_block_producer(), N(alice) ); + + BOOST_REQUIRE_EQUAL( control->head_block_state()->additional_signatures.size(), 1); + + produce_blocks(config::producer_repetitions, false); + + BOOST_REQUIRE_EQUAL( control->head_block_state()->additional_signatures.size(), 1); + + // check that its bobs turn + BOOST_REQUIRE_EQUAL( control->head_block_producer(), N(alice) ); + BOOST_REQUIRE_EQUAL( control->pending_block_producer(), N(bob) ); + + produce_blocks(config::producer_repetitions, false); + + BOOST_REQUIRE_EQUAL( validate(), true ); +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() From 6eb6a2b93d04c792e11e969c923b326e027f4dac Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 23 May 2019 10:27:15 -0400 Subject: [PATCH 17/36] snapshot version support for the changes to block header state --- libraries/chain/block_header_state.cpp | 20 ++++++ libraries/chain/controller.cpp | 22 +++++-- .../eosio/chain/block_header_state.hpp | 64 ++++++++++++++++++- .../include/eosio/chain/chain_snapshot.hpp | 5 +- 4 files changed, 104 insertions(+), 7 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 744bf6a2251..f640e0ba1a5 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -443,4 +443,24 @@ namespace eosio { namespace chain { return header_exts.lower_bound(protocol_feature_activation::extension_id())->second.get().protocol_features; } + block_header_state::block_header_state( legacy::snapshot_block_header_state_v2&& snapshot ) + { + block_num = snapshot.block_num; + dpos_proposed_irreversible_blocknum = snapshot.dpos_proposed_irreversible_blocknum; + dpos_irreversible_blocknum = snapshot.dpos_irreversible_blocknum; + active_schedule = producer_authority_schedule( snapshot.active_schedule ); + blockroot_merkle = std::move(snapshot.blockroot_merkle); + producer_to_last_produced = std::move(snapshot.producer_to_last_produced); + producer_to_last_implied_irb = std::move(snapshot.producer_to_last_implied_irb); + valid_block_signing_authority = block_signing_authority_v0{ 1, {{std::move(snapshot.block_signing_key), 1}} }; + confirm_count = std::move(snapshot.confirm_count); + id = std::move(snapshot.id); + header = std::move(snapshot.header); + pending_schedule.schedule_lib_num = snapshot.pending_schedule.schedule_lib_num; + pending_schedule.schedule_hash = std::move(snapshot.pending_schedule.schedule_hash); + pending_schedule.schedule = producer_authority_schedule( snapshot.pending_schedule.schedule ); + activated_protocol_features = std::move(snapshot.activated_protocol_features); + } + + } } /// namespace eosio::chain diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 898ffed55e3..b5bfd25c535 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -805,16 +805,28 @@ struct controller_impl { } void read_from_snapshot( const snapshot_reader_ptr& snapshot, uint32_t blog_start, uint32_t blog_end ) { - snapshot->read_section([this]( auto §ion ){ - chain_snapshot_header header; + chain_snapshot_header header; + snapshot->read_section([this, &header]( auto §ion ){ section.read_row(header, db); header.validate(); }); - snapshot->read_section([this, blog_start, blog_end]( auto §ion ){ + { /// load an upgrade the block header state block_header_state head_header_state; - section.read_row(head_header_state, db); + using v2 = legacy::snapshot_block_header_state_v2; + + if (std::clamp(header.version, v2::minimum_version, v2::maximum_version) == header.version ) { + snapshot->read_section([this, &head_header_state]( auto §ion ) { + legacy::snapshot_block_header_state_v2 legacy_header_state; + section.read_row(legacy_header_state, db); + head_header_state = block_header_state(std::move(legacy_header_state)); + }); + } else { + snapshot->read_section([this,&head_header_state]( auto §ion ){ + section.read_row(head_header_state, db); + }); + } snapshot_head_block = head_header_state.block_num; EOS_ASSERT( blog_start <= (snapshot_head_block + 1) && snapshot_head_block <= blog_end, @@ -828,7 +840,7 @@ struct controller_impl { fork_db.reset( head_header_state ); head = fork_db.head(); snapshot_head_block = head->block_num; - }); + } controller_index_set::walk_indices([this, &snapshot]( auto utils ){ using value_t = typename decltype(utils)::index_t::value_type; diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 8723edfd17e..eda7d45b033 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -2,10 +2,47 @@ #include #include #include +#include #include namespace eosio { namespace chain { +namespace legacy { + + /** + * a fc::raw::unpack compatible version of the old block_state structure stored in + * version 2 snapshots + */ + struct snapshot_block_header_state_v2 { + static constexpr uint32_t minimum_version = 0; + static constexpr uint32_t maximum_version = 2; + static_assert(chain_snapshot_header::minimum_compatible_version <= maximum_version, "snapshot_block_header_state_v2 is no longer needed"); + + struct schedule_info { + uint32_t schedule_lib_num = 0; /// last irr block num + digest_type schedule_hash; + producer_schedule_type schedule; + }; + + /// from block_header_state_common + uint32_t block_num; + uint32_t dpos_proposed_irreversible_blocknum; + uint32_t dpos_irreversible_blocknum; + producer_schedule_type active_schedule; + incremental_merkle blockroot_merkle; + flat_map producer_to_last_produced; + flat_map producer_to_last_implied_irb; + public_key_type block_signing_key; + vector confirm_count; + + // from block_header_state + block_id_type id; + signed_block_header header; + schedule_info pending_schedule; + protocol_feature_activation_set_ptr activated_protocol_features; + }; +} + using signer_callback_type = std::function(const digest_type&)>; struct block_header_state; @@ -69,7 +106,6 @@ struct pending_block_header_state : public detail::block_header_state_common { const vector& )>& validator )&&; }; - /** * @struct block_header_state * @brief defines the minimum state necessary to validate transaction headers @@ -91,6 +127,8 @@ struct block_header_state : public detail::block_header_state_common { :detail::block_header_state_common( std::move(base) ) {} + explicit block_header_state( legacy::snapshot_block_header_state_v2&& snapshot ); + pending_block_header_state next( block_timestamp_type when, uint16_t num_prev_blocks_to_confirm )const; block_header_state next( const signed_block_header& h, @@ -143,3 +181,27 @@ FC_REFLECT_DERIVED( eosio::chain::block_header_state, (eosio::chain::detail::bl (activated_protocol_features) (additional_signatures) ) + + +FC_REFLECT( eosio::chain::legacy::snapshot_block_header_state_v2::schedule_info, + ( schedule_lib_num ) + ( schedule_hash ) + ( schedule ) +) + + +FC_REFLECT( eosio::chain::legacy::snapshot_block_header_state_v2, + ( block_num ) + ( dpos_proposed_irreversible_blocknum ) + ( dpos_irreversible_blocknum ) + ( active_schedule ) + ( blockroot_merkle ) + ( producer_to_last_produced ) + ( producer_to_last_implied_irb ) + ( block_signing_key ) + ( confirm_count ) + ( id ) + ( header ) + ( pending_schedule ) + ( activated_protocol_features ) +) diff --git a/libraries/chain/include/eosio/chain/chain_snapshot.hpp b/libraries/chain/include/eosio/chain/chain_snapshot.hpp index 5546b301999..9151b7dd566 100644 --- a/libraries/chain/include/eosio/chain/chain_snapshot.hpp +++ b/libraries/chain/include/eosio/chain/chain_snapshot.hpp @@ -15,10 +15,13 @@ struct chain_snapshot_header { * 2: Updated chain snapshot for v1.8.0 initial protocol features release: * - Incompatible with version 1. * - Adds new indices for: protocol_state_object and account_ram_correction_object + * 3: Updated chain snapshot for wtmsig block signing protocol feature: + * - forwards compatible with version 2 + * - the block header state changed to include producer authorities and additional signatures */ static constexpr uint32_t minimum_compatible_version = 2; - static constexpr uint32_t current_version = 2; + static constexpr uint32_t current_version = 3; uint32_t version = current_version; From c708ea2de0ad7d115d2446b5537860f9e006f49e Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 23 May 2019 14:45:39 -0400 Subject: [PATCH 18/36] work on snapshot version tests --- .../testing/include/eosio/testing/tester.hpp | 4 ++ libraries/testing/tester.cpp | 27 +++++++++++++ unittests/CMakeLists.txt | 4 +- unittests/snapshot_tests.cpp | 39 +++++++++++++++++++ unittests/snapshots.hpp.in | 20 ++++++++++ 5 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 unittests/snapshots.hpp.in diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index 1b3c2b7daa0..e8656812ef0 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -67,6 +67,10 @@ namespace eosio { namespace testing { std::vector read_wasm( const char* fn ); std::vector read_abi( const char* fn ); std::string read_wast( const char* fn ); + + std::string read_binary_snapshot( const char* fn ); + fc::variant read_json_snapshot( const char* fn ); + using namespace eosio::chain; fc::variant_object filter_fields(const fc::variant_object& filter, const fc::variant_object& value); diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index d2287e26e88..a61c60fa32f 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -4,11 +4,16 @@ #include #include #include +#include +#include +#include #include #include +namespace bio = boost::iostreams; + eosio::chain::asset core_from_string(const std::string& s) { return eosio::chain::asset::from_string(s + " " CORE_SYMBOL_NAME); } @@ -58,6 +63,28 @@ namespace eosio { namespace testing { return abi; } + namespace { + std::string read_gzipped_snapshot( const char* fn ) { + std::ifstream file(fn, std::ios_base::in | std::ios_base::binary); + bio::filtering_streambuf in; + + in.push(bio::gzip_decompressor()); + in.push(file); + + std::stringstream decompressed; + bio::copy(in, decompressed); + return decompressed.str(); + } + } + + std::string read_binary_snapshot( const char* fn ) { + return read_gzipped_snapshot(fn); + } + + fc::variant read_json_snapshot( const char* fn ) { + return fc::json::from_string( read_gzipped_snapshot(fn) ); + } + const fc::microseconds base_tester::abi_serializer_max_time{1000*1000}; // 1s for slow test machines bool expect_assert_message(const fc::exception& ex, string expected) { diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 82e723e5563..eddf542fe01 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -34,9 +34,11 @@ find_package(LLVM 4.0 REQUIRED CONFIG) link_directories(${LLVM_LIBRARY_DIR}) add_subdirectory(contracts) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/contracts.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/include/contracts.hpp ESCAPE_QUOTES) +add_subdirectory(snapshots) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/snapshots.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/include/snapshots.hpp ESCAPE_QUOTES) + ### BUILD UNIT TEST EXECUTABLE ### file(GLOB UNIT_TESTS "*.cpp") # find all unit test suites add_executable( unit_test ${UNIT_TESTS}) # build unit tests as one executable diff --git a/unittests/snapshot_tests.cpp b/unittests/snapshot_tests.cpp index a3749f9656a..542bfcdfaad 100644 --- a/unittests/snapshot_tests.cpp +++ b/unittests/snapshot_tests.cpp @@ -11,6 +11,7 @@ #include #include +#include using namespace eosio; using namespace testing; @@ -102,6 +103,10 @@ struct variant_snapshot_suite { return std::make_shared(buffer); } + template + static snapshot_t load_from_file() { + return Snapshot::json(); + } }; struct buffered_snapshot_suite { @@ -145,6 +150,10 @@ struct buffered_snapshot_suite { return std::make_shared(std::make_shared(buffer)); } + template + static snapshot_t load_from_file() { + return Snapshot::bin(); + } }; BOOST_AUTO_TEST_SUITE(snapshot_tests) @@ -252,4 +261,34 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_replay_over_snapshot, SNAPSHOT_SUITE, snapsho BOOST_REQUIRE_EQUAL(expected_post_integrity_hash.str(), snap_chain.control->calculate_integrity_hash().str()); } +BOOST_AUTO_TEST_CASE_TEMPLATE(test_compatible_versions, SNAPSHOT_SUITE, snapshot_suites) +{ + tester chain(setup_policy::preactivate_feature_and_new_bios); + + chain.create_account(N(snapshot)); + chain.produce_blocks(1); + chain.set_code(N(snapshot), contracts::snapshot_test_wasm()); + chain.set_abi(N(snapshot), contracts::snapshot_test_abi().data()); + chain.produce_blocks(1); + chain.control->abort_block(); + auto base_integrity_value = chain.control->calculate_integrity_hash(); + + { + auto v2 = SNAPSHOT_SUITE::template load_from_file(); + snapshotted_tester v2_tester(chain.get_config(), SNAPSHOT_SUITE::get_reader(v2), 0); + auto v2_integrity_value = v2_tester.control->calculate_integrity_hash(); + + // create a latest snapshot + auto latest_writer = SNAPSHOT_SUITE::get_writer(); + v2_tester.control->write_snapshot(latest_writer); + auto latest = SNAPSHOT_SUITE::finalize(latest_writer); + + // load the latest snapshot + snapshotted_tester latest_tester(chain.get_config(), SNAPSHOT_SUITE::get_reader(latest), 1); + auto latest_integrity_value = latest_tester.control->calculate_integrity_hash(); + + BOOST_REQUIRE_EQUAL(v2_integrity_value.str(), latest_integrity_value.str()); + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/snapshots.hpp.in b/unittests/snapshots.hpp.in new file mode 100644 index 00000000000..291c452199f --- /dev/null +++ b/unittests/snapshots.hpp.in @@ -0,0 +1,20 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once + +#define MAKE_READ_SNAPSHOT(NAME) \ + struct NAME {\ + static fc::variant json() { return read_json_snapshot ("${CMAKE_BINARY_DIR}/unittests/snapshots/" #NAME ".json.gz"); } \ + static std::string bin() { return read_binary_snapshot("${CMAKE_BINARY_DIR}/unittests/snapshots/" #NAME ".bin.gz" ); } \ + };\ + +namespace eosio { + namespace testing { + struct snapshots { + // v2 + MAKE_READ_SNAPSHOT(snap_v2) + }; + } /// eosio::testing +} /// eosio From 161f765637cf98a3c76db3712c7677f2c77049fa Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 23 May 2019 16:03:39 -0400 Subject: [PATCH 19/36] set the schedule_hash for block signature digests correctly based on the activation of the protocol features. Added v2 snapshots based on a deterministic blockchain and added test to make sure that recreateing the blockchain and loading the compatible snapshot produce the same integrity hash --- libraries/chain/block_header_state.cpp | 16 +++++++++------- libraries/chain/controller.cpp | 4 +++- unittests/snapshot_tests.cpp | 2 ++ unittests/snapshots/CMakeLists.txt | 2 ++ unittests/snapshots/snap_v2.bin.gz | Bin 0 -> 15756 bytes unittests/snapshots/snap_v2.json.gz | Bin 0 -> 38317 bytes 6 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 unittests/snapshots/CMakeLists.txt create mode 100644 unittests/snapshots/snap_v2.bin.gz create mode 100644 unittests/snapshots/snap_v2.json.gz diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index f640e0ba1a5..62dbb2c4e2e 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -234,12 +234,16 @@ namespace eosio { namespace chain { auto exts = h.validate_and_extract_header_extensions(); std::optional maybe_new_producer_schedule; + std::optional maybe_new_producer_schedule_hash; + bool wtmsig_enabled = false; - if( h.new_producers ) { + if (h.new_producers || exts.count(producer_schedule_change_extension::extension_id()) > 0 ) { auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); const auto& protocol_features = prev_activated_protocol_features->protocol_features; - bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + } + if( h.new_producers ) { EOS_ASSERT(!wtmsig_enabled, producer_schedule_exception, "Block header contains legacy producer schedule outdated by activation of WTMsig Block Signatures" ); EOS_ASSERT( !was_pending_promoted, producer_schedule_exception, "cannot set pending producer schedule in the same block in which pending was promoted to active" ); @@ -249,14 +253,11 @@ namespace eosio { namespace chain { EOS_ASSERT( prev_pending_schedule.schedule.producers.empty(), producer_schedule_exception, "cannot set new pending producers until last pending is confirmed" ); + maybe_new_producer_schedule_hash.emplace(digest_type::hash(new_producers)); maybe_new_producer_schedule.emplace(new_producers); } if ( exts.count(producer_schedule_change_extension::extension_id()) > 0 ) { - auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); - const auto& protocol_features = prev_activated_protocol_features->protocol_features; - bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); - EOS_ASSERT(wtmsig_enabled, producer_schedule_exception, "Block header producer_schedule_change_extension before activation of WTMsig Block Signatures" ); EOS_ASSERT( !was_pending_promoted, producer_schedule_exception, "cannot set pending producer schedule in the same block in which pending was promoted to active" ); @@ -266,6 +267,7 @@ namespace eosio { namespace chain { EOS_ASSERT( prev_pending_schedule.schedule.producers.empty(), producer_schedule_exception, "cannot set new pending producers until last pending is confirmed" ); + maybe_new_producer_schedule_hash.emplace(digest_type::hash(new_producer_schedule)); maybe_new_producer_schedule.emplace(new_producer_schedule); } @@ -295,7 +297,7 @@ namespace eosio { namespace chain { if( maybe_new_producer_schedule ) { result.pending_schedule.schedule = std::move(*maybe_new_producer_schedule); - result.pending_schedule.schedule_hash = digest_type::hash(result.pending_schedule.schedule); + result.pending_schedule.schedule_hash = std::move(*maybe_new_producer_schedule_hash); result.pending_schedule.schedule_lib_num = block_number; } else { if( was_pending_promoted ) { diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index b5bfd25c535..42bc992bcb3 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -432,11 +432,13 @@ struct controller_impl { void initialize_blockchain_state() { wlog( "Initializing new blockchain with genesis state" ); producer_authority_schedule initial_schedule = { 0, { producer_authority{config::system_account_name, block_signing_authority_v0{ 1, {{conf.genesis.initial_key, 1}} } } } }; + legacy::producer_schedule_type initial_legacy_schedule{ 0, {{config::system_account_name, conf.genesis.initial_key}} }; block_header_state genheader; genheader.active_schedule = initial_schedule; genheader.pending_schedule.schedule = initial_schedule; - genheader.pending_schedule.schedule_hash = fc::sha256::hash(initial_schedule); + // TODO: if wtmsig block signatures are enabled this should be the hash of the producer authority + genheader.pending_schedule.schedule_hash = fc::sha256::hash(initial_legacy_schedule); genheader.header.timestamp = conf.genesis.initial_timestamp; genheader.header.action_mroot = conf.genesis.compute_chain_id(); genheader.id = genheader.header.id(); diff --git a/unittests/snapshot_tests.cpp b/unittests/snapshot_tests.cpp index 542bfcdfaad..7d73f757e40 100644 --- a/unittests/snapshot_tests.cpp +++ b/unittests/snapshot_tests.cpp @@ -278,6 +278,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_compatible_versions, SNAPSHOT_SUITE, snapshot snapshotted_tester v2_tester(chain.get_config(), SNAPSHOT_SUITE::get_reader(v2), 0); auto v2_integrity_value = v2_tester.control->calculate_integrity_hash(); + BOOST_CHECK_EQUAL(v2_integrity_value.str(), base_integrity_value.str()); + // create a latest snapshot auto latest_writer = SNAPSHOT_SUITE::get_writer(); v2_tester.control->write_snapshot(latest_writer); diff --git a/unittests/snapshots/CMakeLists.txt b/unittests/snapshots/CMakeLists.txt new file mode 100644 index 00000000000..f22b0f72d2e --- /dev/null +++ b/unittests/snapshots/CMakeLists.txt @@ -0,0 +1,2 @@ +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v2.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v2.bin.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v2.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v2.json.gz COPYONLY ) diff --git a/unittests/snapshots/snap_v2.bin.gz b/unittests/snapshots/snap_v2.bin.gz new file mode 100644 index 0000000000000000000000000000000000000000..541a429ca6cdc103ee4bf08d7b0eff7065a2112d GIT binary patch literal 15756 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 literal 0 HcmV?d00001 diff --git a/unittests/snapshots/snap_v2.json.gz b/unittests/snapshots/snap_v2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..1c2cf69b83e8201afa687ece99baab392de1dc93 GIT binary patch literal 38317 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 rv(l^!KXT#%@-_qobC)T1)_EW$m Date: Thu, 23 May 2019 16:18:51 -0400 Subject: [PATCH 20/36] add a sigil that will trip once the unit test for version 2 is no longer necessary to maintain --- unittests/snapshot_tests.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/unittests/snapshot_tests.cpp b/unittests/snapshot_tests.cpp index 7d73f757e40..a0de5cb41bc 100644 --- a/unittests/snapshot_tests.cpp +++ b/unittests/snapshot_tests.cpp @@ -265,15 +265,19 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_compatible_versions, SNAPSHOT_SUITE, snapshot { tester chain(setup_policy::preactivate_feature_and_new_bios); + ///< Begin deterministic code to generate blockchain for comparison + // TODO: create a utility that will write new bin/json gzipped files based on this chain.create_account(N(snapshot)); chain.produce_blocks(1); chain.set_code(N(snapshot), contracts::snapshot_test_wasm()); chain.set_abi(N(snapshot), contracts::snapshot_test_abi().data()); chain.produce_blocks(1); chain.control->abort_block(); + ///< End deterministic code to generate blockchain for comparison auto base_integrity_value = chain.control->calculate_integrity_hash(); { + static_assert(chain_snapshot_header::minimum_compatible_version <= 2, "version 2 unit test is no longer needed. Please clean up data files"); auto v2 = SNAPSHOT_SUITE::template load_from_file(); snapshotted_tester v2_tester(chain.get_config(), SNAPSHOT_SUITE::get_reader(v2), 0); auto v2_integrity_value = v2_tester.control->calculate_integrity_hash(); From a17ff6b099d224094a08a05e8ffca2c03010aa8a Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 24 May 2019 11:54:36 -0400 Subject: [PATCH 21/36] bump state database version to reflect change from producer_key to producer_authority etc --- libraries/chain/controller.cpp | 12 ++---------- .../include/eosio/chain/database_header_object.hpp | 5 +++-- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index cca9e8d34b9..ecce8a2c496 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -590,16 +590,8 @@ struct controller_impl { // check database version const auto& header_idx = db.get_index().indices().get(); - if (database_header_object::minimum_version != 0) { - EOS_ASSERT(header_idx.begin() != header_idx.end(), bad_database_version_exception, - "state database version pre-dates versioning, please restore from a compatible snapshot or replay!"); - } else if ( header_idx.empty() ) { - // temporary code to upgrade from existing un-versioned state database - static_assert(database_header_object::minimum_version == 0, "remove this path once the minimum version moves"); - db.create([](const auto& header){ - // nothing to do here - }); - } + EOS_ASSERT(header_idx.begin() != header_idx.end(), bad_database_version_exception, + "state database version pre-dates versioning, please restore from a compatible snapshot or replay!"); const auto& header_itr = header_idx.begin(); header_itr->validate(); diff --git a/libraries/chain/include/eosio/chain/database_header_object.hpp b/libraries/chain/include/eosio/chain/database_header_object.hpp index e0da70944f0..06cca969065 100644 --- a/libraries/chain/include/eosio/chain/database_header_object.hpp +++ b/libraries/chain/include/eosio/chain/database_header_object.hpp @@ -26,10 +26,11 @@ namespace eosio { namespace chain { * - 0 : implied version when this header is absent * - 1 : initial version, prior to this no `database_header_object` existed in the shared memory file but * no changes to its format were made so it can be safely added to existing databases + * - 2 : change from producer_key to producer_authority for many in-memory structures */ - static constexpr uint32_t current_version = 1; - static constexpr uint32_t minimum_version = 0; + static constexpr uint32_t current_version = 2; + static constexpr uint32_t minimum_version = 2; id_type id; uint32_t version = current_version; From 629e9559665e00f9b2f7f9533b34a37210cece48 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Tue, 28 May 2019 11:02:34 -0400 Subject: [PATCH 22/36] update testnet.template to use the least encumbered but upgradable bios --- testnet.template | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/testnet.template b/testnet.template index 574d7ec9795..efdfd56004e 100644 --- a/testnet.template +++ b/testnet.template @@ -14,7 +14,9 @@ fi bioscontractpath=$BIOS_CONTRACT_PATH if [ -z "$bioscontractpath" ]; then - bioscontractpath="unittests/contracts/eosio.bios" + # this is defaulted to the version of bios that only requires the preactivate_feature + # newer versions may require + bioscontractpath="unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/" fi wddir=eosio-ignition-wd From 4778001edf771cdaa9617f2accea53282258e8b1 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Tue, 28 May 2019 16:47:53 -0400 Subject: [PATCH 23/36] fix compile errors and semantic errors from merge --- .../chain/include/eosio/chain/authority.hpp | 32 +++++++++++-------- .../include/eosio/chain/producer_schedule.hpp | 6 ++-- .../state_history_serialization.hpp | 2 +- unittests/protocol_feature_tests.cpp | 2 +- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/libraries/chain/include/eosio/chain/authority.hpp b/libraries/chain/include/eosio/chain/authority.hpp index a23896ac17e..ecc671771ff 100644 --- a/libraries/chain/include/eosio/chain/authority.hpp +++ b/libraries/chain/include/eosio/chain/authority.hpp @@ -111,6 +111,23 @@ struct shared_key_weight { return key_weight{key, weight}; } + static shared_key_weight convert(chainbase::allocator allocator, const key_weight& k) { + return k.key._storage.visit(overloaded { + [&](const auto& k1r1) { + return shared_key_weight(k1r1, k.weight); + }, + [&](const fc::crypto::webauthn::public_key& wa) { + fc::datastream dsz; + fc::raw::pack(dsz, wa); + shared_string wa_ss(dsz.tellp(), boost::container::default_init, std::move(allocator)); + fc::datastream ds(wa_ss.data(), wa_ss.size()); + fc::raw::pack(ds, wa); + + return shared_key_weight(std::move(wa_ss), k.weight); + } + }); + } + shared_public_key key; weight_type weight; @@ -192,20 +209,7 @@ struct shared_authority { keys.clear(); keys.reserve(a.keys.size()); for(const key_weight& k : a.keys) { - k.key._storage.visit(overloaded { - [&](const auto& k1r1) { - keys.emplace_back(k1r1, k.weight); - }, - [&](const fc::crypto::webauthn::public_key& wa) { - fc::datastream dsz; - fc::raw::pack(dsz, wa); - shared_string wa_ss(dsz.tellp(), boost::container::default_init, keys.get_allocator()); - fc::datastream ds(wa_ss.data(), wa_ss.size()); - fc::raw::pack(ds, wa); - - keys.emplace_back(std::move(wa_ss), k.weight); - } - }); + keys.emplace_back(shared_key_weight::convert(keys.get_allocator(), k)); } accounts = decltype(accounts)(a.accounts.begin(), a.accounts.end(), accounts.get_allocator()); waits = decltype(waits)(a.waits.begin(), a.waits.end(), waits.get_allocator()); diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index 9ca24d70e6d..501342ebb41 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -139,8 +139,8 @@ namespace eosio { namespace chain { shared_block_signing_authority_v0( chainbase::allocator alloc ) :keys(alloc){} - uint32_t threshold; - shared_vector keys; + uint32_t threshold; + shared_vector keys; friend bool operator == ( const shared_block_signing_authority_v0& lhs, const shared_block_signing_authority_v0& rhs ) { return tie( lhs.threshold, lhs.keys ) == tie( rhs.threshold, rhs.keys ); @@ -197,7 +197,7 @@ namespace eosio { namespace chain { result.keys.clear(); result.keys.reserve(src.keys.size()); for (const auto& k: src.keys) { - result.keys.push_back(k); + result.keys.emplace_back(shared_key_weight::convert(alloc, k)); } return result; diff --git a/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp b/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp index a485e033c3f..96e1bb76662 100644 --- a/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp +++ b/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_serialization.hpp @@ -273,7 +273,7 @@ datastream& operator<<(datastream& ds, const history_serial_wrapper& obj) { fc::raw::pack(ds, as_type(obj.obj.threshold)); history_serialize_container(ds, obj.db, - as_type>(obj.obj.keys)); + as_type>(obj.obj.keys)); } diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp index 4bb5945e546..fc905f92790 100644 --- a/unittests/protocol_feature_tests.cpp +++ b/unittests/protocol_feature_tests.cpp @@ -1339,7 +1339,7 @@ BOOST_AUTO_TEST_CASE( webauthn_producer ) { try { c.create_account(N(waprod)); c.produce_block(); - vector waprodsched = {{N(waprod), public_key_type("PUB_WA_WdCPfafVNxVMiW5ybdNs83oWjenQXvSt1F49fg9mv7qrCiRwHj5b38U3ponCFWxQTkDsMC"s)}}; + vector waprodsched = {{N(waprod), block_signing_authority_v0{ 1, { {public_key_type("PUB_WA_WdCPfafVNxVMiW5ybdNs83oWjenQXvSt1F49fg9mv7qrCiRwHj5b38U3ßonCFWxQTkDsMC"s), 1} } } } }; BOOST_CHECK_THROW( c.push_action(config::system_account_name, N(setprods), config::system_account_name, fc::mutable_variant_object()("schedule", waprodsched)), From 0d91d0a793ec1c846337c4f38aebf228acd9cebd Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Tue, 28 May 2019 18:17:18 -0400 Subject: [PATCH 24/36] convert test back to legacy producer keys --- unittests/protocol_feature_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp index fc905f92790..8c07c062a06 100644 --- a/unittests/protocol_feature_tests.cpp +++ b/unittests/protocol_feature_tests.cpp @@ -1339,7 +1339,7 @@ BOOST_AUTO_TEST_CASE( webauthn_producer ) { try { c.create_account(N(waprod)); c.produce_block(); - vector waprodsched = {{N(waprod), block_signing_authority_v0{ 1, { {public_key_type("PUB_WA_WdCPfafVNxVMiW5ybdNs83oWjenQXvSt1F49fg9mv7qrCiRwHj5b38U3ßonCFWxQTkDsMC"s), 1} } } } }; + vector waprodsched = {{N(waprod), public_key_type("PUB_WA_WdCPfafVNxVMiW5ybdNs83oWjenQXvSt1F49fg9mv7qrCiRwHj5b38U3ßonCFWxQTkDsMC"s)}}; BOOST_CHECK_THROW( c.push_action(config::system_account_name, N(setprods), config::system_account_name, fc::mutable_variant_object()("schedule", waprodsched)), From e91a7e06ffb18bce46e54afea58c20e33e733064 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 29 May 2019 09:03:24 -0400 Subject: [PATCH 25/36] fix errant unicode typo that I introduced --- unittests/protocol_feature_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp index 8c07c062a06..d97534ea5a0 100644 --- a/unittests/protocol_feature_tests.cpp +++ b/unittests/protocol_feature_tests.cpp @@ -1339,7 +1339,7 @@ BOOST_AUTO_TEST_CASE( webauthn_producer ) { try { c.create_account(N(waprod)); c.produce_block(); - vector waprodsched = {{N(waprod), public_key_type("PUB_WA_WdCPfafVNxVMiW5ybdNs83oWjenQXvSt1F49fg9mv7qrCiRwHj5b38U3ßonCFWxQTkDsMC"s)}}; + vector waprodsched = {{N(waprod), public_key_type("PUB_WA_WdCPfafVNxVMiW5ybdNs83oWjenQXvSt1F49fg9mv7qrCiRwHj5b38U3ponCFWxQTkDsMC"s)}}; BOOST_CHECK_THROW( c.push_action(config::system_account_name, N(setprods), config::system_account_name, fc::mutable_variant_object()("schedule", waprodsched)), From e5fcca3352b6a5a9b9607e6bf6c034ea2c7e5d42 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 29 May 2019 09:12:36 -0400 Subject: [PATCH 26/36] update more test drivers for preactivate bios w/o producer authorities --- tests/Cluster.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index 1ba4b64d749..1b40aaecae1 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -951,7 +951,7 @@ def bios_bootstrap(self, biosNode, totalNodes, pfSetupPolicy, silent=False): "FEATURE_DIGESTS": "" } if PFSetupPolicy.hasPreactivateFeature(pfSetupPolicy): - env["BIOS_CONTRACT_PATH"] = "unittests/contracts/eosio.bios" + env["BIOS_CONTRACT_PATH"] = "unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/" if pfSetupPolicy == PFSetupPolicy.FULL: allBuiltinProtocolFeatureDigests = biosNode.getAllBuiltinFeatureDigestsToPreactivate() @@ -1069,7 +1069,7 @@ def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPoli contract="eosio.bios" contractDir="unittests/contracts/%s" % (contract) if PFSetupPolicy.hasPreactivateFeature(pfSetupPolicy): - contractDir="unittests/contracts/%s" % (contract) + contractDir="unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/%s" % (contract) else: contractDir="unittests/contracts/old_versions/v1.6.0-rc3/%s" % (contract) wasmFile="%s.wasm" % (contract) From 02732946800a1f7f8c5fee984ada3249b3f0dbb5 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 29 May 2019 16:18:59 -0400 Subject: [PATCH 27/36] fix more NP tests, also fix producer plugins capture of data that goes stale before its use in finalize_block --- plugins/producer_plugin/producer_plugin.cpp | 22 ++++++++++++--------- testnet.template | 5 +++-- tests/Cluster.py | 2 +- tests/Node.py | 2 +- tests/prod_preactivation_test.py | 2 +- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 86bff2ae82d..da78085ecde 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -1870,11 +1870,17 @@ void producer_plugin_impl::produce_block() { const auto& auth = chain.pending_block_signing_authority(); - auto num_relevant_signatures = std::count_if(_signature_providers.begin(), _signature_providers.end(), [&auth](const auto& p){ - return producer_authority::key_is_relevant(p.first, auth); - }); + std::vector> relevant_providers; + + relevant_providers.reserve(_signature_providers.size()); + + for (const auto& p : _signature_providers) { + if (producer_authority::key_is_relevant(p.first, auth)) { + relevant_providers.emplace_back(p.second); + } + } - EOS_ASSERT(num_relevant_signatures > 0, producer_priv_key_not_found, "Attempting to produce a block for which we don't have any relevant private keys"); + EOS_ASSERT(relevant_providers.size() > 0, producer_priv_key_not_found, "Attempting to produce a block for which we don't have any relevant private keys"); if (_protocol_features_signaled) { _protocol_features_to_activate.clear(); // clear _protocol_features_to_activate as it is already set in pending_block @@ -1885,13 +1891,11 @@ void producer_plugin_impl::produce_block() { chain.finalize_block( [&]( const digest_type& d ) { auto debug_logger = maybe_make_debug_time_logger(); vector sigs; - sigs.reserve(num_relevant_signatures); + sigs.reserve(relevant_providers.size()); // sign with all relevant public keys - for (const auto& p : _signature_providers) { - if (producer_authority::key_is_relevant(p.first, auth)) { - sigs.emplace_back(p.second(d)); - } + for (const auto& p : relevant_providers) { + sigs.emplace_back(p.get()(d)); } return sigs; } ); diff --git a/testnet.template b/testnet.template index efdfd56004e..69506c34e7a 100644 --- a/testnet.template +++ b/testnet.template @@ -16,7 +16,7 @@ bioscontractpath=$BIOS_CONTRACT_PATH if [ -z "$bioscontractpath" ]; then # this is defaulted to the version of bios that only requires the preactivate_feature # newer versions may require - bioscontractpath="unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/" + bioscontractpath="unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios" fi wddir=eosio-ignition-wd @@ -83,7 +83,8 @@ wcmd create --to-console -n ignition # where $BIOSKEY is replaced by the private key for the bios node # ------ DO NOT ALTER THE NEXT LINE ------- ###INSERT prodkeys - +echo "Activated Features Check:" >> $logfile +curl http://$bioshost:$biosport/v1/chain/get_activated_protocol_features >> $logfile ecmd set contract eosio $bioscontractpath eosio.bios.wasm eosio.bios.abi # Preactivate all digests diff --git a/tests/Cluster.py b/tests/Cluster.py index 1b40aaecae1..989d9477672 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -951,7 +951,7 @@ def bios_bootstrap(self, biosNode, totalNodes, pfSetupPolicy, silent=False): "FEATURE_DIGESTS": "" } if PFSetupPolicy.hasPreactivateFeature(pfSetupPolicy): - env["BIOS_CONTRACT_PATH"] = "unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios/" + env["BIOS_CONTRACT_PATH"] = "unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/eosio.bios" if pfSetupPolicy == PFSetupPolicy.FULL: allBuiltinProtocolFeatureDigests = biosNode.getAllBuiltinFeatureDigestsToPreactivate() diff --git a/tests/Node.py b/tests/Node.py index f8167663c28..07cc3158175 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -1516,7 +1516,7 @@ def activatePreactivateFeature(self): self.scheduleProtocolFeatureActivations([preactivateFeatureDigest]) # Wait for the next block to be produced so the scheduled protocol feature is activated - self.waitForHeadToAdvance() + assert self.waitForHeadToAdvance(), print("ERROR: TIMEOUT WAITING FOR PREACTIVATE") # Return an array of feature digests to be preactivated in a correct order respecting dependencies # Require producer_api_plugin diff --git a/tests/prod_preactivation_test.py b/tests/prod_preactivation_test.py index 200477c7356..dbc96c2c457 100755 --- a/tests/prod_preactivation_test.py +++ b/tests/prod_preactivation_test.py @@ -108,7 +108,7 @@ node0 = cluster.getNode(0) contract="eosio.bios" - contractDir="unittests/contracts/%s" % (contract) + contractDir="unittests/contracts/old_versions/v1.7.0-develop-preactivate_feature/%s" % (contract) wasmFile="%s.wasm" % (contract) abiFile="%s.abi" % (contract) From 7f68827e3d1ccf94cb216fadc88f035398f2a995 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Tue, 16 Jul 2019 18:34:47 -0400 Subject: [PATCH 28/36] fix snapshot versioning test to reflect 1.8.1 changes --- unittests/snapshot_tests.cpp | 89 +++++++++++++++++++++++++++- unittests/snapshots/snap_v2.bin.gz | Bin 15756 -> 15717 bytes unittests/snapshots/snap_v2.json.gz | Bin 38317 -> 38299 bytes 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/unittests/snapshot_tests.cpp b/unittests/snapshot_tests.cpp index a0de5cb41bc..e7cf449b119 100644 --- a/unittests/snapshot_tests.cpp +++ b/unittests/snapshot_tests.cpp @@ -261,6 +261,85 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_replay_over_snapshot, SNAPSHOT_SUITE, snapsho BOOST_REQUIRE_EQUAL(expected_post_integrity_hash.str(), snap_chain.control->calculate_integrity_hash().str()); } +namespace { + void variant_diff_helper(const fc::variant& lhs, const fc::variant& rhs, std::function&& out){ + if (lhs.get_type() != rhs.get_type()) { + out("", lhs, rhs); + } else if (lhs.is_object() ) { + const auto& l_obj = lhs.get_object(); + const auto& r_obj = rhs.get_object(); + static const std::string sep = "."; + + // test keys from LHS + std::set keys; + for (const auto& entry: l_obj) { + const auto& l_val = entry.value(); + const auto& r_iter = r_obj.find(entry.key()); + if (r_iter == r_obj.end()) { + out(sep + entry.key(), l_val, fc::variant()); + } else { + const auto& r_val = r_iter->value(); + variant_diff_helper(l_val, r_val, [&out, &entry](const std::string& path, const fc::variant& lhs, const fc::variant& rhs){ + out(sep + entry.key() + path, lhs, rhs); + }); + } + + keys.insert(entry.key()); + } + + // print keys in RHS that were not tested + for (const auto& entry: r_obj) { + if (keys.find(entry.key()) != keys.end()) { + continue; + } + const auto& r_val = entry.value(); + out(sep + entry.key(), fc::variant(), r_val); + } + } else if (lhs.is_array()) { + const auto& l_arr = lhs.get_array(); + const auto& r_arr = rhs.get_array(); + + // diff common + auto common_count = std::min(l_arr.size(), r_arr.size()); + for (size_t idx = 0; idx < common_count; idx++) { + const auto& l_val = l_arr.at(idx); + const auto& r_val = r_arr.at(idx); + variant_diff_helper(l_val, r_val, [&](const std::string& path, const fc::variant& lhs, const fc::variant& rhs){ + out( std::string("[") + std::to_string(idx) + std::string("]") + path, lhs, rhs); + }); + } + + // print lhs additions + for (size_t idx = common_count; idx < lhs.size(); idx++) { + const auto& l_val = l_arr.at(idx); + out( std::string("[") + std::to_string(idx) + std::string("]"), l_val, fc::variant()); + } + + // print rhs additions + for (size_t idx = common_count; idx < rhs.size(); idx++) { + const auto& r_val = r_arr.at(idx); + out( std::string("[") + std::to_string(idx) + std::string("]"), fc::variant(), r_val); + } + + } else if (!(lhs == rhs)) { + out("", lhs, rhs); + } + } + + void print_variant_diff(const fc::variant& lhs, const fc::variant& rhs) { + variant_diff_helper(lhs, rhs, [](const std::string& path, const fc::variant& lhs, const fc::variant& rhs){ + std::cout << path << std::endl; + if (!lhs.is_null()) { + std::cout << " < " << fc::json::to_pretty_string(lhs) << std::endl; + } + + if (!rhs.is_null()) { + std::cout << " > " << fc::json::to_pretty_string(rhs) << std::endl; + } + }); + } +} + BOOST_AUTO_TEST_CASE_TEMPLATE(test_compatible_versions, SNAPSHOT_SUITE, snapshot_suites) { tester chain(setup_policy::preactivate_feature_and_new_bios); @@ -276,12 +355,16 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_compatible_versions, SNAPSHOT_SUITE, snapshot ///< End deterministic code to generate blockchain for comparison auto base_integrity_value = chain.control->calculate_integrity_hash(); + // create a latest snapshot + auto base_writer = SNAPSHOT_SUITE::get_writer(); + chain.control->write_snapshot(base_writer); + auto base = SNAPSHOT_SUITE::finalize(base_writer); + { static_assert(chain_snapshot_header::minimum_compatible_version <= 2, "version 2 unit test is no longer needed. Please clean up data files"); auto v2 = SNAPSHOT_SUITE::template load_from_file(); snapshotted_tester v2_tester(chain.get_config(), SNAPSHOT_SUITE::get_reader(v2), 0); auto v2_integrity_value = v2_tester.control->calculate_integrity_hash(); - BOOST_CHECK_EQUAL(v2_integrity_value.str(), base_integrity_value.str()); // create a latest snapshot @@ -289,6 +372,10 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_compatible_versions, SNAPSHOT_SUITE, snapshot v2_tester.control->write_snapshot(latest_writer); auto latest = SNAPSHOT_SUITE::finalize(latest_writer); + if (std::is_same_v && v2_integrity_value.str() != base_integrity_value.str()) { + print_variant_diff(base, latest); + } + // load the latest snapshot snapshotted_tester latest_tester(chain.get_config(), SNAPSHOT_SUITE::get_reader(latest), 1); auto latest_integrity_value = latest_tester.control->calculate_integrity_hash(); diff --git a/unittests/snapshots/snap_v2.bin.gz b/unittests/snapshots/snap_v2.bin.gz index 541a429ca6cdc103ee4bf08d7b0eff7065a2112d..6c2818acee121589a09bd8675162009b12dc0ef0 100644 GIT binary patch delta 13621 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 VvTh~VRWko}iYte`je7Lk{{fU%AHx6u diff --git a/unittests/snapshots/snap_v2.json.gz b/unittests/snapshots/snap_v2.json.gz index 1c2cf69b83e8201afa687ece99baab392de1dc93..6689f080f0021ff293684c5c4d72520e5fc99224 100644 GIT binary patch delta 17804 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 Vm)Pz{3J*X139`oqPv6_dPyi#GTZ#Yx delta 17833 zcmW)Hc{tnY7XNSV+&k@bTDsEK*4@@pt$oWKuBx_VXi>FfR8d>h8cXDR@72;aC2j3% zMis3fB?z+JQq(>KK_nR>(ujx%iOBZr@Auz(p7%WGdEWD!^PJCnKIc?m{??57+s_8b z{rejPt3m&#^=IQB0hIvne@o*Ab?J{%%_{9@YT=EK&oxP(W7|LOfPMt+pP|3H^%zk{ z^Jx2&ae6HJN~7CY*J;=q>4JypPU00_PK!rD1g?07dSS?Z#-}j3BJaT;G<@#WRhH(r zI^Z7i-(+5Pv#hHnm>JryI}Y7fz)^h6P>;Qz*Em`WfBRFXKfo_M-O=TP^}S7c;B3?O zVd~Ybj_B*nvux;TnyL484QIyTa)#pVQ_LDLeVv0@j zLZ&b_F0dIBQbhOE!Rz?Sr!Qx4A3{(jQ4Jv|YIKJm_;yrXrLW1ai2Y|R)#7U+81UeXc*vH>7N>x{aMefyWT9vl z4;!1`<*swVnKJmqK$P=EeDp%_S9~Q5VK+ zhc6!3gN=Vb{s@IGGi-@2~Hd z#k&jWP)DMY$8QHLdb*X0+nm4{^=e=L{N%RdhLj*m!K5(Ap6sLVy-`h`=;8ZA)kyT6jnkcRG`nidDbH(IX(4o9oM~2yqlbH3U4qqyBw1^rz$T* z-L8Uw3iGkK%56pRl(B&)Rch0$oV|LBX>f>;pzcgcODwHERKlZANaeZ6&{UNy=_nq< z}yk#L!h9b%DO&fLgvCL^p`<7_1m)Iv2IIxN6=MDee;MrmoUBkL(AQm;1)zbc}?cIB&AY0YxmOQfw`D8jg9Y~+u2-epyn9b@A6m~sW zbDX&-6ozNawLHzp@2O3-=w$5fS|5@*?8Mu|n@yMisIXl#vmIRaY9spg7?vRwq$tUb z#df;r90WLtU6d#Yk#5>%M6*AgEZ`~=yqV^tb=1kxqRZc}zinHSGS?{*J{w`bt_#frtJ^-fc17r`4EBKC zzhW|MiGqU_nnSW=ObZWPMCf*8q27+cUJzV~uYCl7(7DJJ#crstG`=r+x>|@rO2FojCQ(%BzM9cc z!hL@8Y_7D10de3+uEjMk4*?Y-$wD)czY-&SWKVE3aBg8eVbI~q+_t(;7EVd~2(LRE-R-_qi&IZhB+J*f zhk8Z_Kj{yHQ^v>Ga>ve;VFT+`dowMLrai~9iK6N5rl(OM9Rg%gDZsr~p78XxV`3(3 ztIOWGM-YN%674|xIkIc(8e+s98hfi|Yc=bpCjZ*#(6DOW+iIKBN04Is2?R$!ZjvPR z7zQGsQ@3X<;IEpSe=A||y?Sy5^6~l2vlRcQGAoKBMK|?93IAlziy7fEUMLm1s*8Ia zdq61HMZh_Vl%7eh>RglhI%!z*9jDI+=ScGXX?KKExZ11vgCzJUYgPIb=c^8WS_+?! z7EttpHTn{aU-{}2To#@3nNN4K(o-Gz#dPEZgz`nsS?;;Y?Q>dRvHXHredu%}m(iui zk#^Bm!%Uq|UN98=T0E#G@Lcapm7i3uKo#Kp_ir49lVT7^S3R(1d~@Olu6=Rt1iWHUHYoS!+kRQ_jQwQ?IGALS zMd{JZiIGYN`RsTW$zMzGjjWmEFv=|v+-zX@h|RvB)9!NN&<2d%u{{YR7iy8yP&d9* zg8pw(ri`q#q6j(SGmCwoddz)%04rYIyyFCA1?%hotAuaY%DN@j=4a9(nuVj{F+{^- zIW%%UUp#9rsC}>%x_vu5xoTppp;Oym zu`MZSN>9ZZYzS*8Lf6FkP08P8ik#c zRV7h~3n!j#8K8%`gtuHmAuM!Y2T*bp3d^`d*>6}&x0hOU>X(P2eIRgq`{;g~rR%){ zkz3{#MJ+>>X)$>>uHSmw{=lR6*yDTgv+o}G@up96nu5SOBxqVj{!~cXjktQ! zhXssZ%vfUG&Y$m)*b>f&wp3JTM?V&f0#r zGbx^RCp4o}BNSR7BpR4iKN2(t8Ymr_SxuX2VL9bQv8qiVqBx1Dx61F?2P`&Qu5sGu zU-XLO7e3CsOT7PoIzs#~g~NG?*Ejmd6o-O}QyaZWK^`$3(C&)qVE|b6SEtg|_I{&p z6&?K8&beV!1r~yR18b{yLH&fN7rB`QBZq$qh1E0R;WT%Hbx*9V2 z6I=F2An`o8=hjs6O0ZICHs`h;P~Nu z1BR>(XWUbIWsvcwF~4$$2hZwRJl)5cjRdX?76kN6y&ZqlnYNenVeXO^Qpj_7G4^Pr zU{!p05!=fMJ^DA8=nCuZ^6%w&Q$ANRZk0<5rp5z;ngx~qx6}1{Jh}i4taZ%=FfTIn z$kVHir$@!~zKA?f6Z9Vyf1_J)++Ku#8gL87eU1|$Uwir-p}2fo3wqX_Y4yR^&yWOs zmIUZOFaZC=!}~0LMG7mm{qIC(dtV~}WfqzwOD}ZfAw$@F)3I|ZGb;_#D8nUDX20M| zfb{`AN`WQhP6WZ2PwItEZOd|VYUZNA2vu~Z{`HVf!->d3y@8u%soSFCmB$g{o{hB&H5;8xY#k8WQPwYiH@Y4+is{+Y1aCZj%@NNjB z0q1!F?c+C-VQ=2JmE5zpD-iQG+>g^6FutRR)CZ0gj$oZ|@k4c&$2j@WOTNA|XX%1w zONC+npp6Uv6LFYsd|zSB2+?s^vEpekVG-;=+;0sPV`O`&E>Pp-12Gg8{-4>&{r4DlHEiwd1(YUIqP zct;fG^;XAZT_r*Enhe3V<&oW#4iUZQ$9<7Rqf~=_Z;-sxPS;WY>2r#me!K<(AU;y1 zJ+Xp2!bgKOs`F>PN?&Aej30hhUgsa^kmETutL(bDz`Quxn*?l~5f=LTL(v0)+@*J) zlV6{3B9dxLx%jmzQIONo*tSk<&3>8R2E?m~_;q}@>F6vNLl;9;YX<}3lk}xFD4hzo zw`#|YD0fQ5(VuK%1Es0J_T6<-Z?W@nO*xTgxjycq=c28J+7=7d&#g$BL)jZM5D*f3 zJJxxdm&XDi>L*X^hWmu3t0sUv_kmiLi6$O3Cv7_Veyj^1J&3o&6y5Mc@)OQuF6?xL zL`zKSd~Cd2%A!fyHnmXWTPAr>T*adqkZ3u>Jmy}sx6fK3`-W4Tbxgwfn;y;6AFa!G zLTa`8i*5vcEVLi9L@r0;LYk8^>Odlu7=j>e98hvW$(al}MPC#5=BTA%!}SX)VBe_^%F@Re+%A0ynZxa`uDc*-u^qDDgXI7=btV|+rqvoWC7YkC*Q6! zvsTv;G4<8X48xy0^{-rMmLK|VaQqaYawn-xUmAd&iN7omD(E8>fFdj-AyFnWQ19+> zR@j1}Z9Zpg5MWl40UPBWUcE;zsgMa+DiNS!0hur(opkR1a8)5){He7z-HPv&p} zgz%&~fEMTgamZ|)3S?APgCJStR{eLOVS@gFRirkbx>-5FsvD)^2q54U2bhd9Wp8R#kIz>>b+d1Q?Aa2SLhOn&Y;|GpV?-8Sz+& zIe5Z4dq>E85`O-PPD8_l|7?a~g$@-4G#DD1Lt3!V*rwP@G?Z)$s2(P<8NAh@;f1|{ z`H?Z^!d^!F4ueo)O63i$4i6dBwa*h!LKjN!aT#A*^DO7-Ot2Jwqn`v4DwzZEEPZ;J;z@sN)?Lq zw5H8}QvbSPcC-J_#)izAo8=xMFmqQZNSTo>+(=iQU0nYr zYg)VoO(#E?=II0CClkN-gGNvy9Jl#|kWeNps@je0iR|u}eg}YYWkn;(O0d;jko! zMds3s*CXB874Nz{r95DfeNrIXe|89dswK#`8N_xiLUI zSi|4BDo$`*shj_K<%v8nBOi|L+db$W!%^}c$gU`qCd#Z7;pC1nBm4moKMTIr;->sxb_%Z`UjJr3qz%HrOZF)W8CoB1M!m>+ZDd6dtuK z44apN58DsUEd83s#xQO3#>jK;7v?u`4}trm$mNu;*&J@( zVf0yZD?`>EaCYq3lj-asZ#&STC~->CPT*u=_~as#f`BXvC5HTXCJX9^Kbs^3uf4nM zh1uCuSNxvye(^ZnZ8ynpT262a*7m%+c-#hdwr#kLSstX7Yg5uM_t+t8MC>YjMUBu# zZxCG79X0N34QPF^_^Qu}zEpncK8f)VYXrEOnN4ICFdkY9{Ym!35^VTaM_=ke zXW@8=z|wcuYd2HHOvXR%DyqWQAn<7Nbb}J;)3zZwRyzu zCa@c@t9V!1pJo)c`?5g34TmiOfyy*g4^*;BO1w8c_GvyXxWtrm=8MNc_x)@iU5)Ch zp!_v&i`}3S{}N*_OzEae<-vHDQx70o{skv(+>(-|>6Y~FpZ$Z2&#s?cJe>$Dc|oB^wy|NYPSURIw2O6rwR%>?3KO@wTR zP>k2s9D@vm2JFXaAd|a}S~dHH{V;0%1X%m5RwoX2WNr9&vt>e8-o#O6!r_sJeq%I) zDOj3zO1ikG96W_XML;OW$0V*jJ+WIb>{``f*x5L1ncYTjq^PQ2UbG~H^{1!#iwMd~ z+!r2SkGbszJf}a@J+Sc%II`S0UoLg|k3&Y)GXCeNL~G4Z-Di>2koUHFC1^a`s$XE# zP|`DR24x+0dx`w@R(5!T&sd2wnxWA<5ptrRW)sZZKX3p3KlXmVEs?VmtyU%v(`vUh zB>YR{zBXl(K63@8U5#mhdM%)z0TWZOhvg34rm4#l**bmFy=X1q+Sl0|C${3c5|aA} zrB)^Vx!CZuXWfc4sdIbKOA~V6wT7D4cxagY0g+wIFtVZ)z4p2Vqq{FW;E?Mnu~Qv( zv+A{~{1W55GOH6!U5GZ)L39lo$u0C4xi^1Sd#J*_`!{XIY*$+qm2y$mv@e_zo;L6V ztMb5Z2*)y(GJ$Z}xx#%Zqk+b;B5{ua(;rkEb(V7|x5SPAOc+$Zb;;Qk#H~FJ(+)>K zc*Te;Yh-3m)X4Dh&LFS#r|libsF3F$ zhdku^X221{g2RR>;|4pAy=N5){h2m{;ig%2Iz0B7-|qA9wGX?sQhec!Er%v8;ikvw z^y7w^97xfP7S*uj)*ANK`AirPe!5KU8}&$vy>mWZ-In->{rwW!?Iq0gm{`iZ!d&Q{u);y*-I9F@3(wF zTsK7^S-1c|7ozQbZSR~5K zo~`(oIql=wg&Z5S%Xz2v5^n#Q;6V6+xMw4W$pHsho6fmH--D3!0yI181e)EImA|u1 zG8uHuU?xV|ktM}!Oh`U9QAD&De|S=p3Uj4fn)x7b#J3$lW`6n zUpo>e$`lq;M`mlck1j`4((`yEZI;Vm`?yfvWfTTPIY=)0F#HA=TLV{|y=gWXz_;_k zB!pz0BU@__z@!*C-Th;n4xptH?k)t8;=n1@>Lgp!ep5(6d_B_H#W;+EIuOLj?{ zxO!!PFI-vN!rCUgheqvbp^XBorR<#@Dq$i%Zt_ag)t@v`rrrBM{d6HU?w|mDotsdsv1i+k6 z#Nax9(+BWEgiYss1#9^KtCzQ=JABk2#Q61Eu?$+~U(kXUi5g4ME_n-J9%x$!LzqVA zb9@WO5grVV_KQ_f(93M-lc#*F_FgPuB~>e( z@h4OnWN$C&%TPu;Z9AuCnedW!FQM{GPNZQmEG2l=BD=?!OOf`;brm0_c9}+Umpehl zq~q1i5pM@>xSTGt?j!ZQ4EqsCKNuat>0Az&hbZrP;eYyU5L{=3v6&dr>1|SbkRJ zj_L2eJo9WRShGCh(^26mve@|P(m9m3ZejoA8Pwgy25Zr$IJt&cZ1Q*cxoIEpRASn) z!ZDt`>v9@*C(N>yMJ~2vcq~jK)_9#G5#{0g-mz4#o6(IV)7pnL}cT{(@|zF4j=UJrH<|X z2E(s&GeXUY>MfIoHz6i5iJ5Fj$$7U?|Gr&SMs26QP6HXCE7uz{^I0%%MAu;|Q&S0T zyIC#fVNT2AK0S+${XgLW-T5O%kk26QrclO2r>8TS137bheF;ip6 zSLKz~IW-N;P7A+F;i3d~*1G$E^@Eap*NoWV%>jd!Ls`uv+l>O)TM1%9xi? zgSF4XFCmY4nyb`4N9-GfU(R;vz+q&455Xh-j|@AV<6{Ah5Sy1EsKDlz^9EE^~KvE{=~0u1q0BBZ;`vot%gk{#r!&RC3uuK~&S_ z>FYC18C2my{*p2)o)t%$IUeVvJB4VhEvBm8t%tlI^C-m`$*HN`L?gCvfTzc```=cdXW+!D>c$3owdX4FS@ZE6ere0N zOJENS@U9mPb_Ow6_6kLcT1-!`ljRcXu0(+7^W?xuDrDAW0 zOB+E$99v3yVp^7W{b8O)6m0pfEc_)4xR2*%@bTM1+&$W0%7x(F^50$#cK-HC-qr$5 zLa(id746*<(*<>sEk07_<%?J}nlN__wvn-XX>}PG-qiojK|t-f);(}#M7LA2Z@Zud z$&vhNgwKr#uE51<7WJc{{_2E^$J^%gf5ne9#J%(IN99#dhhg>cFD?O{p1^kG!o+RC zAD_`t`^J#o6x#z)p~YN0w>uWaZd2Zi$O5w;i4WVv5e0r+7WMj$wsJ%cT_+E*FD>bd zWCTev!aZFf=o3mwB|`pOWX!VdqoAOIzU~Tc(MaOq;em=d#Lv<=uVwq^rNv;>%T)Lu z=&ixnVh~Dv0ryT1W&-5$7{TLaBY#E+8^{8$TdvA&M*^qfo>u);X;Sx0UO-VGGA2x6 zjgOmyjnEP_%w}jJnP$#eGkFw~b9MHjTlJ3~Vk_gZFOK)~=slUose2*buUyd%d3T?6 zCLthh`a1I%_JnJfRIKX}5HyfEE)Lp@=aol|t7pVJh)G?*F>q~SMQVx68o?BzH255{ z;Ij`)@>fl~17aGlr?5olYsXF9Gti0)?*5ROq|#INFw+0*bZ|#n|29v^vYag#CP_WZ zLD6zcUT|`XSB80WmDr!IHJ*}2U1YSg57mb1XM4yLQOqh>T=!0YM@yllexj!bSm^8!pGFEo=o;Xd%gd47 zv5 zK=bQMwR(uhXrz@yGHD{D=esqW949YC(as*f9o~0_6=^=`4MVR9aftFT#|H+T2?3~b z!M6TFdhcB1ka%tE*LCzk*MQ<-{ynX+VL@tD?}+U3vzp+$+Wa?@w0HYuOU~mwXbB@@ zFbG^-G_jhX_gBJh(i0n?Tu(~AQ*OJVOPk5^irF&bo6Pk0|7BMA8EddMV9)7`7Ll=( z_o)b@+GRyzLPu)_*Emeh?X>G!C&M}l$6eIX)qKJ|#8b;n>!M@E)ekHFh^DXC9tTSk z^moophqbbbXs(5Ec9Z>gC)c$!!*6&iFEdhTkK!#P5PU)z9S9Qa4IXCkoE*Qvl(^j% z;3z9ao5j%-W&JrIP1R=k<_nd-kwN*wxmj~EcF+>9XOce{?w+8in>PhYscDb>pGA@y z&5PwOOJ!!uv=SoSWkZl3SsWI%I0P7(Tg26Izhx{dew4)=lbM(R?|_&TT={ak9w znOaWD8DPes`9wwrD)-t)uk;<%>;WrKu<5pCes$A{;qXcHebZ^h=La%1~(T&Vik;ZjR6 z$y-+@k1eV1o$i)lroQqi*S-%q)mr7Mu~Rplg#YiOr)sp^l3*{p`=FQnm(0|@U9O{` z129pj@gmjxaS4r6u&bJc>#{g+10AFIzQJ@%hPQv5L-=OFgC}Qxh%d-|vigG(_b_4h zhkq1si<#g8$E3W!Q49)3l5~H#iu;z7^urI4xS$lTAAon7TF#y^#$FqAS!e%)a^gN? z%1A2N79YAGVh=T~M352<=Q&f0NRdGW3{ zR1~Ux0-a`0$Q=YKK7^|rNgW5A1E<50#NvrZY2UEx%1=y6wA`&$q^W$zxJIntZ0oPB z1OTz%ZHP@x-bvQo2Mu-}aRgDYMKtY~FFkv=Z(5xV82||~RW{4vZO=ciGiQ)fC)SU4u@Z z!8UevMHqJC%%+IHKZfL$VDc;3tz) zwzqon>>CCnTA}BgKbOS`8!Z0&TjzR%{TaCaV15gM6rX^;*J5*b9#%Yi4%jZ4yHk4J`;@Mw4|R)y`LV3Oxb7UYd?NRxR~RdcHe8Ol&b zcv9+adN?+4LU(IvGbX6v)Pi;&*woNwU#ofLfR)QBMDe)tXCNUpCFS(SiQZ~)|M(OA zj0-6Y^VFVn?rRzhDbaf$5=)0+w6Z!#$O$B@Vj&1Ae0oiAw_~v(5RToLZM?E-zX~jd z%;gE5?ImQM9XsFO{^$6w+?4vWP#4|H&tz+W(WoY$zkIUaZ2=6lpBnrnm1_H&uJ)~@ z-IS_5c)IEmfa+ec?hkK0JzSvtHL0BXB#>!7@CH+8Cbmr!U8VVqE~Ff+F=wt05_4lP zhrnK$u>NoWA>T{%<2t)NfbAJXOCd0UDpmbBR!W)3G}jLIGpgNkZtNuHsaskW+KfAn zX{adk4dA#M#T}UfO*0i&AsitMI{0C}2-tE2fZqm-J^cq&?OE5q?hGO5TzUDxyF`OV zPfE>344UGRd8K+KJJkOY>qPp&Wy7$AC5ELNv|+So$JGHT!X^;8eh#V`61Rtudz#&# z=WdS-3v7j0Ed1>G3K?zrDs#o$hY;?tx-(a-V~raLjinZL3lB@L=k$>CC#v^MY5?%H zSnhOnon6OwHx3J9)Sn?4l55zolkZ!xm{N2-Sh1X<525Fe!09P1^ziSfPBL@}=cuC4 zk!)4X4!yp?a-(A=pR{TvPG7{_43+6mK@#Zw$_sUBe7gB)AG3%7g<}FK^vkIKT$IZ% z;{)z0%V#~Tk&0u3|HX8n0sEHRJDIY`es}}ImT+FBV2!J@&D14h6I*JPuf7;62I33p zH3=Yi1$k;NjAj;K2BLRnmZ;WJb${m%+5U4)zS?g2ZS}5?;*0cTU(rBVgMc^<)jg)F zl*4h>RqgB+lI6zftuIY_Fem6>aD(VhhwaAcYJn|^P)|G|2h?msb_<}PUZute^ z&6f-Y=XzHAXRX87E&jLJEl5RM!N3BQWH1mc*JQU)i6tgN&*;H_Bi%wiY!JF8lAImV zDGPlkqy6MsJ95qqGpWCu$~2dE>(P_LBOWB7M+V^5 zF7pPRDM3&qYGJ|v3xm>!zQ>MZ6AYT@@VHyxjIP@mw*&C}D)Gr19n%_RPE84yd)nTO z@+;^`rj1barWc;s&Dxk(AD0?-Mmof%#sHRihf?A4U$^ZEmn8nfX&Xc1wN_0t zevN~4`;Rx1{yjrg{!)^lj2<;)Fmr>vW;hp~TI7iiQHhJe-RK2yYfIOHD)aKOmq2a$ zW1aYT{h-AGM`~vtJt;9{@x7Hw(1TnIiKoL_$D9tvZOn0>$5B>S=)XQjYezYK87kS9 z2BkVR*#!mNjTa1d!f9Tpo#}uDkX0O=mjD6Bs;i#~VS??>Ca?5*0jxNk`V{9eP>N_jtp?c+-pUxaVk*UkIqY z&wu?2*0PC{J0boRI!cqmJOg%F9f_b>(8s5<`yx`U7fU@ggix?Q%zQjYj^jPq6zx84 zunlIY;P5WGPFxZ1&I-G6y^2|a9?#h){Z1m{c{>q%(rvOrncQ4frQ0&$h0=gdgl~aziR3YYw26f`nL9`d>PNaxS{Z4Q47Ct zrHLxj@Tp+CPaGfVD|4>K>h{e)Ghb9wqu}yfI({&dgJ+LigTrvJqZ?MIHxLd=TQVvV zqkE04s z&Pr61!Bqnti*%7?Kp>oXng%?ZKRB|W;;!9Z@rZ&28A zJU;TfM_6%1XU7LD^n$=3C{PhnAE|hfn4FO1I7ftQv)EZO)py2Uifa}Wc{u)(UMz7U znL%=s1COgQPBjWNgjuCd$`H#3v4DJV7*GlnTO$G*u*y2QDHjlM6lIuzqLK*uKolmB zA;kmRQ!ENe4kT8L|6AC9Y_Yn94ENxErR{~ddI0pNyed_x2G?x?yNYz0ayW=JB39KRaY zHe{w4b{cTe&2`ji11}HA?S^_9SMYJIpGu`8Y05<68*q(=9W*0()41Bp$;)t3%E z(FH#8^UO+ySduKbywWR(U-E=>7zX>x#EHE38?R0MHC>|Zy3AinlKD#o)DCv<2O!x+ zGAx^1OEB_vdFcRGIjwAIp1sme`*N%m=9W`Is0`jk*dS{& z|J9Rv5*th+XV~ly4ari89;fK`>L$*AUX|jOA{-~PN`-1-M+AnD-GGiuMKc6M%44vA zDfB|BzZcZ3MB5NnI)$?L1W+qMj!IIcdWk2jHShu^8ro4PO&bbY7n%cWlPh;06JW;^ z*F;Y!4m<^q$EMGBJEKHd*NFToBID<}ti27RY6QYjSDMcf9#_K8?LmpE^$)K?1J`Pf?5IBt+krPSu$sZ2c9PK-iwa3Rwpc z`v;e5+eKk;tGH_3MhZnPNjsu;o4hSG(%e^a*7KQ9&sVkTQ({&|yUy)J!7>zay60kw zZ~l!PAo%g)BKf{-q{rhx>y+IjLM0^cC$`5|qMXzIVdUMs=9S*x79n@`&az5y_X&e{ z87NlnTR9*1Teg?a3ulT+Uly0T#1Oy6L4re|mBnXW$x&qT{<-w%A^qu;V}WhhMi|Y| zU?N1Jb!TLyVKAU0e1S+_FPYK#zV%f&bhlW;4c+%6c8DO`81qXzkCL}p4U7!P58OFb;LfG z7ae0LaHe=Iy{NS?zMQb~M@y;?IMi_aw}r`i_yhMMFV|rQ?eeN2L_uagpL)M*gP8AN z$6ssIJAxvtjI?JbTP#fGoJXIz4-Cx6OBid^U7_5NmE{{Nd==4$ttojM`CFrHd3#01 zZ#ATu2@+;$ui}zKS3GfpVe&4mJOF>}TFVCVx~;#!$d^J$EzB&0Md|k3I)B-pcj4vC z@<>xG`{R%rTCH#pruKN^w%l-fV8=MLxio~mHelwNqd&JXDCl%AXNCjaaLTootBd59 z37?W(Hm*+myMFT1$aKtzskuAlLCQS2HWa@_STOCWO6YF*^aeKXAG~MaTQI}(wdwJ( zqP{ywVum_%-hHi2D`6a%#!SDQAkDCDxW&*w3mk(v(RLmkGABx&Nu9n}{SO`1M2Zz5 z{WOr!8DTnp#wsEAf9(K&$Xd`yOFnS-+s{Id6pjYdUOH_V2zEf9T&tbrAN9oiKlD6K zgY?~Q(N-r%;L+3PntcUB|B4IZw9izHUIQC8YSG8Wm_GVmvW%`S$o1$(DX)Fdq&W?&5UHo7KhsISw7yV=Msm!^k>8mZok74m?)8kq*$g++>)0lX1Y=D0Hs{{+7 z-{;kkgl~WczP5)EUyydfe>W(Ob1d&A%k_8IPlD)0X;kNisPRA3Bl>;`7v8@zJhlpJ zL%UB?qy8?Bn{bvCM@F^VsV?VaT}A9RA4ti}aFqw(6Jsp)uBB}U+DoYMZyxVSYky5@ z?=HY19mF=NN!3pGPj^EXPANQ$*wn`Cb5+Uuuq(hTWJg!84Y4GYdA2Zu2k5L8to8WD zrQjQ{jGrFd-ZW`Bd{P^I1DDdDkba-x<{U!0j7urCy z$O6dI8kk8h*bXvgp*puN_jxsr$Qd8v2-hE3#(;f!!=Ike@Z!{tO{wL7@72jejwAK{ zewz?|LvX8v_zphHsc55@y^g#!wZ>w!zja(G3suDFTEtB&=lPcQDHa5yo@4Rm5vUu# z`+U!Wf3!NasrQ6qGZmITb~-D;2X31VmjJ5Zx;LY^K|?_@0mD|Q6EaN{xpey(bsYUn zQCTqkFN7g5yYY=UDk^jTYibzj7>!O1;xNAQSlES0?1%|Krfx_G>)Z)evSH+V+glV~ z=ysq9#=PfP>(sh;P>rbG!6VGDN_CQ*@LEm0xw8_kBs=}*O1^>{P<%~Y#srk~jXnCt zy^h34p1xqnSI(PQ&x$8e`+2eP1ZuE6edg9zs*P+Ve^b>LDf`zSQdwckJwV!4;I;f* znpr0P{UBc`FlHGFHhP1q8#<&*3$mth!{?T=zh{GkNz|2NGl358iRPKLRpRLO5;0?@ zsps-Ch5pGaa)zH5AmRXtlKQB~&1J3~N-6o^{7b?S>hi-2gr>-p3~)d5!(}N&cZ>?# z%j~b%yo_7R5XSJ^cYw-(lX(>d*Hk$*$@YO|uvg?!uMz)yb^|#5b>hS_vFhTGVH-z! z>U2LLz3#4VUY2&`%^Pfm#<)wS{L-vByECs~jSD@ls`0cm!}<_J^&f@ws%+89$K<1d zE!z8dg-wMyeMtiWGxeU_?y=WX|15&3R?>j$6BOm|4OYG4!hWgb?}Sq4c-K8*6PBDcxvTnO1s#Xo!P>@HP=22r z@&!_Uk)N6-NEP9CE~pwI{j9&7v9CRFzPCj-ua;tA3#+j5VU+b9TwC5mx;FY_RUtF5 zWWrT#*0LD~Bd-?%tVJIO_U|nRl_@ zygD({#KdHoJd#IArE|#BaiyWs1giNz3yt03Jg+7sF73} z=OHaq+_h}jAK=J#}u>yatUls8jT z%a2;-E|K)ceAMKMv!-HOm&u)q9;%JFrTW0im(_Sa zNi2%o_5m&_v!<)p+o*!$qqsukUFWThPq2~ee0e%7AgP^L9{PQ{sv%&t{j|}0j-(K8 z4M(N+#$WMMO{^MkYhQ*eZU7=+zKHvCP1Rt!O4~Z{5*zMpVN~_H#1F?&O&GK^%?+hTD7u5^ zljzNJX8L}y6WUn~?GFh<99Q#=YkBI8R4QKirvJdR9*3+?0v*nYpZYrC6AsC%V~>TQ z(vh`TiiyDj*G*^EU}Siv?(USAyk9e%qp((|77vDVwv3~I63$jrQqUWsk*QeYt-E3C zm5o-M?XxRbtypE?l6_H;ps|p?t<#`iRFvXh>||!D-SDixtqpO{ z&-!*ar_ZVL`}~!%*Whwd5eS94=U2b`*dLc47BJteH5#?#9WYPyGOc+aPngqgxNapF z{{*%Am2g+jbi27Ebt82t4%~M7J+ZLBXw$^z4!{1h1{+iYbk40ICp6&kk>|L3#A@J=BkG~Z>YIDF38uFJUndQ#$YYQX+7TI^ozN( znOSLJ&m$EyOP=xV|BdVV0JFY#a>bWOKbyrZDKrp1c#rcL?MDbzO5N3nfqzz=0H$PD9rrILcyF9UhGDO^Q|MHJvS)t0WDZnTFQ5#oDa7Lh6 zDC?|^=aa94x#YK>2lTakQ*OmKpPj_%W&aC&%=&SU*QW#jJ0kTB9iy7wxz8#OOKdD_ zHl5G=_{~*A?i}hd3saqlGG7kUe3dt_E5jFrTC#r=$q8ci&^IbmSz6PWu66 z0!OxGdW)H`(%m++8DRuJBens0b%wX5HYhYw2cyXGHfe1*0e?+Pi|NhTc7HB*JQ5?) z3i4?+n$FX|8h_*Gs3}BlUG!r~aJiJ^>7&KCMLnvnxiW zadvpRBvEY`k*0fZib#`=MWp?SF=>$%Vi1`fM_FRo*(|f>ft!N-Brek0$^X8UVP>>n4>HOhMt{lK=&-5y3sY z$cj>zMd1YrYb(e}s2lld-t|te?|v)fqQ~2V;pTeXZ8^50@``*3?bbB+uX%9wP zcii~u)#D4!o(HYg*?(l<>eGhaum&&VA|e{XMu)vM?&kSLeNY#!wu5-mZLE9##mF42?=}m0?9TI^mPbbKi&WIbvqmeI z@55W^Zrcaph3k6@>}C(It$$g|10XzDi;z{eLHe|D*!H0Q#WvZsf)+pi*4z?bUBfeIK55qyrG_YIhU^eEb|*%lPNE zl@W*-5J?Pzp&5l({X0^QhQ|z6wr^~1#^{QCiYl|vi&(d{#@_Ha%B zCC(Gug?u3t^z6k84)QY}%gtt`%D)4)_A1|dPONY?-)6ATfk#y2R4?@QrO4%VB=;T2BdjyL9k61i$YdxdbvRXv`uq8n66I&L68QMI~!#OU7C zNCNzb)v`oI`i`Cl1c`ioPiaW-MDK>o@zT32oqvrYX$Hgj3kGkk-2}S=-A6fsfmGF4 z6%<7g#r@i=#76f89>HQypi&prx}pkuDDZXR0mtbGa(GqUxW~7Z0^n^HXI3z)4j;Rh z)iGrEvNDjM3HeEuv-|`$?00p(A;^#4p&0g^z;QCi{YaxgUBCL?3$3y%Wd;*Ch|b(3 zC4Y0Mm3S^KngfMHYr{ShL=qo+7#JD-Y8K`}=C1-DEuAN6b{k&_SOW@-0BZP*bEB$$ zsyS{HgGWhU@Wcq+&B}_LmoiY1ds$Rf1-GNN1Mq Date: Wed, 17 Jul 2019 15:06:10 -0400 Subject: [PATCH 29/36] add protocol feature tests for wtmsig that ensure block validation rules and intrinsic visibility change at expected blocks and in-flight schedule updates are handled appropriately --- unittests/protocol_feature_tests.cpp | 235 +++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp index d97534ea5a0..4a6d9304227 100644 --- a/unittests/protocol_feature_tests.cpp +++ b/unittests/protocol_feature_tests.cpp @@ -1507,4 +1507,239 @@ BOOST_AUTO_TEST_CASE( webauthn_assert_recover_key ) { try { } FC_LOG_AND_RETHROW() } +static const char import_set_proposed_producer_ex_wast[] = R"=====( +(module + (import "env" "set_proposed_producers_ex" (func $set_proposed_producers_ex (param i64 i32 i32) (result i64))) + (memory $0 1) + (export "apply" (func $apply)) + (func $apply (param $0 i64) (param $1 i64) (param $2 i64) + (drop + (call $set_proposed_producers_ex + (i64.const 0) + (i32.const 0) + (i32.const 43) + ) + ) + ) + (data (i32.const 8) "\01\00\00\00\00\00\85\5C\34\00\03\EB\CF\44\B4\5A\71\D4\F2\25\76\8F\60\2D\1E\2E\2B\25\EF\77\9E\E9\89\7F\E7\44\BF\1A\16\E8\54\23\D5") +) +)====="; + +BOOST_AUTO_TEST_CASE( set_proposed_producers_ex_test ) { try { + 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::wtmsig_block_signatures); + BOOST_REQUIRE(d); + + const auto& alice_account = account_name("alice"); + c.create_accounts( {alice_account} ); + c.produce_block(); + + BOOST_CHECK_EXCEPTION( c.set_code( alice_account, import_set_proposed_producer_ex_wast ), + wasm_exception, + fc_exception_message_is( "env.set_proposed_producers_ex unresolveable" ) ); + + c.preactivate_protocol_features( {*d} ); + c.produce_block(); + + // ensure it now resolves + c.set_code( alice_account, import_set_proposed_producer_ex_wast ); + + // ensure it requires privilege + BOOST_REQUIRE_EQUAL( + c.push_action(action({{ alice_account, permission_name("active") }}, alice_account, action_name(), {} ), alice_account.to_uint64_t()), + "alice does not have permission to call this API" + ); + + c.push_action(config::system_account_name, N(setpriv), config::system_account_name, fc::mutable_variant_object()("account", alice_account)("is_priv", 1)); + + //ensure it can be called w/ privilege + BOOST_REQUIRE_EQUAL(c.push_action(action({{ alice_account, permission_name("active") }}, alice_account, action_name(), {} ), alice_account.to_uint64_t()), c.success()); + + c.produce_block(); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( producer_schedule_change_extension_test ) { try { + 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::wtmsig_block_signatures); + BOOST_REQUIRE(d); + + c.produce_blocks(2); + + // sync a remote node into this chain + tester remote( setup_policy::none ); + push_blocks(c, remote); + + // activate the feature + // there is a 1 block delay before header-only validation (which is responsible for extension validation) can be + // aware of the activation. So the expectation is that if it is activated in the _state_ at block N, block N + 1 + // will bear an extension making header-only validators aware of it, and therefore block N + 2 is the first block + // where a block may bear a downstream extension. + c.preactivate_protocol_features( {*d} ); + remote.push_block(c.produce_block()); + + auto last_legacy_block = c.produce_block(); + + + { // ensure producer_schedule_change_extension is rejected + const auto& hbs = remote.control->head_block_state(); + + // create a bad block that has the producer schedule change extension before the feature upgrade + auto bad_block = std::make_shared(last_legacy_block->clone()); + bad_block->header_extensions.emplace_back( + producer_schedule_change_extension::extension_id(), + fc::raw::pack(std::make_pair(hbs->active_schedule.version + 1, std::vector{})) + ); + + // re-sign the bad block + auto header_bmroot = digest_type::hash( std::make_pair( bad_block->digest(), remote.control->head_block_state()->blockroot_merkle ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state()->pending_schedule.schedule_hash) ); + bad_block->producer_signature = remote.get_private_key(N(eosio), "active").sign(sig_digest); + + // ensure it is rejected as an unknown extension + BOOST_REQUIRE_EXCEPTION( + remote.push_block(bad_block), producer_schedule_exception, + fc_exception_message_is( "Block header producer_schedule_change_extension before activation of WTMsig Block Signatures" ) + ); + } + + { // ensure that non-null new_producers is accepted (and fails later in validation) + const auto& hbs = remote.control->head_block_state(); + + // create a bad block that has the producer schedule change extension before the feature upgrade + auto bad_block = std::make_shared(last_legacy_block->clone()); + bad_block->new_producers = legacy::producer_schedule_type{hbs->active_schedule.version + 1, {}}; + + // re-sign the bad block + auto header_bmroot = digest_type::hash( std::make_pair( bad_block->digest(), remote.control->head_block_state()->blockroot_merkle ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state()->pending_schedule.schedule_hash) ); + bad_block->producer_signature = remote.get_private_key(N(eosio), "active").sign(sig_digest); + + // ensure it is accepted (but rejected because it doesn't match expected state) + BOOST_REQUIRE_EXCEPTION( + remote.push_block(bad_block), wrong_signing_key, + fc_exception_message_is( "block signed by unexpected key" ) + ); + } + + remote.push_block(last_legacy_block); + + // propagate header awareness of the activation. + auto first_new_block = c.produce_block(); + + { + const auto& hbs = remote.control->head_block_state(); + + // create a bad block that has the producer schedule change extension that is valid but not warranted by actions in the block + auto bad_block = std::make_shared(first_new_block->clone()); + bad_block->header_extensions.emplace_back( + producer_schedule_change_extension::extension_id(), + fc::raw::pack(std::make_pair(hbs->active_schedule.version + 1, std::vector{})) + ); + + // re-sign the bad block + auto header_bmroot = digest_type::hash( std::make_pair( bad_block->digest(), remote.control->head_block_state()->blockroot_merkle ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state()->pending_schedule.schedule_hash) ); + bad_block->producer_signature = remote.get_private_key(N(eosio), "active").sign(sig_digest); + + // ensure it is rejected because it doesn't match expected state (but the extention was accepted) + BOOST_REQUIRE_EXCEPTION( + remote.push_block(bad_block), wrong_signing_key, + fc_exception_message_is( "block signed by unexpected key" ) + ); + } + + { // ensure that non-null new_producers is rejected + const auto& hbs = remote.control->head_block_state(); + + // create a bad block that has the producer schedule change extension before the feature upgrade + auto bad_block = std::make_shared(first_new_block->clone()); + bad_block->new_producers = legacy::producer_schedule_type{hbs->active_schedule.version + 1, {}}; + + // re-sign the bad block + auto header_bmroot = digest_type::hash( std::make_pair( bad_block->digest(), remote.control->head_block_state()->blockroot_merkle ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state()->pending_schedule.schedule_hash) ); + bad_block->producer_signature = remote.get_private_key(N(eosio), "active").sign(sig_digest); + + // ensure it is accepted (but rejected because it doesn't match expected state) + BOOST_REQUIRE_EXCEPTION( + remote.push_block(bad_block), producer_schedule_exception, + fc_exception_message_is( "Block header contains legacy producer schedule outdated by activation of WTMsig Block Signatures" ) + ); + } + + remote.push_block(first_new_block); + remote.push_block(c.produce_block()); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( wtmsig_block_signing_inflight_legacy_test ) { try { + 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::wtmsig_block_signatures); + BOOST_REQUIRE(d); + + c.produce_blocks(2); + + // activate the feature, and start an in-flight producer schedule change with the legacy format + c.preactivate_protocol_features( {*d} ); + vector sched = {{N(eosio), c.get_public_key(N(eosio), "bsk")}}; + c.push_action(config::system_account_name, N(setprods), config::system_account_name, fc::mutable_variant_object()("schedule", sched)); + c.produce_block(); + + // ensure the last legacy block contains a new_producers + auto last_legacy_block = c.produce_block(); + BOOST_REQUIRE_EQUAL(last_legacy_block->new_producers.valid(), true); + + // promote to active schedule + c.produce_block(); + + // ensure that the next block is updated to the new schedule + BOOST_REQUIRE_EXCEPTION( c.produce_block(), wrong_signing_key, fc_exception_message_is( "block signed by unexpected key" )); + c.control->abort_block(); + + c.block_signing_private_keys.emplace(get_public_key(N(eosio), "bsk"), get_private_key(N(eosio), "bsk")); + c.produce_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( wtmsig_block_signing_inflight_extension_test ) { try { + 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::wtmsig_block_signatures); + BOOST_REQUIRE(d); + + c.produce_blocks(2); + + // activate the feature + c.preactivate_protocol_features( {*d} ); + c.produce_block(); + + // start an in-flight producer schedule change before the activation is availble to header only validators + vector sched = {{N(eosio), c.get_public_key(N(eosio), "bsk")}}; + c.push_action(config::system_account_name, N(setprods), config::system_account_name, fc::mutable_variant_object()("schedule", sched)); + c.produce_block(); + + // ensure the first possible new block contains a producer_schedule_change_extension + auto first_new_block = c.produce_block(); + BOOST_REQUIRE_EQUAL(first_new_block->new_producers.valid(), false); + BOOST_REQUIRE_EQUAL(first_new_block->header_extensions.size(), 1); + BOOST_REQUIRE_EQUAL(first_new_block->header_extensions.at(0).first, producer_schedule_change_extension::extension_id()); + + // promote to active schedule + c.produce_block(); + + // ensure that the next block is updated to the new schedule + BOOST_REQUIRE_EXCEPTION( c.produce_block(), wrong_signing_key, fc_exception_message_is( "block signed by unexpected key" )); + c.control->abort_block(); + + c.block_signing_private_keys.emplace(get_public_key(N(eosio), "bsk"), get_private_key(N(eosio), "bsk")); + c.produce_block(); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 487387dacfda9dc36b0957d262c233e42bd30288 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Mon, 22 Jul 2019 11:34:32 -0400 Subject: [PATCH 30/36] Add tests for producer schedule authority satisfiability, duplicated accounts, duplicated keys on a wtmsig authority. Add enforcement of unique keys in a wtmsig authority --- libraries/chain/wasm_interface.cpp | 8 +++- unittests/producer_schedule_tests.cpp | 69 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 8751a7568e8..2a9a994705c 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -188,8 +188,9 @@ class privileged_api : public context_aware_api { for (const auto& p: producers) { EOS_ASSERT( context.is_account(p.producer_name), wasm_execution_error, "producer schedule includes a nonexisting account" ); - p.authority.visit([this](const auto& a) { + p.authority.visit([this, &p](const auto& a) { uint32_t sum_weights = 0; + std::set unique_keys; for (const auto& kw: a.keys ) { EOS_ASSERT( kw.key.which() < context.db.get().num_supported_key_types, unactivated_key_type, "Unactivated key type used in proposed producer schedule"); @@ -201,9 +202,12 @@ class privileged_api : public context_aware_api { } else { sum_weights += kw.weight; } + + unique_keys.insert(kw.key); } - EOS_ASSERT( sum_weights >= a.threshold, wasm_execution_error, "producer schedule includes an unsatisfiable authority" ); + EOS_ASSERT( a.keys.size() == unique_keys.size(), wasm_execution_error, "producer schedule includes a duplicated key for ${account}", ("account", p.producer_name)); + EOS_ASSERT( sum_weights >= a.threshold, wasm_execution_error, "producer schedule includes an unsatisfiable authority for ${account}", ("account", p.producer_name)); }); diff --git a/unittests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp index 8be96cbdba6..f1d61a7ba78 100644 --- a/unittests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -612,4 +612,73 @@ BOOST_FIXTURE_TEST_CASE( producer_m_of_n_test, TESTER ) try { BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( satisfiable_msig_test, TESTER ) try { + create_accounts( {N(alice),N(bob)} ); + while (control->head_block_num() < 3) { + produce_block(); + } + + vector sch1 = { + producer_authority{N(alice), block_signing_authority_v0{2, {{get_public_key(N(alice), "bs1"), 1}}}} + }; + + // ensure that the entries in a wtmsig schedule are rejected if not satisfiable + BOOST_REQUIRE_EXCEPTION( + set_producer_schedule( sch1 ), wasm_execution_error, + fc_exception_message_is( "producer schedule includes an unsatisfiable authority for alice" ) + ); + + BOOST_REQUIRE_EQUAL( false, control->proposed_producers().valid() ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( duplicate_producers_test, TESTER ) try { + create_accounts( {N(alice),N(bob)} ); + while (control->head_block_num() < 3) { + produce_block(); + } + + vector sch1 = { + producer_authority{N(alice), block_signing_authority_v0{1, {{get_public_key(N(alice), "bs1"), 1}}}}, + producer_authority{N(alice), block_signing_authority_v0{1, {{get_public_key(N(alice), "bs2"), 1}}}} + }; + + // ensure that the schedule is rejected if it has duplicate producers in it + BOOST_REQUIRE_EXCEPTION( + set_producer_schedule( sch1 ), wasm_execution_error, + fc_exception_message_is( "duplicate producer name in producer schedule" ) + ); + + BOOST_REQUIRE_EQUAL( false, control->proposed_producers().valid() ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( duplicate_keys_test, TESTER ) try { + create_accounts( {N(alice),N(bob)} ); + while (control->head_block_num() < 3) { + produce_block(); + } + + vector sch1 = { + producer_authority{N(alice), block_signing_authority_v0{2, {{get_public_key(N(alice), "bs1"), 1}, {get_public_key(N(alice), "bs1"), 1}}}} + }; + + // ensure that the schedule is rejected if it has duplicate keys for a single producer in it + BOOST_REQUIRE_EXCEPTION( + set_producer_schedule( sch1 ), wasm_execution_error, + fc_exception_message_is( "producer schedule includes a duplicated key for alice" ) + ); + + BOOST_REQUIRE_EQUAL( false, control->proposed_producers().valid() ); + + // ensure that multiple producers are allowed to share keys + vector sch2 = { + producer_authority{N(alice), block_signing_authority_v0{1, {{get_public_key(N(alice), "bs1"), 1}}}}, + producer_authority{N(bob), block_signing_authority_v0{1, {{get_public_key(N(alice), "bs1"), 1}}}} + }; + + set_producer_schedule( sch2 ); + BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() ); +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() From e80e4093a93950dc2d6eff734839f3b289eebf88 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Tue, 23 Jul 2019 10:36:28 -0400 Subject: [PATCH 31/36] Forked tests had been instantiating peers with the expectation that they would sync into a blockchain, however they had actually been deterministically re-creating the preamble of those chains. Recent changes to the set-up policy violated the implicit expectation by leaving unapplied_transactions around after syncing. These transactions were expired and throwing asserts --- unittests/forked_tests.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index d0e5efb2eac..902d09a62e8 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { BOOST_REQUIRE( produce_empty_blocks_until( bios, N(e), N(a) ) ); // sync remote node - tester remote; + tester remote(setup_policy::none); push_blocks(bios, remote); // produce 6 blocks on bios @@ -170,7 +170,7 @@ BOOST_AUTO_TEST_CASE( forking ) try { ); - tester c2; + tester c2(setup_policy::none); wlog( "push c1 blocks to c2" ); push_blocks(c, c2); wlog( "end push c1 blocks to c2" ); @@ -293,7 +293,7 @@ BOOST_AUTO_TEST_CASE( prune_remove_branch ) try { wlog("set producer schedule to [dan,sam,pam,scott]"); c.produce_blocks(50); - tester c2; + tester c2(setup_policy::none); wlog( "push c1 blocks to c2" ); push_blocks(c, c2); @@ -346,9 +346,9 @@ BOOST_AUTO_TEST_CASE( prune_remove_branch ) try { */ BOOST_AUTO_TEST_CASE( validator_accepts_valid_blocks ) try { - tester n1; - tester n2; - tester n3; + tester n1(setup_policy::none); + tester n2(setup_policy::none); + tester n3(setup_policy::none); n1.produce_block(); @@ -389,7 +389,7 @@ BOOST_AUTO_TEST_CASE( read_modes ) try { auto head_block_num = c.control->head_block_num(); auto last_irreversible_block_num = c.control->last_irreversible_block_num(); - tester head(setup_policy::old_bios_only, db_read_mode::HEAD); + tester head(setup_policy::none, db_read_mode::HEAD); push_blocks(c, head); BOOST_CHECK_EQUAL(head_block_num, head.control->fork_db_head_block_num()); BOOST_CHECK_EQUAL(head_block_num, head.control->head_block_num()); @@ -399,7 +399,7 @@ BOOST_AUTO_TEST_CASE( read_modes ) try { BOOST_CHECK_EQUAL(head_block_num, read_only.control->fork_db_head_block_num()); BOOST_CHECK_EQUAL(head_block_num, read_only.control->head_block_num()); - tester irreversible(setup_policy::old_bios_only, db_read_mode::IRREVERSIBLE); + tester irreversible(setup_policy::none, db_read_mode::IRREVERSIBLE); push_blocks(c, irreversible); BOOST_CHECK_EQUAL(head_block_num, irreversible.control->fork_db_pending_head_block_num()); BOOST_CHECK_EQUAL(last_irreversible_block_num, irreversible.control->fork_db_head_block_num()); @@ -434,7 +434,7 @@ BOOST_AUTO_TEST_CASE( irreversible_mode ) try { BOOST_REQUIRE( lib2 < hbn1 ); - tester other; + tester other(setup_policy::none); push_blocks( main, other ); BOOST_CHECK_EQUAL( other.control->head_block_num(), hbn2 ); @@ -533,7 +533,7 @@ BOOST_AUTO_TEST_CASE( reopen_forkdb ) try { c1.produce_block(); produce_empty_blocks_until( c1, N(carol), N(alice) ); - tester c2; + tester c2(setup_policy::none); push_blocks( c1, c2 ); @@ -582,7 +582,7 @@ BOOST_AUTO_TEST_CASE( push_block_returns_forked_transactions ) try { wlog("set producer schedule to [dan,sam,pam]"); c.produce_blocks(40); - tester c2; + tester c2(setup_policy::none); wlog( "push c1 blocks to c2" ); push_blocks(c, c2); From 3a47de6422c47ec342b18c6f25d325ce10bda0d5 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Tue, 23 Jul 2019 10:48:08 -0400 Subject: [PATCH 32/36] remove dangerous default constructor (no allocator), fix bad invocations of `std::forward` that should be `std::move` --- libraries/chain/include/eosio/chain/producer_schedule.hpp | 4 ++-- libraries/chain/wasm_interface.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index 501342ebb41..c1ad09fab03 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -153,7 +153,7 @@ namespace eosio { namespace chain { using shared_block_signing_authority = static_variant; struct shared_producer_authority { - shared_producer_authority() = default; + shared_producer_authority() = delete; shared_producer_authority( const shared_producer_authority& ) = default; shared_producer_authority( shared_producer_authority&& ) = default; shared_producer_authority& operator= ( shared_producer_authority && ) = default; @@ -161,7 +161,7 @@ namespace eosio { namespace chain { shared_producer_authority( const name& producer_name, shared_block_signing_authority&& authority ) :producer_name(producer_name) - ,authority(std::forward(authority)) + ,authority(std::move(authority)) {} name producer_name; diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 2a9a994705c..f3afa9d44f4 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -215,7 +215,7 @@ class privileged_api : public context_aware_api { } EOS_ASSERT( producers.size() == unique_producers.size(), wasm_execution_error, "duplicate producer name in producer schedule" ); - return context.control.set_proposed_producers( std::forward>(producers) ); + return context.control.set_proposed_producers( std::move(producers) ); } int64_t set_proposed_producers( array_ptr packed_producer_schedule, size_t datalen) { From 510118ac3bb48c8eecffb44370a9c7fafc0b32b5 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Tue, 23 Jul 2019 18:26:06 -0400 Subject: [PATCH 33/36] addressing PR feedback made all conversions to and from shared data structures explicit and reduced shared structures to just basically POD types --- libraries/chain/block.cpp | 4 + libraries/chain/block_header_state.cpp | 30 +- libraries/chain/block_state.cpp | 12 +- libraries/chain/controller.cpp | 13 +- .../chain/include/eosio/chain/authority.hpp | 3 - .../eosio/chain/block_header_state.hpp | 10 +- .../chain/include/eosio/chain/exceptions.hpp | 2 +- .../eosio/chain/global_property_object.hpp | 4 +- .../include/eosio/chain/producer_schedule.hpp | 274 ++++++++---------- libraries/chain/include/eosio/chain/types.hpp | 3 + libraries/chain/producer_schedule.cpp | 4 + libraries/chain/wasm_interface.cpp | 1 - 12 files changed, 159 insertions(+), 201 deletions(-) diff --git a/libraries/chain/block.cpp b/libraries/chain/block.cpp index 91c93f6c359..0f43874f77d 100644 --- a/libraries/chain/block.cpp +++ b/libraries/chain/block.cpp @@ -1,3 +1,7 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ #include namespace eosio { namespace chain { diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 73980eb7421..842053efde8 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -4,6 +4,16 @@ namespace eosio { namespace chain { + namespace detail { + bool is_builtin_activated( const protocol_feature_activation_set_ptr& pfa, + const protocol_feature_set& pfs, + builtin_protocol_feature_t feature_codename ) + { + auto digest = pfs.get_builtin_digest(feature_codename); + const auto& protocol_features = pfa->protocol_features; + return digest && protocol_features.find(*digest) != protocol_features.end(); + } + } bool block_header_state::is_active_producer( account_name n )const { return producer_to_last_produced.find(n) != producer_to_last_produced.end(); @@ -190,11 +200,7 @@ namespace eosio { namespace chain { } if (new_producers) { - auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); - const auto& protocol_features = prev_activated_protocol_features->protocol_features; - bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); - - if ( wtmsig_enabled ) { + if ( detail::is_builtin_activated(prev_activated_protocol_features, pfs, builtin_protocol_feature_t::wtmsig_block_signatures) ) { // add the header extension to update the block schedule h.header_extensions.emplace_back( producer_schedule_change_extension::extension_id(), @@ -209,7 +215,7 @@ namespace eosio { namespace chain { downgraded_producers.producers.emplace_back(legacy::producer_key{p.producer_name, auth.keys.front().key}); }); } - h.new_producers = downgraded_producers; + h.new_producers = std::move(downgraded_producers); } } @@ -238,9 +244,7 @@ namespace eosio { namespace chain { bool wtmsig_enabled = false; if (h.new_producers || exts.count(producer_schedule_change_extension::extension_id()) > 0 ) { - auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); - const auto& protocol_features = prev_activated_protocol_features->protocol_features; - wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + wtmsig_enabled = detail::is_builtin_activated(prev_activated_protocol_features, pfs, builtin_protocol_feature_t::wtmsig_block_signatures); } if( h.new_producers ) { @@ -325,9 +329,7 @@ namespace eosio { namespace chain { )&& { if( !additional_signatures.empty() ) { - auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); - const auto& protocol_features = prev_activated_protocol_features->protocol_features; - bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + bool wtmsig_enabled = detail::is_builtin_activated(prev_activated_protocol_features, pfs, builtin_protocol_feature_t::wtmsig_block_signatures); EOS_ASSERT(wtmsig_enabled, producer_schedule_exception, "Block contains multiple signatures before WTMsig block signatures are enabled" ); } @@ -355,9 +357,7 @@ namespace eosio { namespace chain { const signer_callback_type& signer )&& { - auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); - const auto& protocol_features = prev_activated_protocol_features->protocol_features; - bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + bool wtmsig_enabled = detail::is_builtin_activated(prev_activated_protocol_features, pfs, builtin_protocol_feature_t::wtmsig_block_signatures); auto result = std::move(*this)._finish_next( h, pfs, validator ); result.sign( signer ); diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index 8f51ffde7b2..7fb1f96400f 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -22,9 +22,7 @@ namespace eosio { namespace chain { auto exts = b->validate_and_extract_extensions(); if ( pfa && exts.count(additional_sigs_eid) > 0 ) { - const auto& protocol_features = pfa->protocol_features; - auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); - bool wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); + bool wtmsig_enabled = detail::is_builtin_activated(pfa, pfs, builtin_protocol_feature_t::wtmsig_block_signatures); EOS_ASSERT(wtmsig_enabled, block_validate_exception, "Block contained additional_block_signatures_extension before activation of WTMsig Block Signatures"); @@ -60,13 +58,7 @@ namespace eosio { namespace chain { Extras&& ... extras ) { const auto& pfa = cur.prev_activated_protocol_features; - bool wtmsig_enabled = false; - - if (pfa) { - const auto& protocol_features = pfa->protocol_features; - auto wtmsig_digest = pfs.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); - wtmsig_enabled = wtmsig_digest && protocol_features.find(*wtmsig_digest) != protocol_features.end(); - } + bool wtmsig_enabled = pfa && detail::is_builtin_activated(pfa, pfs, builtin_protocol_feature_t::wtmsig_block_signatures); block_header_state result = std::move(cur).finish_next(b, pfs, std::forward(extras)...); diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index c6351fdb5ac..417496baaf5 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -434,7 +434,7 @@ struct controller_impl { block_header_state genheader; genheader.active_schedule = initial_schedule; genheader.pending_schedule.schedule = initial_schedule; - // TODO: if wtmsig block signatures are enabled this should be the hash of the producer authority + // NOTE: if wtmsig block signatures are enabled at genesis time this should be the hash of a producer authority schedule genheader.pending_schedule.schedule_hash = fc::sha256::hash(initial_legacy_schedule); genheader.header.timestamp = conf.genesis.initial_timestamp; genheader.header.action_mroot = conf.genesis.compute_chain_id(); @@ -1521,16 +1521,17 @@ struct controller_impl { ilog( "promoting proposed schedule (set in block ${proposed_num}) to pending; current block: ${n} lib: ${lib} schedule: ${schedule} ", ("proposed_num", *gpo.proposed_schedule_block_num)("n", pbhs.block_num) ("lib", pbhs.dpos_irreversible_blocknum) - ("schedule", static_cast(gpo.proposed_schedule) ) ); + ("schedule", producer_authority_schedule::from_shared(gpo.proposed_schedule) ) ); } EOS_ASSERT( gpo.proposed_schedule.version == pbhs.active_schedule_version + 1, producer_schedule_exception, "wrong producer schedule version specified" ); - pending->_block_stage.get()._new_pending_producer_schedule = gpo.proposed_schedule; + pending->_block_stage.get()._new_pending_producer_schedule = producer_authority_schedule::from_shared(gpo.proposed_schedule); db.modify( gpo, [&]( auto& gp ) { gp.proposed_schedule_block_num = optional(); - gp.proposed_schedule.clear(); + gp.proposed_schedule.version=0; + gp.proposed_schedule.producers.clear(); }); } @@ -2820,7 +2821,7 @@ int64_t controller::set_proposed_producers( vector producers my->db.modify( gpo, [&]( auto& gp ) { gp.proposed_schedule_block_num = cur_block_num; - gp.proposed_schedule = std::move(sch); + gp.proposed_schedule = sch.to_shared(gp.proposed_schedule.producers.get_allocator()); }); return version; } @@ -2868,7 +2869,7 @@ optional controller::proposed_producers()const { if( !gpo.proposed_schedule_block_num.valid() ) return optional(); - return (producer_authority_schedule)gpo.proposed_schedule; + return producer_authority_schedule::from_shared(gpo.proposed_schedule); } bool controller::light_validation_allowed(bool replay_opts_disabled_by_policy) const { diff --git a/libraries/chain/include/eosio/chain/authority.hpp b/libraries/chain/include/eosio/chain/authority.hpp index 604cc5d29bd..882d7be0799 100644 --- a/libraries/chain/include/eosio/chain/authority.hpp +++ b/libraries/chain/include/eosio/chain/authority.hpp @@ -13,9 +13,6 @@ namespace eosio { namespace chain { using shared_public_key_data = fc::static_variant; -template struct overloaded : Ts... { using Ts::operator()...; }; -template overloaded(Ts...) -> overloaded; - struct shared_public_key { shared_public_key( shared_public_key_data&& p ) : pubkey(std::move(p)) {} diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index eda7d45b033..6875d7f800a 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -25,9 +25,9 @@ namespace legacy { }; /// from block_header_state_common - uint32_t block_num; - uint32_t dpos_proposed_irreversible_blocknum; - uint32_t dpos_irreversible_blocknum; + uint32_t block_num = 0; + uint32_t dpos_proposed_irreversible_blocknum = 0; + uint32_t dpos_irreversible_blocknum = 0; producer_schedule_type active_schedule; incremental_merkle blockroot_merkle; flat_map producer_to_last_produced; @@ -65,6 +65,10 @@ namespace detail { digest_type schedule_hash; producer_authority_schedule schedule; }; + + bool is_builtin_activated( const protocol_feature_activation_set_ptr& pfa, + const protocol_feature_set& pfs, + builtin_protocol_feature_t feature_codename ); } struct pending_block_header_state : public detail::block_header_state_common { diff --git a/libraries/chain/include/eosio/chain/exceptions.hpp b/libraries/chain/include/eosio/chain/exceptions.hpp index 03033726783..6d8e6682177 100644 --- a/libraries/chain/include/eosio/chain/exceptions.hpp +++ b/libraries/chain/include/eosio/chain/exceptions.hpp @@ -565,7 +565,7 @@ namespace eosio { namespace chain { FC_DECLARE_DERIVED_EXCEPTION( no_block_signatures, producer_exception, 3170011, "The signer returned no valid block signatures" ) FC_DECLARE_DERIVED_EXCEPTION( unsupported_multiple_block_signatures, producer_exception, - 3170011, "The signer returned multiple signatures but that is not supported" ) + 3170012, "The signer returned multiple signatures but that is not supported" ) FC_DECLARE_DERIVED_EXCEPTION( reversible_blocks_exception, chain_exception, 3180000, "Reversible Blocks exception" ) diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index 56281da86c3..99b4e8ba3dd 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -57,12 +57,12 @@ namespace eosio { namespace chain { using snapshot_type = snapshot_global_property_object; static snapshot_global_property_object to_snapshot_row( const global_property_object& value, const chainbase::database& ) { - return {value.proposed_schedule_block_num, (producer_authority_schedule)value.proposed_schedule, value.configuration}; + return {value.proposed_schedule_block_num, producer_authority_schedule::from_shared(value.proposed_schedule), value.configuration}; } static void from_snapshot_row( snapshot_global_property_object&& row, global_property_object& value, chainbase::database& ) { value.proposed_schedule_block_num = row.proposed_schedule_block_num; - value.proposed_schedule = row.proposed_schedule; + value.proposed_schedule = row.proposed_schedule.to_shared(value.proposed_schedule.producers.get_allocator()); value.configuration = row.configuration; } }; diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index c1ad09fab03..a0862135f66 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -46,12 +46,59 @@ namespace eosio { namespace chain { }; } + struct shared_block_signing_authority_v0 { + shared_block_signing_authority_v0() = delete; + shared_block_signing_authority_v0( const shared_block_signing_authority_v0& ) = default; + shared_block_signing_authority_v0( shared_block_signing_authority_v0&& ) = default; + shared_block_signing_authority_v0& operator= ( shared_block_signing_authority_v0 && ) = default; + shared_block_signing_authority_v0& operator= ( const shared_block_signing_authority_v0 & ) = default; + + explicit shared_block_signing_authority_v0( chainbase::allocator alloc ) + :keys(alloc){} + + uint32_t threshold = 0; + shared_vector keys; + }; + + using shared_block_signing_authority = static_variant; + + struct shared_producer_authority { + shared_producer_authority() = delete; + shared_producer_authority( const shared_producer_authority& ) = default; + shared_producer_authority( shared_producer_authority&& ) = default; + shared_producer_authority& operator= ( shared_producer_authority && ) = default; + shared_producer_authority& operator= ( const shared_producer_authority & ) = default; + + shared_producer_authority( const name& producer_name, shared_block_signing_authority&& authority ) + :producer_name(producer_name) + ,authority(std::move(authority)) + {} + + name producer_name; + shared_block_signing_authority authority; + }; + + struct shared_producer_authority_schedule { + shared_producer_authority_schedule() = delete; + + explicit shared_producer_authority_schedule( chainbase::allocator alloc ) + :producers(alloc){} + + shared_producer_authority_schedule( const shared_producer_authority_schedule& ) = default; + shared_producer_authority_schedule( shared_producer_authority_schedule&& ) = default; + shared_producer_authority_schedule& operator= ( shared_producer_authority_schedule && ) = default; + shared_producer_authority_schedule& operator= ( const shared_producer_authority_schedule & ) = default; + + uint32_t version = 0; ///< sequentially incrementing version number + shared_vector producers; + }; + /** * block signing authority version 0 * this authority allows for a weighted threshold multi-sig per-producer */ struct block_signing_authority_v0 { - uint32_t threshold; + uint32_t threshold = 0; vector keys; bool key_is_relevant( const public_key_type& key ) const { @@ -74,6 +121,29 @@ namespace eosio { namespace chain { return false; } + auto to_shared(chainbase::allocator alloc) const { + shared_block_signing_authority_v0 result(alloc); + result.threshold = threshold; + result.keys.clear(); + result.keys.reserve(keys.size()); + for (const auto& k: keys) { + result.keys.emplace_back(shared_key_weight::convert(alloc, k)); + } + + return result; + } + + static auto from_shared(const shared_block_signing_authority_v0& src) { + block_signing_authority_v0 result; + result.threshold = src.threshold; + result.keys.reserve(src.keys.size()); + for (const auto& k: src.keys) { + result.keys.push_back(k); + } + + return result; + } + friend bool operator == ( const block_signing_authority_v0& lhs, const block_signing_authority_v0& rhs ) { return tie( lhs.threshold, lhs.keys ) == tie( rhs.threshold, rhs.keys ); } @@ -108,6 +178,26 @@ namespace eosio { namespace chain { return keys_satisfy(keys, authority); } + auto to_shared(chainbase::allocator alloc) const { + auto shared_auth = authority.visit([&alloc](const auto& a) { + return a.to_shared(alloc); + }); + + return shared_producer_authority(producer_name, std::move(shared_auth)); + } + + static auto from_shared( const shared_producer_authority& src ) { + producer_authority result; + result.producer_name = src.producer_name; + result.authority = src.authority.visit(overloaded { + [](const shared_block_signing_authority_v0& a) { + return block_signing_authority_v0::from_shared(a); + } + }); + + return result; + } + /** * ABI's for contracts expect variants to be serialized as a 2 entry array of * [type-name, value]. @@ -130,120 +220,6 @@ namespace eosio { namespace chain { } }; - struct shared_block_signing_authority_v0 { - shared_block_signing_authority_v0( const shared_block_signing_authority_v0& ) = default; - shared_block_signing_authority_v0( shared_block_signing_authority_v0&& ) = default; - shared_block_signing_authority_v0& operator= ( shared_block_signing_authority_v0 && ) = default; - shared_block_signing_authority_v0& operator= ( const shared_block_signing_authority_v0 & ) = default; - - shared_block_signing_authority_v0( chainbase::allocator alloc ) - :keys(alloc){} - - uint32_t threshold; - shared_vector keys; - - friend bool operator == ( const shared_block_signing_authority_v0& lhs, const shared_block_signing_authority_v0& rhs ) { - return tie( lhs.threshold, lhs.keys ) == tie( rhs.threshold, rhs.keys ); - } - friend bool operator != ( const shared_block_signing_authority_v0& lhs, const shared_block_signing_authority_v0& rhs ) { - return tie( lhs.threshold, lhs.keys ) != tie( rhs.threshold, rhs.keys ); - } - }; - - using shared_block_signing_authority = static_variant; - - struct shared_producer_authority { - shared_producer_authority() = delete; - shared_producer_authority( const shared_producer_authority& ) = default; - shared_producer_authority( shared_producer_authority&& ) = default; - shared_producer_authority& operator= ( shared_producer_authority && ) = default; - shared_producer_authority& operator= ( const shared_producer_authority & ) = default; - - shared_producer_authority( const name& producer_name, shared_block_signing_authority&& authority ) - :producer_name(producer_name) - ,authority(std::move(authority)) - {} - - name producer_name; - shared_block_signing_authority authority; - - friend bool operator == ( const shared_producer_authority& lhs, const shared_producer_authority& rhs ) { - return tie( lhs.producer_name, lhs.authority ) == tie( rhs.producer_name, rhs.authority ); - } - friend bool operator != ( const shared_producer_authority& lhs, const shared_producer_authority& rhs ) { - return tie( lhs.producer_name, lhs.authority ) != tie( rhs.producer_name, rhs.authority ); - } - }; - - namespace detail { - template - struct shared_converter; - - template - auto shared_convert( chainbase::allocator alloc, const T& src ) { - return shared_converter>::convert(alloc, src); - } - - template - auto shared_convert( const T& src ) { - return shared_converter>::convert(src); - } - - template<> - struct shared_converter { - static shared_block_signing_authority_v0 convert (chainbase::allocator alloc, const block_signing_authority_v0& src) { - shared_block_signing_authority_v0 result(alloc); - result.threshold = src.threshold; - result.keys.clear(); - result.keys.reserve(src.keys.size()); - for (const auto& k: src.keys) { - result.keys.emplace_back(shared_key_weight::convert(alloc, k)); - } - - return result; - } - }; - - template<> - struct shared_converter { - static block_signing_authority_v0 convert (const shared_block_signing_authority_v0& src) { - block_signing_authority_v0 result; - result.threshold = src.threshold; - result.keys.reserve(src.keys.size()); - for (const auto& k: src.keys) { - result.keys.push_back(k); - } - - return result; - } - }; - - template<> - struct shared_converter { - static shared_producer_authority convert (chainbase::allocator alloc, const producer_authority& src) { - auto authority = src.authority.visit([&alloc](const auto& a) { - return shared_convert(alloc, a); - }); - - return shared_producer_authority(src.producer_name, std::move(authority)); - } - - }; - - template<> - struct shared_converter { - static producer_authority convert (const shared_producer_authority& src) { - producer_authority result; - result.producer_name = src.producer_name; - result.authority = src.authority.visit([](const auto& a) { - return shared_convert(a); - }); - - return result; - } - }; - } - struct producer_authority_schedule { producer_authority_schedule() = default; @@ -263,6 +239,28 @@ namespace eosio { namespace chain { ,producers(producers) {} + auto to_shared(chainbase::allocator alloc) const { + auto result = shared_producer_authority_schedule(alloc); + result.version = version; + result.producers.clear(); + result.producers.reserve( producers.size() ); + for( const auto& p : producers ) { + result.producers.emplace_back(p.to_shared(alloc)); + } + return result; + } + + static auto from_shared( const shared_producer_authority_schedule& src ) { + producer_authority_schedule result; + result.version = src.version; + result.producers.reserve(src.producers.size()); + for( const auto& p : src.producers ) { + result.producers.emplace_back(producer_authority::from_shared(p)); + } + + return result; + } + uint32_t version = 0; ///< sequentially incrementing version number vector producers; @@ -284,13 +282,10 @@ namespace eosio { namespace chain { /** * Block Header Extension Compatibility */ - struct producer_schedule_change_extension : producer_authority_schedule, fc::reflect_init { + struct producer_schedule_change_extension : producer_authority_schedule { static constexpr uint16_t extension_id() { return 1; } static constexpr bool enforce_unique() { return true; } - void reflector_init() { - static_assert( fc::raw::has_feature_reflector_init_on_unpacked_reflected_types, "producer_schedule_extension expects FC to support reflector_init" ); - } producer_schedule_change_extension() = default; producer_schedule_change_extension(const producer_schedule_change_extension&) = default; @@ -301,47 +296,6 @@ namespace eosio { namespace chain { }; - - struct shared_producer_authority_schedule { - shared_producer_authority_schedule( chainbase::allocator alloc ) - :producers(alloc){} - - shared_producer_authority_schedule( const shared_producer_authority_schedule& ) = default; - shared_producer_authority_schedule( shared_producer_authority_schedule&& ) = default; - shared_producer_authority_schedule& operator= ( shared_producer_authority_schedule && ) = default; - shared_producer_authority_schedule& operator= ( const shared_producer_authority_schedule & ) = default; - - - shared_producer_authority_schedule& operator=( const producer_authority_schedule& a ) { - version = a.version; - producers.clear(); - producers.reserve( a.producers.size() ); - for( const auto& p : a.producers ) { - producers.emplace_back(detail::shared_convert(producers.get_allocator(), p)); - } - return *this; - } - - explicit operator producer_authority_schedule()const { - producer_authority_schedule result; - result.version = version; - result.producers.reserve(producers.size()); - for( const auto& p : producers ) { - result.producers.emplace_back(detail::shared_convert(p)); - } - - return result; - } - - void clear() { - version = 0; - producers.clear(); - } - - uint32_t version = 0; ///< sequentially incrementing version number - shared_vector producers; - }; - inline bool operator == ( const producer_authority& pa, const shared_producer_authority& pb ) { if(pa.producer_name != pb.producer_name) return false; diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index 94c3aa93832..7e26eab6bb8 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -382,6 +382,9 @@ namespace eosio { namespace chain { return ( flags & ~static_cast(field) ); } + template struct overloaded : Ts... { using Ts::operator()...; }; + template overloaded(Ts...) -> overloaded; + } } // eosio::chain FC_REFLECT_EMPTY( eosio::chain::void_t ) diff --git a/libraries/chain/producer_schedule.cpp b/libraries/chain/producer_schedule.cpp index 151e9e7e200..2284229a3da 100644 --- a/libraries/chain/producer_schedule.cpp +++ b/libraries/chain/producer_schedule.cpp @@ -1,3 +1,7 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ #include namespace eosio { namespace chain { diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index f3afa9d44f4..d340de6e0d5 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -222,7 +222,6 @@ class privileged_api : public context_aware_api { datastream ds( packed_producer_schedule, datalen ); vector producers; -// if ( context.control.is_builtin_activated( builtin_protocol_feature_t::wtmsig_block_signatures )) { vector old_version; fc::raw::unpack(ds, old_version); From 0d88b0b9716c5395fd73b46c65b93672b5fed117 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 24 Jul 2019 18:29:16 -0400 Subject: [PATCH 34/36] address PR feedback from @arhag --- libraries/chain/apply_context.cpp | 4 +- libraries/chain/block_header_state.cpp | 46 +++++++++--------- libraries/chain/block_state.cpp | 18 ++----- libraries/chain/controller.cpp | 12 ++--- .../eosio/chain/block_header_state.hpp | 1 - .../include/eosio/chain/chain_snapshot.hpp | 7 ++- .../eosio/chain/database_header_object.hpp | 6 +-- .../include/eosio/chain/producer_schedule.hpp | 48 +++++++++++-------- libraries/chain/include/eosio/chain/types.hpp | 12 +++++ libraries/chain/producer_schedule.cpp | 6 +-- libraries/chain/protocol_feature_manager.cpp | 8 ++-- libraries/chain/wasm_interface.cpp | 6 ++- libraries/testing/tester.cpp | 17 ++++--- plugins/producer_plugin/producer_plugin.cpp | 17 ++++--- unittests/producer_schedule_tests.cpp | 42 ++++++++++++++++ unittests/protocol_feature_tests.cpp | 10 ++-- .../deferred_test/deferred_test.cpp | 2 +- 17 files changed, 156 insertions(+), 106 deletions(-) diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index 01c86764d1c..b7f56361993 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -372,8 +372,8 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a ("expected", trx_context.id)("actual", context.sender_trx_id) ); } else { - FC_ASSERT( trx.transaction_extensions.size() == 0, "invariant failure" ); - trx.transaction_extensions.emplace_back( + emplace_extension( + trx.transaction_extensions, deferred_transaction_generation_context::extension_id(), fc::raw::pack( deferred_transaction_generation_context( trx_context.id, sender_id, receiver ) ) ); diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 842053efde8..9771af57d42 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -15,10 +15,6 @@ namespace eosio { namespace chain { } } - bool block_header_state::is_active_producer( account_name n )const { - return producer_to_last_produced.find(n) != producer_to_last_produced.end(); - } - producer_authority block_header_state::get_scheduled_producer( block_timestamp_type t )const { auto index = t.slot % (active_schedule.producers.size() * config::producer_repetitions); index /= config::producer_repetitions; @@ -193,7 +189,8 @@ namespace eosio { namespace chain { h.schedule_version = active_schedule_version; if( new_protocol_feature_activations.size() > 0 ) { - h.header_extensions.emplace_back( + emplace_extension( + h.header_extensions, protocol_feature_activation::extension_id(), fc::raw::pack( protocol_feature_activation{ std::move(new_protocol_feature_activations) } ) ); @@ -202,7 +199,8 @@ namespace eosio { namespace chain { if (new_producers) { if ( detail::is_builtin_activated(prev_activated_protocol_features, pfs, builtin_protocol_feature_t::wtmsig_block_signatures) ) { // add the header extension to update the block schedule - h.header_extensions.emplace_back( + emplace_extension( + h.header_extensions, producer_schedule_change_extension::extension_id(), fc::raw::pack( producer_schedule_change_extension( *new_producers ) ) ); @@ -357,13 +355,14 @@ namespace eosio { namespace chain { const signer_callback_type& signer )&& { - bool wtmsig_enabled = detail::is_builtin_activated(prev_activated_protocol_features, pfs, builtin_protocol_feature_t::wtmsig_block_signatures); + auto pfa = prev_activated_protocol_features; auto result = std::move(*this)._finish_next( h, pfs, validator ); result.sign( signer ); h.producer_signature = result.header.producer_signature; if( !result.additional_signatures.empty() ) { + bool wtmsig_enabled = detail::is_builtin_activated(pfa, pfs, builtin_protocol_feature_t::wtmsig_block_signatures); EOS_ASSERT(wtmsig_enabled, producer_schedule_exception, "Block was signed with multiple signatures before WTMsig block signatures are enabled" ); } @@ -409,28 +408,27 @@ namespace eosio { namespace chain { } void block_header_state::verify_signee( )const { - flat_set keys; - keys.reserve(1 + additional_signatures.size()); - keys.emplace(fc::crypto::public_key( header.producer_signature, sig_digest(), true )); + std::set keys; + auto digest = sig_digest(); + keys.emplace(fc::crypto::public_key( header.producer_signature, digest, true )); for (const auto& s: additional_signatures) { - auto key = fc::crypto::public_key( s, sig_digest(), true ); - EOS_ASSERT(keys.find(key) == keys.end(), wrong_signing_key, - "block signed by same key twice", - ("key", key)); - - keys.emplace(std::move(key)); + auto res = keys.emplace(s, digest, true); + EOS_ASSERT(res.second, wrong_signing_key, "block signed by same key twice", ("key", *res.first)); } - for (const auto& k: keys) { - EOS_ASSERT(producer_authority::key_is_relevant(k, valid_block_signing_authority), wrong_signing_key, - "block signed by unexpected key", - ("signing_key", k)("valid_keys", valid_block_signing_authority)); - } + 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", keys)("valid_keys", valid_block_signing_authority)); - EOS_ASSERT(producer_authority::keys_satisfy(keys, valid_block_signing_authority), wrong_signing_key, - "block signatures do not satisfy the block signing authority", - ("keys", keys)("authority", valid_block_signing_authority)); + EOS_ASSERT(is_satisfied, wrong_signing_key, + "block signatures do not satisfy the block signing authority", + ("keys", keys)("authority", valid_block_signing_authority)); } /** diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index 7fb1f96400f..fea31bea05d 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -21,12 +21,7 @@ namespace eosio { namespace chain { { auto exts = b->validate_and_extract_extensions(); - if ( pfa && exts.count(additional_sigs_eid) > 0 ) { - bool wtmsig_enabled = detail::is_builtin_activated(pfa, pfs, builtin_protocol_feature_t::wtmsig_block_signatures); - - EOS_ASSERT(wtmsig_enabled, block_validate_exception, - "Block contained additional_block_signatures_extension before activation of WTMsig Block Signatures"); - + if ( exts.count(additional_sigs_eid) > 0 ) { auto& additional_sigs = exts.lower_bound(additional_sigs_eid)->second.get(); return std::move(additional_sigs.signatures); @@ -57,12 +52,12 @@ namespace eosio { namespace chain { const protocol_feature_set& pfs, Extras&& ... extras ) { - const auto& pfa = cur.prev_activated_protocol_features; - bool wtmsig_enabled = pfa && detail::is_builtin_activated(pfa, pfs, builtin_protocol_feature_t::wtmsig_block_signatures); - + auto pfa = cur.prev_activated_protocol_features; block_header_state result = std::move(cur).finish_next(b, pfs, std::forward(extras)...); if (!result.additional_signatures.empty()) { + bool wtmsig_enabled = detail::is_builtin_activated(pfa, pfs, builtin_protocol_feature_t::wtmsig_block_signatures); + EOS_ASSERT(wtmsig_enabled, block_validate_exception, "Block has multiple signatures before activation of WTMsig Block Signatures"); @@ -71,10 +66,7 @@ namespace eosio { namespace chain { static_assert(fc::reflector::total_member_count == 1); static_assert(std::is_same_v>); - b.block_extensions.emplace_back( - additional_sigs_eid, - fc::raw::pack( result.additional_signatures ) - ); + emplace_extension(b.block_extensions, additional_sigs_eid, fc::raw::pack( result.additional_signatures )); } return result; diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 417496baaf5..30a5d846a1f 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -2844,15 +2844,9 @@ const producer_authority_schedule& controller::pending_producers()const { return my->pending->_block_stage.get()._block_state->pending_schedule.schedule; if( my->pending->_block_stage.contains() ) { - if (is_builtin_activated(builtin_protocol_feature_t::wtmsig_block_signatures)) { - auto exts = my->pending->_block_stage.get()._unsigned_block->validate_and_extract_header_extensions(); - if (exts.count(producer_schedule_change_extension::extension_id())) - return exts.lower_bound(producer_schedule_change_extension::extension_id())->second.get(); - } else { - const auto& new_prods_cache = my->pending->_block_stage.get()._new_producer_authority_cache; - if( new_prods_cache ) { - return *new_prods_cache; - } + const auto& new_prods_cache = my->pending->_block_stage.get()._new_producer_authority_cache; + if( new_prods_cache ) { + return *new_prods_cache; } } diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 6875d7f800a..784c3156641 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -145,7 +145,6 @@ struct block_header_state : public detail::block_header_state_common { bool has_pending_producers()const { return pending_schedule.schedule.producers.size(); } uint32_t calc_dpos_last_irreversible( account_name producer_of_next_block )const; - bool is_active_producer( account_name n )const; producer_authority get_scheduled_producer( block_timestamp_type t )const; const block_id_type& prev()const { return header.previous; } diff --git a/libraries/chain/include/eosio/chain/chain_snapshot.hpp b/libraries/chain/include/eosio/chain/chain_snapshot.hpp index b1ecd520955..e7322b56ace 100644 --- a/libraries/chain/include/eosio/chain/chain_snapshot.hpp +++ b/libraries/chain/include/eosio/chain/chain_snapshot.hpp @@ -16,14 +16,13 @@ struct chain_snapshot_header { * - Incompatible with version 1. * - Adds new indices for: protocol_state_object and account_ram_correction_object * 3: Updated for v2.0.0 protocol features: - * - WebAuthn keys - * 4: Updated chain snapshot for wtmsig block signing protocol feature: * - forwards compatible with version 2 - * - the block header state changed to include producer authorities and additional signatures + * - WebAuthn keys + * - wtmsig block siganatures: the block header state changed to include producer authorities and additional signatures */ static constexpr uint32_t minimum_compatible_version = 2; - static constexpr uint32_t current_version = 4; + static constexpr uint32_t current_version = 3; uint32_t version = current_version; diff --git a/libraries/chain/include/eosio/chain/database_header_object.hpp b/libraries/chain/include/eosio/chain/database_header_object.hpp index b8625bfae19..7b5348b756d 100644 --- a/libraries/chain/include/eosio/chain/database_header_object.hpp +++ b/libraries/chain/include/eosio/chain/database_header_object.hpp @@ -27,11 +27,11 @@ namespace eosio { namespace chain { * - 1 : initial version, prior to this no `database_header_object` existed in the shared memory file but * no changes to its format were made so it can be safely added to existing databases * - 2 : shared_authority now holds shared_key_weights & shared_public_keys - * - 3 : change from producer_key to producer_authority for many in-memory structures + * change from producer_key to producer_authority for many in-memory structures */ - static constexpr uint32_t current_version = 3; - static constexpr uint32_t minimum_version = 3; + static constexpr uint32_t current_version = 2; + static constexpr uint32_t minimum_version = 2; id_type id; uint32_t version = current_version; diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index a0862135f66..fb12dab0d58 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -98,27 +98,33 @@ namespace eosio { namespace chain { * this authority allows for a weighted threshold multi-sig per-producer */ struct block_signing_authority_v0 { + static constexpr std::string_view abi_type_name() { return "block_signing_authority_v0"; } + uint32_t threshold = 0; vector keys; - bool key_is_relevant( const public_key_type& key ) const { - return std::find_if(keys.begin(), keys.end(), [&key](const auto& kw){ - return kw.key == key; - }) != keys.end(); + template + void for_each_key( Op op ) const { + for (const auto& kw : keys ) { + op(kw.key); + } } - bool keys_satisfy( const flat_set& presented_keys ) const { + std::pair keys_satisfy_and_relevant( const std::set& presented_keys ) const { + size_t num_relevant_keys = 0; uint32_t total_weight = 0; - for (const auto& kw : keys) { + for (const auto& kw : keys ) { const auto& iter = presented_keys.find(kw.key); if (iter != presented_keys.end()) { - total_weight += kw.weight; - } + ++num_relevant_keys; - if (total_weight >= threshold) - return true; + if( total_weight < threshold ) { + total_weight += std::min(std::numeric_limits::max() - total_weight, kw.weight); + } + } } - return false; + + return {total_weight >= threshold, num_relevant_keys}; } auto to_shared(chainbase::allocator alloc) const { @@ -158,24 +164,26 @@ namespace eosio { namespace chain { name producer_name; block_signing_authority authority; - static bool key_is_relevant( const public_key_type& key, const block_signing_authority& authority ) { - return authority.visit([&key](const auto &a){ - return a.key_is_relevant(key); + template + static void for_each_key( const block_signing_authority& authority, Op op ) { + authority.visit([&op](const auto &a){ + a.for_each_key(op); }); } - bool key_is_relevant( const public_key_type& key ) const { - return key_is_relevant(key, authority); + template + void for_each_key( Op op ) const { + for_each_key(authority, op); } - static bool keys_satisfy( const flat_set& keys, const block_signing_authority& authority ) { + static std::pair keys_satisfy_and_relevant( const std::set& keys, const block_signing_authority& authority ) { return authority.visit([&keys](const auto &a){ - return a.keys_satisfy(keys); + return a.keys_satisfy_and_relevant(keys); }); } - bool keys_satisfy( const flat_set& keys ) const { - return keys_satisfy(keys, authority); + std::pair keys_satisfy_and_relevant( const std::set& presented_keys ) const { + return keys_satisfy_and_relevant(presented_keys, authority); } auto to_shared(chainbase::allocator alloc) const { diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index 7e26eab6bb8..daaf63c234b 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -275,6 +275,18 @@ namespace eosio { namespace chain { */ typedef vector>> extensions_type; + /** + * emplace an extension into the extensions type such that it is properly ordered by extension id + * this assumes exts is already sorted by extension id + */ + inline auto emplace_extension( extensions_type& exts, uint16_t eid, vector&& data) { + auto insert_itr = std::lower_bound(exts.begin(), exts.end(), eid, [](const auto& ext, uint16_t id){ + return ext.first < id; + }); + + return exts.emplace(insert_itr, eid, std::move(data)); + } + template class end_insert_iterator : public std::iterator< std::output_iterator_tag, void, void, void, void > diff --git a/libraries/chain/producer_schedule.cpp b/libraries/chain/producer_schedule.cpp index 2284229a3da..03d4469578f 100644 --- a/libraries/chain/producer_schedule.cpp +++ b/libraries/chain/producer_schedule.cpp @@ -11,12 +11,10 @@ fc::variant producer_authority::get_abi_variant() const { fc::variant value; fc::to_variant(a, value); - std::string full_type_name = fc::get_typename>::name(); - auto last_colon = full_type_name.rfind(":"); - std::string type_name = last_colon == std::string::npos ? std::move(full_type_name): full_type_name.substr(last_colon + 1); + fc::variant type = std::string(std::decay_t::abi_type_name()); return fc::variants { - fc::variant(std::move(type_name)), + std::move(type), std::move(value) }; }); diff --git a/libraries/chain/protocol_feature_manager.cpp b/libraries/chain/protocol_feature_manager.cpp index 0ae60d91749..9e944cd4f92 100644 --- a/libraries/chain/protocol_feature_manager.cpp +++ b/libraries/chain/protocol_feature_manager.cpp @@ -170,20 +170,20 @@ Enables usage of WebAuthn keys and signatures. } ) ( builtin_protocol_feature_t::wtmsig_block_signatures, builtin_protocol_feature_spec{ "WTMSIG_BLOCK_SIGNATURES", - fc::variant("46d65101388a44ebdbbb152978ac1879e34785a2d57ad050196826734370b995").as(), + fc::variant("ab76031cad7a457f4fd5f5fca97a3f03b8a635278e0416f77dcc91eb99a48e10").as(), // SHA256 hash of the raw message below within the comment delimiters (do not modify message below). /* Builtin protocol feature: WTMSIG_BLOCK_SIGNATURES -Allows producers to specify a subset of our auhority structures as the method for signing blocks. +Allows producers to specify a multisig of weighted keys as the authority for signing blocks. A valid block header: is no longer allowed to have a non-empty `new_producers` field; -must announce new producer schedules using a block header extension with ID `1` +must announce new producer schedules using a block header extension with ID `1`. A valid signed block: must continue to have exactly one signature in its `signatures` field; -and may have additional signatures in a block extension with ID `2` +and may have additional signatures in a block extension with ID `2`. Privileged Contracts: may continue to use `set_proposed_producers` as they have; diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index d340de6e0d5..cf11f17d2ff 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -183,16 +183,18 @@ class privileged_api : public context_aware_api { "Producer schedule cannot be empty" ); + const auto num_supported_key_types = context.db.get().num_supported_key_types; + // check that producers are unique std::set unique_producers; for (const auto& p: producers) { EOS_ASSERT( context.is_account(p.producer_name), wasm_execution_error, "producer schedule includes a nonexisting account" ); - p.authority.visit([this, &p](const auto& a) { + p.authority.visit([&p, num_supported_key_types](const auto& a) { uint32_t sum_weights = 0; std::set unique_keys; for (const auto& kw: a.keys ) { - EOS_ASSERT( kw.key.which() < context.db.get().num_supported_key_types, unactivated_key_type, + EOS_ASSERT( kw.key.which() < num_supported_key_types, unactivated_key_type, "Unactivated key type used in proposed producer schedule"); EOS_ASSERT( kw.key.valid(), wasm_execution_error, "producer schedule includes an invalid key" ); diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index 343987fe8b2..ab6e39dd3f1 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -337,16 +337,15 @@ namespace eosio { namespace testing { auto producer = control->head_block_state()->get_scheduled_producer( control->pending_block_time() ); vector signing_keys; - for ( const auto& bsk : block_signing_private_keys ) { - if (producer.key_is_relevant(bsk.first)) { - signing_keys.push_back( bsk.second ); + auto default_active_key = get_public_key( producer.producer_name, "active"); + producer.for_each_key([&](const public_key_type& key){ + const auto& iter = block_signing_private_keys.find(key); + if(iter != block_signing_private_keys.end()) { + signing_keys.push_back(iter->second); + } else if (key == default_active_key) { + signing_keys.emplace_back( get_private_key( producer.producer_name, "active") ); } - } - - // if the "active" key is relevant or no other keys were found, add the "active" private key - if( signing_keys.empty() || producer.key_is_relevant(get_public_key( producer.producer_name, "active") ) ) { - signing_keys.emplace_back( get_private_key( producer.producer_name, "active") ); - } + }); control->finalize_block( [&]( digest_type d ) { std::vector result; diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 0f00fd2f159..16fd8e3a362 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -1304,8 +1304,12 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { const auto& scheduled_producer = hbs->get_scheduled_producer(block_time); auto currrent_watermark_itr = _producer_watermarks.find(scheduled_producer.producer_name); - auto num_relevant_signatures = std::count_if(_signature_providers.begin(), _signature_providers.end(), [&scheduled_producer](const auto& p){ - return scheduled_producer.key_is_relevant(p.first); + size_t num_relevant_signatures = 0; + scheduled_producer.for_each_key([&](const public_key_type& key){ + const auto& iter = _signature_providers.find(key); + if(iter != _signature_providers.end()) { + num_relevant_signatures++; + } }); auto irreversible_block_age = get_irreversible_block_age(); @@ -1818,11 +1822,12 @@ void producer_plugin_impl::produce_block() { relevant_providers.reserve(_signature_providers.size()); - for (const auto& p : _signature_providers) { - if (producer_authority::key_is_relevant(p.first, auth)) { - relevant_providers.emplace_back(p.second); + producer_authority::for_each_key(auth, [&](const public_key_type& key){ + const auto& iter = _signature_providers.find(key); + if (iter != _signature_providers.end()) { + relevant_providers.emplace_back(iter->second); } - } + }); EOS_ASSERT(relevant_providers.size() > 0, producer_priv_key_not_found, "Attempting to produce a block for which we don't have any relevant private keys"); diff --git a/unittests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp index f1d61a7ba78..a9d7ed82faf 100644 --- a/unittests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -681,4 +681,46 @@ BOOST_FIXTURE_TEST_CASE( duplicate_keys_test, TESTER ) try { BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() ); } FC_LOG_AND_RETHROW() +BOOST_AUTO_TEST_CASE( large_authority_overflow_test ) try { + + block_signing_authority_v0 auth; + { // create a large authority that should overflow + const size_t pre_overflow_count = 65'537UL; // enough for weights of 0xFFFF to add up to 0xFFFFFFFF + auth.keys.reserve(pre_overflow_count + 1); + + for (int i = 0; i < pre_overflow_count; i++) { + auto key_str = std::to_string(i) + "_bsk"; + auth.keys.emplace_back(key_weight{get_public_key(N(alice), key_str), 0xFFFFU}); + } + + // reduce the last weight by 1 so that its unsatisfiable + auth.keys.back().weight = 0xFFFEU; + + // add one last key with a weight of 2 so that its only satisfiable with values that sum to an overflow of 32bit uint + auth.keys.emplace_back(key_weight{get_public_key(N(alice), std::to_string(pre_overflow_count) + "_bsk"), 0x0002U}); + + auth.threshold = 0xFFFFFFFFUL; + } + + std::set provided_keys; + { // construct a set of all keys to provide + for( const auto& kw: auth.keys) { + provided_keys.emplace(kw.key); + } + } + + { // prove the naive accumulation overflows + uint32_t total = 0; + for( const auto& kw: auth.keys) { + total += kw.weight; + } + BOOST_REQUIRE_EQUAL(total, 0x0UL); + } + + auto res = auth.keys_satisfy_and_relevant(provided_keys); + + BOOST_REQUIRE_EQUAL(res.first, true); + BOOST_REQUIRE_EQUAL(res.second, provided_keys.size()); +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp index 4a6d9304227..499bffa84f4 100644 --- a/unittests/protocol_feature_tests.cpp +++ b/unittests/protocol_feature_tests.cpp @@ -1589,7 +1589,8 @@ BOOST_AUTO_TEST_CASE( producer_schedule_change_extension_test ) { try { // create a bad block that has the producer schedule change extension before the feature upgrade auto bad_block = std::make_shared(last_legacy_block->clone()); - bad_block->header_extensions.emplace_back( + emplace_extension( + bad_block->header_extensions, producer_schedule_change_extension::extension_id(), fc::raw::pack(std::make_pair(hbs->active_schedule.version + 1, std::vector{})) ); @@ -1635,7 +1636,8 @@ BOOST_AUTO_TEST_CASE( producer_schedule_change_extension_test ) { try { // create a bad block that has the producer schedule change extension that is valid but not warranted by actions in the block auto bad_block = std::make_shared(first_new_block->clone()); - bad_block->header_extensions.emplace_back( + emplace_extension( + bad_block->header_extensions, producer_schedule_change_extension::extension_id(), fc::raw::pack(std::make_pair(hbs->active_schedule.version + 1, std::vector{})) ); @@ -1698,7 +1700,7 @@ BOOST_AUTO_TEST_CASE( wtmsig_block_signing_inflight_legacy_test ) { try { c.produce_block(); // ensure that the next block is updated to the new schedule - BOOST_REQUIRE_EXCEPTION( c.produce_block(), wrong_signing_key, fc_exception_message_is( "block signed by unexpected key" )); + BOOST_REQUIRE_EXCEPTION( c.produce_block(), no_block_signatures, fc_exception_message_is( "Signer returned no signatures" )); c.control->abort_block(); c.block_signing_private_keys.emplace(get_public_key(N(eosio), "bsk"), get_private_key(N(eosio), "bsk")); @@ -1734,7 +1736,7 @@ BOOST_AUTO_TEST_CASE( wtmsig_block_signing_inflight_extension_test ) { try { c.produce_block(); // ensure that the next block is updated to the new schedule - BOOST_REQUIRE_EXCEPTION( c.produce_block(), wrong_signing_key, fc_exception_message_is( "block signed by unexpected key" )); + BOOST_REQUIRE_EXCEPTION( c.produce_block(), no_block_signatures, fc_exception_message_is( "Signer returned no signatures" )); c.control->abort_block(); c.block_signing_private_keys.emplace(get_public_key(N(eosio), "bsk"), get_private_key(N(eosio), "bsk")); diff --git a/unittests/test-contracts/deferred_test/deferred_test.cpp b/unittests/test-contracts/deferred_test/deferred_test.cpp index 1ff307d8426..b4d271af7ce 100644 --- a/unittests/test-contracts/deferred_test/deferred_test.cpp +++ b/unittests/test-contracts/deferred_test/deferred_test.cpp @@ -25,7 +25,7 @@ void deferred_test::defercall( name payer, uint64_t sender_id, name contract, ui read_transaction( buffer, tx_size ); auto tx_id = sha256( buffer, tx_size ); char context_buffer[56]; - trx.transaction_extensions.emplace_back( (uint16_t)0, std::vector() ); + emplace_extension(trx.transaction_extensions, (uint16_t)0, std::vector() ); auto& context_vector = std::get<1>( trx.transaction_extensions.back() ); context_vector.resize(56); datastream ds( context_vector.data(), 56 ); From 607d70de1fc7dab9528a48eb3b264f41450b0453 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 25 Jul 2019 11:14:45 -0400 Subject: [PATCH 35/36] address more feedback on PR --- libraries/chain/block_header_state.cpp | 4 ++-- .../include/eosio/chain/producer_schedule.hpp | 10 +++++----- libraries/chain/include/eosio/chain/types.hpp | 4 ++-- plugins/chain_plugin/chain_plugin.cpp | 10 +++++++++- plugins/producer_plugin/producer_plugin.cpp | 16 ++-------------- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 9771af57d42..aea87103283 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -424,11 +424,11 @@ namespace eosio { namespace chain { EOS_ASSERT(relevant_sig_count == keys.size(), wrong_signing_key, "block signed by unexpected key", - ("signing_keys", keys)("valid_keys", valid_block_signing_authority)); + ("signing_keys", keys)("authority", valid_block_signing_authority)); EOS_ASSERT(is_satisfied, wrong_signing_key, "block signatures do not satisfy the block signing authority", - ("keys", keys)("authority", valid_block_signing_authority)); + ("signing_keys", keys)("authority", valid_block_signing_authority)); } /** diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index fb12dab0d58..ec0d5bc79dd 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -104,7 +104,7 @@ namespace eosio { namespace chain { vector keys; template - void for_each_key( Op op ) const { + void for_each_key( Op&& op ) const { for (const auto& kw : keys ) { op(kw.key); } @@ -165,15 +165,15 @@ namespace eosio { namespace chain { block_signing_authority authority; template - static void for_each_key( const block_signing_authority& authority, Op op ) { + static void for_each_key( const block_signing_authority& authority, Op&& op ) { authority.visit([&op](const auto &a){ - a.for_each_key(op); + a.for_each_key(std::forward(op)); }); } template - void for_each_key( Op op ) const { - for_each_key(authority, op); + void for_each_key( Op&& op ) const { + for_each_key(authority, std::forward(op)); } static std::pair keys_satisfy_and_relevant( const std::set& keys, const block_signing_authority& authority ) { diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index daaf63c234b..81c8e8b8924 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -280,8 +280,8 @@ namespace eosio { namespace chain { * this assumes exts is already sorted by extension id */ inline auto emplace_extension( extensions_type& exts, uint16_t eid, vector&& data) { - auto insert_itr = std::lower_bound(exts.begin(), exts.end(), eid, [](const auto& ext, uint16_t id){ - return ext.first < id; + auto insert_itr = std::upper_bound(exts.begin(), exts.end(), eid, [](uint16_t id, const auto& ext){ + return id < ext.first; }); return exts.emplace(insert_itr, eid, std::move(data)); diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index c47889f5c48..73e998fa548 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1689,12 +1689,20 @@ read_only::get_producers_result read_only::get_producers( const read_only::get_p read_only::get_producers_result result; for (auto p : db.active_producers().producers) { - fc::variant row = fc::mutable_variant_object() + auto row = fc::mutable_variant_object() ("owner", p.producer_name) ("producer_authority", p.authority) ("url", "") ("total_votes", 0.0f); + // detect a legacy key and maintain API compatibility for those entries + if (p.authority.contains()) { + const auto& auth = p.authority.get(); + if (auth.keys.size() == 1 && auth.keys.back().weight == auth.threshold) { + row("producer_key", auth.keys.back().key); + } + } + result.rows.push_back(row); } diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 16fd8e3a362..22e0887d949 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -189,9 +189,7 @@ class producer_plugin_impl : public std::enable_shared_from_this _protocol_features_to_activate; bool _protocol_features_signaled = false; // to mark whether it has been signaled in start_block - time_point _last_signed_block_time; time_point _start_time = fc::time_point::now(); - uint32_t _last_signed_block_num = 0; producer_plugin* _self = nullptr; chain_plugin* chain_plug = nullptr; @@ -231,17 +229,7 @@ class producer_plugin_impl : public std::enable_shared_from_thisheader.timestamp <= _last_signed_block_time ) return; if( bsp->header.timestamp <= _start_time ) return; - if( bsp->block_num <= _last_signed_block_num ) return; - - const auto& active_producer_to_signing_key = bsp->active_schedule.producers; - - flat_set active_producers; - active_producers.reserve(bsp->active_schedule.producers.size()); - for (const auto& p: bsp->active_schedule.producers) { - active_producers.insert(p.producer_name); - } // simplify handling of watermark in on_block auto block_producer = bsp->header.producer; @@ -1412,11 +1400,11 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { if( chain.is_building_block() ) { auto pending_block_time = chain.pending_block_time(); - auto pending_block_signing_authority = chain.pending_block_signing_authority(); + const auto& pending_block_signing_authority = chain.pending_block_signing_authority(); const fc::time_point preprocess_deadline = calculate_block_deadline(block_time); if (_pending_block_mode == pending_block_mode::producing && pending_block_signing_authority != scheduled_producer.authority) { - elog("Block Signing Key is not expected value, reverting to speculative mode! [expected: \"${expected}\", actual: \"${actual\"", ("expected", scheduled_producer.authority)("actual", pending_block_signing_authority)); + elog("Unexpected block signing authority, reverting to speculative mode! [expected: \"${expected}\", actual: \"${actual\"", ("expected", scheduled_producer.authority)("actual", pending_block_signing_authority)); _pending_block_mode = pending_block_mode::speculating; } From 5e0dc6d7062d415f351516782586b117b00ac912 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 26 Jul 2019 12:03:16 -0400 Subject: [PATCH 36/36] address more PR feedback refactored the wtmsig tests so that they are only testing that those authorities work. added a utility to produce blocks (with a limit) until a set of BPs has been seen --- .../state_history_plugin_abi.cpp | 2 +- unittests/fork_test_utilities.cpp | 47 +++++--- unittests/fork_test_utilities.hpp | 12 +- unittests/forked_tests.cpp | 20 +-- unittests/producer_schedule_tests.cpp | 114 +++--------------- unittests/protocol_feature_tests.cpp | 7 +- 6 files changed, 73 insertions(+), 129 deletions(-) diff --git a/plugins/state_history_plugin/state_history_plugin_abi.cpp b/plugins/state_history_plugin/state_history_plugin_abi.cpp index 338dc867852..541dc4a6262 100644 --- a/plugins/state_history_plugin/state_history_plugin_abi.cpp +++ b/plugins/state_history_plugin/state_history_plugin_abi.cpp @@ -313,7 +313,7 @@ extern const char* const state_history_plugin_abi = R"({ }, { "name": "block_signing_authority_v0", "fields": [ - { "type": "uint32", "name": "weight" }, + { "type": "uint32", "name": "threshold" }, { "type": "key_weight[]", "name": "keys" } ] }, diff --git a/unittests/fork_test_utilities.cpp b/unittests/fork_test_utilities.cpp index d63404de983..55e4fdf2d4e 100644 --- a/unittests/fork_test_utilities.cpp +++ b/unittests/fork_test_utilities.cpp @@ -21,22 +21,39 @@ void push_blocks( tester& from, tester& to, uint32_t block_num_limit ) { } } -bool produce_empty_blocks_until( tester& t, - account_name last_producer, - account_name next_producer, - uint32_t max_num_blocks_to_produce ) -{ - auto condition_satisfied = [&t, last_producer, next_producer]() { - return t.control->pending_block_producer() == next_producer && t.control->head_block_producer() == last_producer; - }; +namespace { + template + bool produce_empty_blocks_until( base_tester& t, uint32_t max_num_blocks_to_produce, Pred&& pred) { + for( uint32_t blocks_produced = 0; + blocks_produced < max_num_blocks_to_produce; + t.produce_block(), ++blocks_produced ) + { + if( pred() ) + return true; + } - for( uint32_t blocks_produced = 0; - blocks_produced < max_num_blocks_to_produce; - t.produce_block(), ++blocks_produced ) - { - if( condition_satisfied() ) - return true; + return pred(); } +} + +bool produce_until_transition( base_tester& t, + account_name last_producer, + account_name next_producer, + uint32_t max_num_blocks_to_produce ) +{ + return produce_empty_blocks_until(t, max_num_blocks_to_produce, [&t, last_producer, next_producer]() { + return t.control->pending_block_producer() == next_producer && t.control->head_block_producer() == last_producer; + }); +} - return condition_satisfied(); +bool produce_until_blocks_from( base_tester& t, + const std::set& expected_producers, + uint32_t max_num_blocks_to_produce ) +{ + auto remaining_producers = expected_producers; + return produce_empty_blocks_until(t, max_num_blocks_to_produce, [&t, &remaining_producers]() { + remaining_producers.erase(t.control->head_block_producer()); + return remaining_producers.size() == 0; + }); } + diff --git a/unittests/fork_test_utilities.hpp b/unittests/fork_test_utilities.hpp index f5ae33ae718..0739ffc9378 100644 --- a/unittests/fork_test_utilities.hpp +++ b/unittests/fork_test_utilities.hpp @@ -15,7 +15,11 @@ public_key_type get_public_key( name keyname, string role ); void push_blocks( tester& from, tester& to, uint32_t block_num_limit = std::numeric_limits::max() ); -bool produce_empty_blocks_until( tester& t, - account_name last_producer, - account_name next_producer, - uint32_t max_num_blocks_to_produce = std::numeric_limits::max() ); +bool produce_until_transition( base_tester& t, + account_name last_producer, + account_name next_producer, + uint32_t max_num_blocks_to_produce = std::numeric_limits::max() ); + +bool produce_until_blocks_from( base_tester& t, + const std::set& expected_producers, + uint32_t max_num_blocks_to_produce = std::numeric_limits::max() ); diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index 902d09a62e8..ece1fb09329 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -49,7 +49,7 @@ BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { auto res = bios.set_producers( {N(a),N(b),N(c),N(d),N(e)} ); // run until the producers are installed and its the start of "a's" round - BOOST_REQUIRE( produce_empty_blocks_until( bios, N(e), N(a) ) ); + BOOST_REQUIRE( produce_until_transition( bios, N(e), N(a) ) ); // sync remote node tester remote(setup_policy::none); @@ -420,14 +420,14 @@ BOOST_AUTO_TEST_CASE( irreversible_mode ) try { main.produce_block(); main.set_producers( {N(producer1), N(producer2)} ); main.produce_block(); - BOOST_REQUIRE( produce_empty_blocks_until( main, N(producer1), N(producer2), 26) ); + BOOST_REQUIRE( produce_until_transition( main, N(producer1), N(producer2), 26) ); main.create_accounts( {N(alice)} ); main.produce_block(); auto hbn1 = main.control->head_block_num(); auto lib1 = main.control->last_irreversible_block_num(); - BOOST_REQUIRE( produce_empty_blocks_until( main, N(producer2), N(producer1), 11) ); + BOOST_REQUIRE( produce_until_transition( main, N(producer2), N(producer1), 11) ); auto hbn2 = main.control->head_block_num(); auto lib2 = main.control->last_irreversible_block_num(); @@ -439,8 +439,8 @@ BOOST_AUTO_TEST_CASE( irreversible_mode ) try { push_blocks( main, other ); BOOST_CHECK_EQUAL( other.control->head_block_num(), hbn2 ); - BOOST_REQUIRE( produce_empty_blocks_until( main, N(producer1), N(producer2), 12) ); - BOOST_REQUIRE( produce_empty_blocks_until( main, N(producer2), N(producer1), 12) ); + BOOST_REQUIRE( produce_until_transition( main, N(producer1), N(producer2), 12) ); + BOOST_REQUIRE( produce_until_transition( main, N(producer2), N(producer1), 12) ); auto hbn3 = main.control->head_block_num(); auto lib3 = main.control->last_irreversible_block_num(); @@ -457,15 +457,15 @@ BOOST_AUTO_TEST_CASE( irreversible_mode ) try { auto fork_first_block_id = other.control->head_block_id(); wlog( "{w}", ("w", fork_first_block_id)); - BOOST_REQUIRE( produce_empty_blocks_until( other, N(producer2), N(producer1), 11) ); // finish producer2's round + BOOST_REQUIRE( produce_until_transition( other, N(producer2), N(producer1), 11) ); // finish producer2's round BOOST_REQUIRE_EQUAL( other.control->pending_block_producer().to_string(), "producer1" ); // Repeat two more times to ensure other has a longer chain than main other.produce_block( fc::milliseconds( 13 * config::block_interval_ms ) ); // skip over producer1's round - BOOST_REQUIRE( produce_empty_blocks_until( other, N(producer2), N(producer1), 11) ); // finish producer2's round + BOOST_REQUIRE( produce_until_transition( other, N(producer2), N(producer1), 11) ); // finish producer2's round other.produce_block( fc::milliseconds( 13 * config::block_interval_ms ) ); // skip over producer1's round - BOOST_REQUIRE( produce_empty_blocks_until( other, N(producer2), N(producer1), 11) ); // finish producer2's round + BOOST_REQUIRE( produce_until_transition( other, N(producer2), N(producer1), 11) ); // finish producer2's round auto hbn4 = other.control->head_block_num(); auto lib4 = other.control->last_irreversible_block_num(); @@ -529,9 +529,9 @@ BOOST_AUTO_TEST_CASE( reopen_forkdb ) try { BOOST_REQUIRE_EQUAL( c1.control->active_producers().version, 1u ); - produce_empty_blocks_until( c1, N(carol), N(alice) ); + produce_until_transition( c1, N(carol), N(alice) ); c1.produce_block(); - produce_empty_blocks_until( c1, N(carol), N(alice) ); + produce_until_transition( c1, N(carol), N(alice) ); tester c2(setup_policy::none); diff --git a/unittests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp index a9d7ed82faf..678d2a50c28 100644 --- a/unittests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -437,9 +437,9 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { BOOST_REQUIRE_EQUAL( c.control->active_producers().version, 1u ); BOOST_CHECK_EQUAL( true, compare_schedules( sch1, c.control->active_producers() ) ); - produce_empty_blocks_until( c, N(carol), N(alice) ); + produce_until_transition( c, N(carol), N(alice) ); c.produce_block(); - produce_empty_blocks_until( c, N(carol), N(alice) ); + produce_until_transition( c, N(carol), N(alice) ); res = c.set_producers( {N(alice),N(bob)} ); vector sch2 = { @@ -450,16 +450,16 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() ); BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *c.control->proposed_producers() ) ); - produce_empty_blocks_until( c, N(bob), N(carol) ); - produce_empty_blocks_until( c, N(alice), N(bob) ); + produce_until_transition( c, N(bob), N(carol) ); + produce_until_transition( c, N(alice), N(bob) ); BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u ); BOOST_CHECK_EQUAL( c.control->active_producers().version, 1u ); - produce_empty_blocks_until( c, N(carol), N(alice) ); + produce_until_transition( c, N(carol), N(alice) ); BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u ); BOOST_CHECK_EQUAL( c.control->active_producers().version, 1u ); - produce_empty_blocks_until( c, N(bob), N(carol) ); + produce_until_transition( c, N(bob), N(carol) ); BOOST_CHECK_EQUAL( c.control->pending_block_producer(), N(carol) ); BOOST_REQUIRE_EQUAL( c.control->active_producers().version, 2u ); @@ -474,15 +474,15 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() ); BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *c.control->proposed_producers() ) ); - produce_empty_blocks_until( c, N(bob), N(alice) ); - produce_empty_blocks_until( c, N(alice), N(bob) ); + produce_until_transition( c, N(bob), N(alice) ); + produce_until_transition( c, N(alice), N(bob) ); BOOST_CHECK_EQUAL( c.control->pending_producers().version, 3u ); BOOST_REQUIRE_EQUAL( c.control->active_producers().version, 2u ); - produce_empty_blocks_until( c, N(bob), N(alice) ); + produce_until_transition( c, N(bob), N(alice) ); BOOST_REQUIRE_EQUAL( c.control->active_producers().version, 3u ); - produce_empty_blocks_until( c, N(alice), N(bob) ); + produce_until_transition( c, N(alice), N(bob) ); c.produce_blocks(11); BOOST_CHECK_EQUAL( c.control->pending_block_producer(), N(bob) ); c.finish_block(); @@ -499,19 +499,13 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { BOOST_CHECK_EQUAL( h.producer, N(carol) ); BOOST_CHECK_EQUAL( h.confirmed, confirmed ); - produce_empty_blocks_until( c, N(carol), N(alice) ); + produce_until_transition( c, N(carol), N(alice) ); } FC_LOG_AND_RETHROW() BOOST_FIXTURE_TEST_CASE( producer_one_of_n_test, TESTER ) try { create_accounts( {N(alice),N(bob)} ); - while (control->head_block_num() < 3) { - produce_block(); - } - - auto compare_schedules = [&]( const vector& a, const producer_authority_schedule& b ) { - return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); - }; + produce_block(); vector sch1 = { producer_authority{N(alice), block_signing_authority_v0{1, {{get_public_key(N(alice), "bs1"), 1}, {get_public_key(N(alice), "bs2"), 1}}}}, @@ -522,50 +516,15 @@ BOOST_FIXTURE_TEST_CASE( producer_one_of_n_test, TESTER ) try { block_signing_private_keys.emplace(get_public_key(N(alice), "bs1"), get_private_key(N(alice), "bs1")); block_signing_private_keys.emplace(get_public_key(N(bob), "bs1"), get_private_key(N(bob), "bs1")); - //wdump((fc::json::to_pretty_string(res))); - wlog("set producer schedule to [alice,bob]"); - BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *control->proposed_producers() ) ); - BOOST_CHECK_EQUAL( control->pending_producers().version, 0u ); - produce_block(); // Starts new block which promotes the proposed schedule to pending - BOOST_CHECK_EQUAL( control->pending_producers().version, 1u ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->pending_producers() ) ); - BOOST_CHECK_EQUAL( control->active_producers().version, 0u ); - produce_block(); - produce_block(); // Starts new block which promotes the pending schedule to active - BOOST_CHECK_EQUAL( control->active_producers().version, 1u ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->active_producers() ) ); - produce_blocks(18); - - // fast forward until the first block of alices round - BOOST_REQUIRE_EQUAL( control->head_block_producer(), N(bob) ); - BOOST_REQUIRE_EQUAL( control->pending_block_producer(), N(alice) ); - - // produce with backup key by listing that in block signing keys - block_signing_private_keys.clear(); - block_signing_private_keys.emplace(get_public_key(N(alice), "bs2"), get_private_key(N(alice), "bs2")); - block_signing_private_keys.emplace(get_public_key(N(bob), "bs2"), get_private_key(N(bob), "bs2")); - - produce_blocks(config::producer_repetitions, false); - - // check that its bobs turn - BOOST_REQUIRE_EQUAL( control->head_block_producer(), N(alice) ); - BOOST_REQUIRE_EQUAL( control->pending_block_producer(), N(bob) ); - - produce_blocks(config::producer_repetitions, false); + BOOST_REQUIRE(produce_until_blocks_from(*this, {N(alice), N(bob)}, 300)); BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() BOOST_FIXTURE_TEST_CASE( producer_m_of_n_test, TESTER ) try { create_accounts( {N(alice),N(bob)} ); - while (control->head_block_num() < 3) { - produce_block(); - } + produce_block(); - auto compare_schedules = [&]( const vector& a, const producer_authority_schedule& b ) { - return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); - }; vector sch1 = { producer_authority{N(alice), block_signing_authority_v0{2, {{get_public_key(N(alice), "bs1"), 1}, {get_public_key(N(alice), "bs2"), 1}}}}, @@ -578,45 +537,14 @@ BOOST_FIXTURE_TEST_CASE( producer_m_of_n_test, TESTER ) try { block_signing_private_keys.emplace(get_public_key(N(bob), "bs1"), get_private_key(N(bob), "bs1")); block_signing_private_keys.emplace(get_public_key(N(bob), "bs2"), get_private_key(N(bob), "bs2")); - //wdump((fc::json::to_pretty_string(res))); - wlog("set producer schedule to [alice,bob]"); - BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *control->proposed_producers() ) ); - BOOST_CHECK_EQUAL( control->pending_producers().version, 0u ); - produce_block(); // Starts new block which promotes the proposed schedule to pending - BOOST_CHECK_EQUAL( control->pending_producers().version, 1u ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->pending_producers() ) ); - BOOST_CHECK_EQUAL( control->active_producers().version, 0u ); - produce_block(); - produce_block(); // Starts new block which promotes the pending schedule to active - BOOST_CHECK_EQUAL( control->active_producers().version, 1u ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->active_producers() ) ); - produce_blocks(18); - - // fast forward until the first block of alices round - BOOST_REQUIRE_EQUAL( control->head_block_producer(), N(bob) ); - BOOST_REQUIRE_EQUAL( control->pending_block_producer(), N(alice) ); - - BOOST_REQUIRE_EQUAL( control->head_block_state()->additional_signatures.size(), 1); - - produce_blocks(config::producer_repetitions, false); - - BOOST_REQUIRE_EQUAL( control->head_block_state()->additional_signatures.size(), 1); - - // check that its bobs turn - BOOST_REQUIRE_EQUAL( control->head_block_producer(), N(alice) ); - BOOST_REQUIRE_EQUAL( control->pending_block_producer(), N(bob) ); - - produce_blocks(config::producer_repetitions, false); + BOOST_REQUIRE(produce_until_blocks_from(*this, {N(alice), N(bob)}, 300)); BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() BOOST_FIXTURE_TEST_CASE( satisfiable_msig_test, TESTER ) try { create_accounts( {N(alice),N(bob)} ); - while (control->head_block_num() < 3) { - produce_block(); - } + produce_block(); vector sch1 = { producer_authority{N(alice), block_signing_authority_v0{2, {{get_public_key(N(alice), "bs1"), 1}}}} @@ -633,10 +561,8 @@ BOOST_FIXTURE_TEST_CASE( satisfiable_msig_test, TESTER ) try { } FC_LOG_AND_RETHROW() BOOST_FIXTURE_TEST_CASE( duplicate_producers_test, TESTER ) try { - create_accounts( {N(alice),N(bob)} ); - while (control->head_block_num() < 3) { - produce_block(); - } + create_accounts( {N(alice)} ); + produce_block(); vector sch1 = { producer_authority{N(alice), block_signing_authority_v0{1, {{get_public_key(N(alice), "bs1"), 1}}}}, @@ -655,9 +581,7 @@ BOOST_FIXTURE_TEST_CASE( duplicate_producers_test, TESTER ) try { BOOST_FIXTURE_TEST_CASE( duplicate_keys_test, TESTER ) try { create_accounts( {N(alice),N(bob)} ); - while (control->head_block_num() < 3) { - produce_block(); - } + produce_block(); vector sch1 = { producer_authority{N(alice), block_signing_authority_v0{2, {{get_public_key(N(alice), "bs1"), 1}, {get_public_key(N(alice), "bs1"), 1}}}} diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp index 499bffa84f4..730f58bb227 100644 --- a/unittests/protocol_feature_tests.cpp +++ b/unittests/protocol_feature_tests.cpp @@ -53,8 +53,7 @@ BOOST_AUTO_TEST_CASE( activate_preactivate_feature ) try { c.produce_block(); // Now the latest bios contract can be set. - c.set_code( config::system_account_name, contracts::before_producer_authority_eosio_bios_wasm() ); - c.set_abi( config::system_account_name, contracts::before_producer_authority_eosio_bios_abi().data() ); + c.set_before_producer_authority_bios_contract(); c.produce_block(); @@ -769,7 +768,7 @@ BOOST_AUTO_TEST_CASE( disallow_empty_producer_schedule_test ) { try { // After activation, it should not be allowed c.preactivate_protocol_features( {*d} ); c.produce_block(); - BOOST_REQUIRE_EXCEPTION( c.set_producers( {} ), + BOOST_REQUIRE_EXCEPTION( c.set_producers_legacy( {} ), wasm_execution_error, fc_exception_message_is( "Producer schedule cannot be empty" ) ); @@ -1666,7 +1665,7 @@ BOOST_AUTO_TEST_CASE( producer_schedule_change_extension_test ) { try { auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state()->pending_schedule.schedule_hash) ); bad_block->producer_signature = remote.get_private_key(N(eosio), "active").sign(sig_digest); - // ensure it is accepted (but rejected because it doesn't match expected state) + // ensure it is rejected because the new_producers field is not null BOOST_REQUIRE_EXCEPTION( remote.push_block(bad_block), producer_schedule_exception, fc_exception_message_is( "Block header contains legacy producer schedule outdated by activation of WTMsig Block Signatures" )