diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 2c430fecea0..15e8bfb0802 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -41,12 +41,16 @@ add_library( eosio_chain webassembly/wabt.cpp # get_config.cpp -# global_property_object.cpp # # contracts/chain_initializer.cpp transaction_metadata.cpp + protocol_state_object.cpp + protocol_feature_activation.cpp + protocol_feature_manager.cpp + genesis_intrinsics.cpp + whitelisted_intrinsics.cpp ${HEADERS} ) diff --git a/libraries/chain/block_header.cpp b/libraries/chain/block_header.cpp index d623406fd25..692089dc9e6 100644 --- a/libraries/chain/block_header.cpp +++ b/libraries/chain/block_header.cpp @@ -28,5 +28,44 @@ namespace eosio { namespace chain { return result; } + vector 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; + + static_assert( std::is_same::value, + "block_header_extensions is not setup as expected" ); + + vector results; + + uint16_t id_type_lower_bound = 0; + + for( size_t i = 0; i < header_extensions.size(); ++i ) { + const auto& e = header_extensions[i]; + auto id = e.first; + + EOS_ASSERT( id >= id_type_lower_bound, invalid_block_header_extension, + "Block header extensions are not in the correct order (ascending id types required)" + ); + + results.emplace_back(); + + auto match = decompose_t::extract( id, e.second, results.back() ); + EOS_ASSERT( match, invalid_block_header_extension, + "Block header 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 header extension with id type ${id} is not allowed to repeat", + ("id", id) + ); + } + + id_type_lower_bound = id; + } + + return results; + } } } diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index e60df03f7eb..f7dd7aba656 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -23,9 +23,10 @@ namespace eosio { namespace chain { /// 2/3 must be greater, so if I go 1/3 into the list sorted from low to high, then 2/3 are greater if( blocknums.size() == 0 ) return 0; - /// TODO: update to nth_element - std::sort( blocknums.begin(), blocknums.end() ); - return blocknums[ (blocknums.size()-1) / 3 ]; + + std::size_t index = (blocknums.size()-1) / 3; + std::nth_element( blocknums.begin(), blocknums.begin() + index, blocknums.end() ); + return blocknums[ index ]; } pending_block_header_state block_header_state::next( block_timestamp_type when, @@ -54,6 +55,7 @@ namespace eosio { namespace chain { result.timestamp = when; result.confirmed = num_prev_blocks_to_confirm; result.active_schedule_version = active_schedule.version; + result.prev_activated_protocol_features = activated_protocol_features; result.block_signing_key = prokey.block_signing_key; result.producer = prokey.producer_name; @@ -129,6 +131,7 @@ namespace eosio { namespace chain { } } } + new_producer_to_last_produced[prokey.producer_name] = result.block_num; result.producer_to_last_produced = std::move( new_producer_to_last_produced ); @@ -161,9 +164,12 @@ namespace eosio { namespace chain { return result; } - signed_block_header pending_block_header_state::make_block_header( const checksum256_type& transaction_mroot, - const checksum256_type& action_mroot, - optional&& new_producers )const + 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 + )const { signed_block_header h; @@ -176,10 +182,22 @@ namespace eosio { namespace chain { 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) } ) + ); + } + return h; } - block_header_state pending_block_header_state::_finish_next( const signed_block_header& h )&& + block_header_state pending_block_header_state::_finish_next( + const signed_block_header& h, + const std::function&, + const vector& )>& validator + )&& { EOS_ASSERT( h.timestamp == timestamp, block_validate_exception, "timestamp mismatch" ); EOS_ASSERT( h.previous == previous, unlinkable_block_exception, "previous mismatch" ); @@ -187,8 +205,6 @@ 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" ); - EOS_ASSERT( h.header_extensions.size() == 0, block_validate_exception, "no supported extensions" ); - if( h.new_producers ) { 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" ); @@ -196,6 +212,23 @@ namespace eosio { namespace chain { "cannot set new pending producers until last pending is confirmed" ); } + protocol_feature_activation_set_ptr new_activated_protocol_features; + + auto exts = h.validate_and_extract_header_extensions(); + { + if( exts.size() > 0 ) { + const auto& new_protocol_features = exts.front().get().protocol_features; + validator( timestamp, prev_activated_protocol_features->protocol_features, new_protocol_features ); + + new_activated_protocol_features = std::make_shared( + *prev_activated_protocol_features, + new_protocol_features + ); + } else { + new_activated_protocol_features = std::move( prev_activated_protocol_features ); + } + } + auto block_number = block_num; block_header_state result( std::move( *static_cast(this) ) ); @@ -203,9 +236,11 @@ namespace eosio { namespace chain { result.id = h.id(); result.header = h; + 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( result.pending_schedule ); + result.pending_schedule.schedule_hash = digest_type::hash( *h.new_producers ); result.pending_schedule.schedule_lib_num = block_number; } else { if( was_pending_promoted ) { @@ -217,13 +252,20 @@ namespace eosio { namespace chain { result.pending_schedule.schedule_lib_num = prev_pending_schedule.schedule_lib_num; } + result.activated_protocol_features = std::move( new_activated_protocol_features ); + return result; } - block_header_state pending_block_header_state::finish_next( const signed_block_header& h, - bool skip_validate_signee )&& + block_header_state pending_block_header_state::finish_next( + const signed_block_header& h, + const std::function&, + const vector& )>& validator, + bool skip_validate_signee + )&& { - auto result = std::move(*this)._finish_next( h ); + auto result = std::move(*this)._finish_next( h, validator ); // ASSUMPTION FROM controller_impl::apply_block = all untrusted blocks will have their signatures pre-validated here if( !skip_validate_signee ) { @@ -233,10 +275,15 @@ namespace eosio { namespace chain { return result; } - block_header_state pending_block_header_state::finish_next( signed_block_header& h, - const std::function& signer )&& + block_header_state pending_block_header_state::finish_next( + signed_block_header& h, + const std::function&, + const vector& )>& validator, + const std::function& signer + )&& { - auto result = std::move(*this)._finish_next( h ); + auto result = std::move(*this)._finish_next( h, validator ); result.sign( signer ); h.producer_signature = result.header.producer_signature; return result; @@ -250,28 +297,48 @@ namespace eosio { namespace chain { * * If the header specifies new_producers then apply them accordingly. */ - block_header_state block_header_state::next( const signed_block_header& h, bool skip_validate_signee )const { - return next( h.timestamp, h.confirmed ).finish_next( h, skip_validate_signee ); + block_header_state block_header_state::next( + const signed_block_header& h, + const std::function&, + const vector& )>& validator, + bool skip_validate_signee )const + { + return next( h.timestamp, h.confirmed ).finish_next( h, validator, skip_validate_signee ); } - digest_type block_header_state::sig_digest()const { - auto header_bmroot = digest_type::hash( std::make_pair( header.digest(), blockroot_merkle.get_root() ) ); - return digest_type::hash( std::make_pair(header_bmroot, pending_schedule.schedule_hash) ); - } - - 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 ), wrong_signing_key, "block is signed with unexpected key" ); - } - - public_key_type block_header_state::signee()const { - 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, "block not signed by expected key", - ("block_signing_key", block_signing_key)( "signee", signee ) ); - } + digest_type block_header_state::sig_digest()const { + auto header_bmroot = digest_type::hash( std::make_pair( header.digest(), blockroot_merkle.get_root() ) ); + return digest_type::hash( std::make_pair(header_bmroot, pending_schedule.schedule_hash) ); + } + + 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 ), + wrong_signing_key, "block is signed with unexpected key" ); + } + + public_key_type block_header_state::signee()const { + 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, + "block not signed by expected key", + ("block_signing_key", block_signing_key)( "signee", signee ) ); + } + + /** + * Reference cannot outlive *this. Assumes header_exts is not mutated after instatiation. + */ + 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() ) + return no_activations; + + return header_exts.front().get().protocol_features; + } } } /// namespace eosio::chain diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index ecd7fceefc8..3a246038149 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -5,18 +5,24 @@ namespace eosio { namespace chain { block_state::block_state( const block_header_state& prev, signed_block_ptr b, + const std::function&, + const vector& )>& validator, bool skip_validate_signee ) - :block_header_state( prev.next( *b, skip_validate_signee ) ) + :block_header_state( prev.next( *b, 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 std::function&, + const vector& )>& validator, const std::function& signer ) - :block_header_state( std::move(cur).finish_next( *b, signer ) ) + :block_header_state( std::move(cur).finish_next( *b, validator, signer ) ) ,block( std::move(b) ) ,trxs( std::move(trx_metas) ) {} @@ -25,9 +31,12 @@ namespace eosio { namespace chain { block_state::block_state( pending_block_header_state&& cur, const signed_block_ptr& b, vector&& trx_metas, + const std::function&, + const vector& )>& validator, bool skip_validate_signee ) - :block_header_state( std::move(cur).finish_next( *b, skip_validate_signee ) ) + :block_header_state( std::move(cur).finish_next( *b, validator, skip_validate_signee ) ) ,block( b ) ,trxs( std::move(trx_metas) ) {} diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 79f17cdec5a..279964752e1 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -9,11 +9,15 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include #include #include #include @@ -32,6 +36,7 @@ using controller_index_set = index_set< account_index, account_sequence_index, global_property_multi_index, + protocol_state_multi_index, dynamic_global_property_multi_index, block_summary_multi_index, transaction_multi_index, @@ -94,12 +99,18 @@ class maybe_session { }; struct building_block { - building_block( const block_header_state& prev, block_timestamp_type when, uint16_t num_prev_blocks_to_confirm ) + building_block( const block_header_state& prev, + block_timestamp_type when, + uint16_t num_prev_blocks_to_confirm, + const vector& new_protocol_feature_activations ) :_pending_block_header_state( prev.next( when, num_prev_blocks_to_confirm ) ) + ,_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; @@ -120,9 +131,11 @@ using block_stage_type = fc::static_variant& new_protocol_feature_activations ) :_db_session( move(s) ) - ,_block_stage( building_block( prev, when, num_prev_blocks_to_confirm ) ) + ,_block_stage( building_block( prev, when, num_prev_blocks_to_confirm, new_protocol_feature_activations ) ) {} maybe_session _db_session; @@ -158,6 +171,32 @@ struct pending_state { return _block_stage.get()._block_state->trxs; } + bool is_protocol_feature_activated( const digest_type& feature_digest )const { + if( _block_stage.contains() ) { + auto& bb = _block_stage.get(); + const auto& activated_features = bb._pending_block_header_state.prev_activated_protocol_features->protocol_features; + + if( activated_features.find( feature_digest ) != activated_features.end() ) return true; + + if( bb._num_new_protocol_features_that_have_activated == 0 ) return false; + + auto end = bb._new_protocol_feature_activations.begin() + bb._num_new_protocol_features_that_have_activated; + return (std::find( bb._new_protocol_feature_activations.begin(), end, feature_digest ) != end); + } + + if( _block_stage.contains() ) { + // Calling is_protocol_feature_activated during the assembled_block stage is not efficient. + // We should avoid doing it. + // In fact for now it isn't even implemented. + EOS_THROW( misc_exception, + "checking if protocol feature is activated in the assembled_block stage is not yet supported" ); + // TODO: implement this + } + + const auto& activated_features = _block_stage.get()._block_state->activated_protocol_features->protocol_features; + return (activated_features.find( feature_digest ) != activated_features.end()); + } + void push() { _db_session.push(); } @@ -174,6 +213,7 @@ struct controller_impl { wasm_interface wasmif; resource_limits_manager resource_limits; authorization_manager authorization; + protocol_feature_manager protocol_features; controller::config conf; chain_id_type chain_id; optional replay_head_time; @@ -186,6 +226,7 @@ struct controller_impl { typedef pair handler_key; map< account_name, map > apply_handlers; + unordered_map< builtin_protocol_feature_t, std::function, enum_hash > protocol_feature_activation_handlers; /** * Transactions that were undone by pop_block or abort_block, transactions @@ -215,14 +256,30 @@ struct controller_impl { head = prev; db.undo(); + + protocol_features.popped_blocks_to( prev->block_num ); + } + + template + void on_activation(); + + template + inline void set_activation_handler() { + auto res = protocol_feature_activation_handlers.emplace( F, &controller_impl::on_activation ); + EOS_ASSERT( res.second, misc_exception, "attempting to set activation handler twice" ); } + inline void trigger_activation_handler( builtin_protocol_feature_t f ) { + auto itr = protocol_feature_activation_handlers.find( f ); + if( itr == protocol_feature_activation_handlers.end() ) return; + (itr->second)( *this ); + } void set_apply_handler( account_name receiver, account_name contract, action_name action, apply_handler v ) { apply_handlers[receiver][make_pair(contract,action)] = v; } - controller_impl( const controller::config& cfg, controller& s ) + controller_impl( const controller::config& cfg, controller& s, protocol_feature_set&& pfs ) :self(s), db( cfg.state_dir, cfg.read_only ? database::read_only : database::read_write, @@ -235,12 +292,22 @@ struct controller_impl { wasmif( cfg.wasm_runtime ), resource_limits( db ), authorization( s, db ), + protocol_features( std::move(pfs) ), conf( cfg ), chain_id( cfg.genesis.compute_chain_id() ), read_mode( cfg.read_mode ), thread_pool( cfg.thread_pool_size ) { + fork_db.open( [this]( block_timestamp_type timestamp, + const flat_set& cur_features, + const vector& new_features ) + { check_protocol_features( timestamp, cur_features, new_features ); } + ); + + set_activation_handler(); + + #define SET_APP_HANDLER( receiver, contract, action) \ set_apply_handler( #receiver, #contract, #action, &BOOST_PP_CAT(apply_, BOOST_PP_CAT(contract, BOOST_PP_CAT(_,action) ) ) ) @@ -313,7 +380,7 @@ struct controller_impl { for( auto bitr = branch.rbegin(); bitr != branch.rend(); ++bitr ) { if( read_mode == db_read_mode::IRREVERSIBLE ) { - apply_block( (*bitr)->block, controller::block_status::complete ); + apply_block( *bitr, controller::block_status::complete ); head = (*bitr); fork_db.mark_valid( head ); } @@ -363,6 +430,7 @@ struct controller_impl { head = std::make_shared(); static_cast(*head) = genheader; + head->activated_protocol_features = std::make_shared(); head->block = std::make_shared(genheader.header); db.set_revision( head->block_num ); initialize_database(); @@ -505,6 +573,8 @@ struct controller_impl { db.undo(); } + protocol_features.init( db ); + const auto& rbi = reversible_blocks.get_index(); auto last_block_num = lib_num; @@ -806,13 +876,20 @@ struct controller_impl { const auto& tapos_block_summary = db.get(1); db.modify( tapos_block_summary, [&]( auto& bs ) { - bs.block_id = head->id; + bs.block_id = head->id; }); conf.genesis.initial_configuration.validate(); db.create([&](auto& gpo ){ - gpo.configuration = conf.genesis.initial_configuration; + gpo.configuration = conf.genesis.initial_configuration; + }); + + db.create([&](auto& pso ){ + for( const auto& i : genesis_intrinsics ) { + add_intrinsic_to_whitelist( pso.whitelisted_intrinsics, i ); + } }); + db.create([](auto&){}); authorization.initialize_database(); @@ -898,6 +975,8 @@ struct controller_impl { trx_context.squash(); restore.cancel(); return trace; + } catch( const protocol_feature_bad_block_exception& ) { + throw; } catch( const fc::exception& e ) { cpu_time_to_bill_us = trx_context.update_billed_cpu_time( fc::time_point::now() ); trace->except = e; @@ -1030,6 +1109,8 @@ struct controller_impl { restore.cancel(); return trace; + } catch( const protocol_feature_bad_block_exception& ) { + throw; } catch( const fc::exception& e ) { cpu_time_to_bill_us = trx_context.update_billed_cpu_time( fc::time_point::now() ); trace->except = e; @@ -1234,13 +1315,16 @@ struct controller_impl { } FC_CAPTURE_AND_RETHROW((trace)) } /// push_transaction - - void start_block( block_timestamp_type when, uint16_t confirm_block_count, controller::block_status s, + void start_block( block_timestamp_type when, + uint16_t confirm_block_count, + const vector& new_protocol_feature_activations, + controller::block_status s, const optional& producer_block_id ) { EOS_ASSERT( !pending, block_validate_exception, "pending block already exists" ); - auto guard_pending = fc::make_scoped_exit([this](){ + auto guard_pending = fc::make_scoped_exit([this, head_block_num=head->block_num](){ + protocol_features.popped_blocks_to( head_block_num ); pending.reset(); }); @@ -1248,20 +1332,88 @@ struct controller_impl { EOS_ASSERT( db.revision() == head->block_num, database_exception, "db revision is not on par with head block", ("db.revision()", db.revision())("controller_head_block", head->block_num)("fork_db_head_block", fork_db.head()->block_num) ); - pending.emplace( maybe_session(db), *head, when, confirm_block_count ); + pending.emplace( maybe_session(db), *head, when, confirm_block_count, new_protocol_feature_activations ); } else { - pending.emplace( maybe_session(), *head, when, confirm_block_count ); + pending.emplace( maybe_session(), *head, when, confirm_block_count, new_protocol_feature_activations ); } pending->_block_status = s; pending->_producer_block_id = producer_block_id; - const auto& pbhs = pending->get_pending_block_header_state(); + auto& bb = pending->_block_stage.get(); + const auto& pbhs = bb._pending_block_header_state; - //modify state in speculative block only if we are speculative reads mode (other wise we need clean state for head or irreversible reads) + // modify state of speculative block only if we are in speculative read mode (otherwise we need clean state for head or read-only modes) if ( read_mode == db_read_mode::SPECULATIVE || pending->_block_status != controller::block_status::incomplete ) { + const auto& pso = db.get(); + + auto num_preactivated_protocol_features = pso.preactivated_protocol_features.size(); + bool handled_all_preactivated_features = (num_preactivated_protocol_features == 0); + + if( new_protocol_feature_activations.size() > 0 ) { + flat_map activated_protocol_features; + activated_protocol_features.reserve( std::max( num_preactivated_protocol_features, + new_protocol_feature_activations.size() ) ); + for( const auto& feature_digest : pso.preactivated_protocol_features ) { + activated_protocol_features.emplace( feature_digest, false ); + } + + size_t num_preactivated_features_that_have_activated = 0; + + const auto& pfs = protocol_features.get_protocol_feature_set(); + for( const auto& feature_digest : new_protocol_feature_activations ) { + const auto& f = pfs.get_protocol_feature( feature_digest ); + + auto res = activated_protocol_features.emplace( feature_digest, true ); + if( res.second ) { + // feature_digest was not preactivated + EOS_ASSERT( !f.preactivation_required, protocol_feature_exception, + "attempted to activate protocol feature without prior required preactivation: ${digest}", + ("digest", feature_digest) + ); + } else { + EOS_ASSERT( !res.first->second, block_validate_exception, + "attempted duplicate activation within a single block: ${digest}", + ("digest", feature_digest) + ); + // feature_digest was preactivated + res.first->second = true; + ++num_preactivated_features_that_have_activated; + } + + if( f.builtin_feature ) { + trigger_activation_handler( *f.builtin_feature ); + } + + protocol_features.activate_feature( feature_digest, pbhs.block_num ); + + ++bb._num_new_protocol_features_that_have_activated; + } + + if( num_preactivated_features_that_have_activated == num_preactivated_protocol_features ) { + handled_all_preactivated_features = true; + } + } + + EOS_ASSERT( handled_all_preactivated_features, block_validate_exception, + "There are pre-activated protocol features that were not activated at the start of this block" + ); + + if( new_protocol_feature_activations.size() > 0 ) { + db.modify( pso, [&]( auto& ps ) { + ps.preactivated_protocol_features.clear(); + + ps.activated_protocol_features.reserve( ps.activated_protocol_features.size() + + new_protocol_feature_activations.size() ); + for( const auto& feature_digest : new_protocol_feature_activations ) { + ps.activated_protocol_features.emplace_back( feature_digest, pbhs.block_num ); + } + }); + } + const auto& gpo = db.get(); + if( gpo.proposed_schedule_block_num.valid() && // if there is a proposed schedule that was proposed in a block ... ( *gpo.proposed_schedule_block_num <= pbhs.dpos_irreversible_blocknum ) && // ... that has now become irreversible ... pbhs.prev_pending_schedule.schedule.producers.size() == 0 // ... and there was room for a new pending schedule prior to any possible promotion @@ -1280,8 +1432,8 @@ struct controller_impl { pending->_block_stage.get()._new_pending_producer_schedule = gpo.proposed_schedule; db.modify( gpo, [&]( auto& gp ) { - gp.proposed_schedule_block_num = optional(); - gp.proposed_schedule.clear(); + gp.proposed_schedule_block_num = optional(); + gp.proposed_schedule.clear(); }); } @@ -1335,7 +1487,8 @@ 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_pending_producer_schedule ), + std::move( bb._new_protocol_feature_activations ) ) ); block_ptr->transactions = std::move( bb._pending_trx_receipts ); @@ -1412,11 +1565,76 @@ struct controller_impl { pending->push(); } - void apply_block( const signed_block_ptr& b, controller::block_status s ) { try { + /** + * This method is called from other threads. The controller_impl should outlive those threads. + * However, to avoid race conditions, it means that the behavior of this function should not change + * after controller_impl construction. + + * This should not be an issue since the purpose of this function is to ensure all of the protocol features + * in the supplied vector are recognized by the software, and the set of recognized protocol features is + * determined at startup and cannot be changed without a restart. + */ + void check_protocol_features( block_timestamp_type timestamp, + const flat_set& currently_activated_protocol_features, + const vector& new_protocol_features ) + { + const auto& pfs = protocol_features.get_protocol_feature_set(); + + for( auto itr = new_protocol_features.begin(); itr != new_protocol_features.end(); ++itr ) { + const auto& f = *itr; + + auto status = pfs.is_recognized( f, timestamp ); + switch( status ) { + case protocol_feature_set::recognized_t::unrecognized: + EOS_THROW( protocol_feature_exception, + "protocol feature with digest '${digest}' is unrecognized", ("digest", f) ); + break; + case protocol_feature_set::recognized_t::disabled: + EOS_THROW( protocol_feature_exception, + "protocol feature with digest '${digest}' is disabled", ("digest", f) ); + break; + case protocol_feature_set::recognized_t::too_early: + EOS_THROW( protocol_feature_exception, + "${timestamp} is too early for the earliest allowed activation time of the protocol feature with digest '${digest}'", ("digest", f)("timestamp", timestamp) ); + break; + case protocol_feature_set::recognized_t::ready: + break; + default: + EOS_THROW( protocol_feature_exception, "unexpected recognized_t status" ); + break; + } + + EOS_ASSERT( currently_activated_protocol_features.find( f ) == currently_activated_protocol_features.end(), + protocol_feature_exception, + "protocol feature with digest '${digest}' has already been activated", + ("digest", f) + ); + + auto dependency_checker = [¤tly_activated_protocol_features, &new_protocol_features, &itr] + ( const digest_type& f ) -> bool + { + if( currently_activated_protocol_features.find( f ) != currently_activated_protocol_features.end() ) + return true; + + return (std::find( new_protocol_features.begin(), itr, f ) != itr); + }; + + EOS_ASSERT( pfs.validate_dependencies( f, dependency_checker ), protocol_feature_exception, + "not all dependencies of protocol feature with digest '${digest}' have been activated", + ("digest", f) + ); + } + } + + void apply_block( const block_state_ptr& bsp, controller::block_status s ) + { try { try { - EOS_ASSERT( b->block_extensions.size() == 0, block_validate_exception, "no supported extensions" ); + 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, s , producer_block_id); + start_block( b->timestamp, b->confirmed, new_protocol_feature_activations, s, producer_block_id); std::vector packed_transactions; packed_transactions.reserve( b->transactions.size() ); @@ -1478,6 +1696,10 @@ struct controller_impl { std::move( ab._pending_block_header_state ), b, std::move( ab._trx_metas ), + []( block_timestamp_type timestamp, + const flat_set& cur_features, + const vector& new_features ) + {}, // validation of any new protocol features should have already occurred prior to apply_block true // signature should have already been verified (assuming untrusted) prior to apply_block ); @@ -1505,9 +1727,17 @@ struct controller_impl { EOS_ASSERT( prev, unlinkable_block_exception, "unlinkable block ${id}", ("id", id)("previous", b->previous) ); - return async_thread_pool( thread_pool, [b, prev]() { + return async_thread_pool( thread_pool, [b, prev, control=this]() { const bool skip_validate_signee = false; - return std::make_shared( *prev, move( b ), skip_validate_signee ); + return std::make_shared( + *prev, + move( b ), + [control]( block_timestamp_type timestamp, + const flat_set& cur_features, + const vector& new_features ) + { control->check_protocol_features( timestamp, cur_features, new_features ); }, + skip_validate_signee + ); } ); } @@ -1554,7 +1784,15 @@ struct controller_impl { emit( self.pre_accepted_block, b ); const bool skip_validate_signee = !conf.force_all_checks; - auto bsp = std::make_shared( *head, b, skip_validate_signee ); + auto bsp = std::make_shared( + *head, + b, + [this]( block_timestamp_type timestamp, + const flat_set& cur_features, + const vector& new_features ) + { check_protocol_features( timestamp, cur_features, new_features ); }, + skip_validate_signee + ); if( s != controller::block_status::irreversible ) { fork_db.add( bsp, true ); @@ -1563,7 +1801,7 @@ struct controller_impl { emit( self.accepted_block_header, bsp ); if( s == controller::block_status::irreversible ) { - apply_block( bsp->block, s ); + apply_block( bsp, s ); head = bsp; // On replay, log_irreversible is not called and so no irreversible_block signal is emittted. @@ -1582,7 +1820,7 @@ struct controller_impl { bool head_changed = true; if( new_head->header.previous == head->id ) { try { - apply_block( new_head->block, s ); + apply_block( new_head, s ); fork_db.mark_valid( new_head ); head = new_head; } catch ( const fc::exception& e ) { @@ -1606,7 +1844,8 @@ struct controller_impl { for( auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr ) { optional except; try { - apply_block( (*ritr)->block, (*ritr)->is_valid() ? controller::block_status::validated : controller::block_status::complete ); + apply_block( *ritr, (*ritr)->is_valid() ? controller::block_status::validated + : controller::block_status::complete ); fork_db.mark_valid( *ritr ); head = *ritr; } catch (const fc::exception& e) { @@ -1630,7 +1869,7 @@ struct controller_impl { // re-apply good blocks for( auto ritr = branches.second.rbegin(); ritr != branches.second.rend(); ++ritr ) { - apply_block( (*ritr)->block, controller::block_status::validated /* we previously validated these blocks*/ ); + apply_block( *ritr, controller::block_status::validated /* we previously validated these blocks*/ ); head = *ritr; } throw *except; @@ -1653,6 +1892,7 @@ struct controller_impl { unapplied_transactions[t->signed_id] = t; } pending.reset(); + protocol_features.popped_blocks_to( head->block_num ); } } @@ -1920,8 +2160,18 @@ authorization_manager& controller::get_mutable_authorization_manager() return my->authorization; } +const protocol_feature_manager& controller::get_protocol_feature_manager()const +{ + return my->protocol_features; +} + controller::controller( const controller::config& cfg ) -:my( new controller_impl( cfg, *this ) ) +:my( new controller_impl( cfg, *this, protocol_feature_set{} ) ) +{ +} + +controller::controller( const config& cfg, protocol_feature_set&& pfs ) +:my( new controller_impl( cfg, *this, std::move(pfs) ) ) { } @@ -1961,10 +2211,163 @@ chainbase::database& controller::mutable_db()const { return my->db; } const fork_database& controller::fork_db()const { return my->fork_db; } +void controller::preactivate_feature( const digest_type& feature_digest ) { + const auto& pfs = my->protocol_features.get_protocol_feature_set(); + auto cur_time = pending_block_time(); + + auto status = pfs.is_recognized( feature_digest, cur_time ); + switch( status ) { + case protocol_feature_set::recognized_t::unrecognized: + if( is_producing_block() ) { + EOS_THROW( subjective_block_production_exception, + "protocol feature with digest '${digest}' is unrecognized", ("digest", feature_digest) ); + } else { + EOS_THROW( protocol_feature_bad_block_exception, + "protocol feature with digest '${digest}' is unrecognized", ("digest", feature_digest) ); + } + break; + case protocol_feature_set::recognized_t::disabled: + if( is_producing_block() ) { + EOS_THROW( subjective_block_production_exception, + "protocol feature with digest '${digest}' is disabled", ("digest", feature_digest) ); + } else { + EOS_THROW( protocol_feature_bad_block_exception, + "protocol feature with digest '${digest}' is disabled", ("digest", feature_digest) ); + } + break; + case protocol_feature_set::recognized_t::too_early: + if( is_producing_block() ) { + EOS_THROW( subjective_block_production_exception, + "${timestamp} is too early for the earliest allowed activation time of the protocol feature with digest '${digest}'", ("digest", feature_digest)("timestamp", cur_time) ); + } else { + EOS_THROW( protocol_feature_bad_block_exception, + "${timestamp} is too early for the earliest allowed activation time of the protocol feature with digest '${digest}'", ("digest", feature_digest)("timestamp", cur_time) ); + } + break; + case protocol_feature_set::recognized_t::ready: + break; + default: + if( is_producing_block() ) { + EOS_THROW( subjective_block_production_exception, "unexpected recognized_t status" ); + } else { + EOS_THROW( protocol_feature_bad_block_exception, "unexpected recognized_t status" ); + } + break; + } + + // The above failures depend on subjective information. + // Because of deferred transactions, this complicates things considerably. + + // If producing a block, we throw a subjective failure if the feature is not properly recognized in order + // to try to avoid retiring into a block a deferred transacton driven by subjective information. + + // But it is still possible for a producer to retire a deferred transaction that deals with this subjective + // information. If they recognized the feature, they would retire it successfully, but a validator that + // does not recognize the feature should reject the entire block (not just fail the deferred transaction). + // Even if they don't recognize the feature, the producer could change their nodeos code to treat it like an + // objective failure thus leading the deferred transaction to retire with soft_fail or hard_fail. + // In this case, validators that don't recognize the feature would reject the whole block immediately, and + // validators that do recognize the feature would likely lead to a different retire status which would + // ultimately cause a validation failure and thus rejection of the block. + // In either case, it results in rejection of the block which is the desired behavior in this scenario. + + // If the feature is properly recognized by producer and validator, we have dealt with the subjectivity and + // now only consider the remaining failure modes which are deterministic and objective. + // Thus the exceptions that can be thrown below can be regular objective exceptions + // that do not cause immediate rejection of the block. + + EOS_ASSERT( !is_protocol_feature_activated( feature_digest ), + protocol_feature_exception, + "protocol feature with digest '${digest}' is already activated", + ("digest", feature_digest) + ); + + const auto& pso = my->db.get(); + + EOS_ASSERT( std::find( pso.preactivated_protocol_features.begin(), + pso.preactivated_protocol_features.end(), + feature_digest + ) == pso.preactivated_protocol_features.end(), + protocol_feature_exception, + "protocol feature with digest '${digest}' is already pre-activated", + ("digest", feature_digest) + ); + + auto dependency_checker = [&]( const digest_type& d ) -> bool + { + if( is_protocol_feature_activated( d ) ) return true; + + return ( std::find( pso.preactivated_protocol_features.begin(), + pso.preactivated_protocol_features.end(), + d ) != pso.preactivated_protocol_features.end() ); + }; + + EOS_ASSERT( pfs.validate_dependencies( feature_digest, dependency_checker ), + protocol_feature_exception, + "not all dependencies of protocol feature with digest '${digest}' have been activated or pre-activated", + ("digest", feature_digest) + ); + + my->db.modify( pso, [&]( auto& ps ) { + ps.preactivated_protocol_features.push_back( feature_digest ); + } ); +} + +vector controller::get_preactivated_protocol_features()const { + const auto& pso = my->db.get(); + + if( pso.preactivated_protocol_features.size() == 0 ) return {}; + + vector preactivated_protocol_features; + + for( const auto& f : pso.preactivated_protocol_features ) { + preactivated_protocol_features.emplace_back( f ); + } -void controller::start_block( block_timestamp_type when, uint16_t confirm_block_count) { + return preactivated_protocol_features; +} + +void controller::validate_protocol_features( const vector& features_to_activate )const { + my->check_protocol_features( my->head->header.timestamp, + my->head->activated_protocol_features->protocol_features, + features_to_activate ); +} + +void controller::start_block( block_timestamp_type when, uint16_t confirm_block_count ) +{ + validate_db_available_size(); + + EOS_ASSERT( !my->pending, block_validate_exception, "pending block already exists" ); + + vector new_protocol_feature_activations; + + const auto& pso = my->db.get(); + if( pso.preactivated_protocol_features.size() > 0 ) { + for( const auto& f : pso.preactivated_protocol_features ) { + new_protocol_feature_activations.emplace_back( f ); + } + } + + if( new_protocol_feature_activations.size() > 0 ) { + validate_protocol_features( new_protocol_feature_activations ); + } + + my->start_block( when, confirm_block_count, new_protocol_feature_activations, + block_status::incomplete, optional() ); +} + +void controller::start_block( block_timestamp_type when, + uint16_t confirm_block_count, + const vector& new_protocol_feature_activations ) +{ validate_db_available_size(); - my->start_block(when, confirm_block_count, block_status::incomplete, optional() ); + + if( new_protocol_feature_activations.size() > 0 ) { + validate_protocol_features( new_protocol_feature_activations ); + } + + my->start_block( when, confirm_block_count, new_protocol_feature_activations, + block_status::incomplete, optional() ); } block_state_ptr controller::finalize_block( const std::function& signer_callback ) { @@ -1978,6 +2381,10 @@ block_state_ptr controller::finalize_block( const std::function& cur_features, + const vector& new_features ) + {}, signer_callback ); @@ -2497,6 +2904,24 @@ void controller::validate_reversible_available_size() const { EOS_ASSERT(free >= guard, reversible_guard_exception, "reversible free: ${f}, guard size: ${g}", ("f", free)("g",guard)); } +bool controller::is_protocol_feature_activated( const digest_type& feature_digest )const { + if( my->pending ) + return my->pending->is_protocol_feature_activated( feature_digest ); + + const auto& activated_features = my->head->activated_protocol_features->protocol_features; + return (activated_features.find( feature_digest ) != activated_features.end()); +} + +bool controller::is_builtin_activated( builtin_protocol_feature_t f )const { + uint32_t current_block_num = head_block_num(); + + if( my->pending ) { + ++current_block_num; + } + + return my->protocol_features.is_builtin_activated( f, current_block_num ); +} + bool controller::is_known_unexpired_transaction( const transaction_id_type& id) const { return db().find(id); } @@ -2521,4 +2946,16 @@ const flat_set &controller::get_resource_greylist() const { return my->conf.resource_greylist; } +/// Protocol feature activation handlers: + +template<> +void controller_impl::on_activation() { + db.modify( db.get(), [&]( auto& ps ) { + add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "preactivate_feature" ); + add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "is_feature_activated" ); + } ); +} + +/// End of protocol feature activation handlers + } } /// eosio::chain diff --git a/libraries/chain/eosio_contract.cpp b/libraries/chain/eosio_contract.cpp index 03e0fed7f7f..cc303bb50cd 100644 --- a/libraries/chain/eosio_contract.cpp +++ b/libraries/chain/eosio_contract.cpp @@ -315,7 +315,15 @@ void apply_eosio_linkauth(apply_context& context) { EOS_ASSERT(code != nullptr, account_query_exception, "Failed to retrieve code for account: ${account}", ("account", requirement.code)); if( requirement.requirement != config::eosio_any_name ) { - const auto *permission = db.find(requirement.requirement); + const permission_object* permission = nullptr; + if( context.control.is_builtin_activated( builtin_protocol_feature_t::only_link_to_existing_permission ) ) { + permission = db.find( + boost::make_tuple( requirement.account, requirement.requirement ) + ); + } else { + permission = db.find(requirement.requirement); + } + EOS_ASSERT(permission != nullptr, permission_query_exception, "Failed to retrieve permission: ${permission}", ("permission", requirement.requirement)); } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 0cb2485e23b..1fe7459dfae 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -55,16 +55,34 @@ namespace eosio { namespace chain { } struct fork_database_impl { + fork_database_impl( fork_database& self, const fc::path& data_dir ) + :self(self) + ,datadir(data_dir) + {} + + fork_database& self; fork_multi_index_type index; block_state_ptr root; // Only uses the block_header_state portion block_state_ptr head; fc::path datadir; + + void add( const block_state_ptr& n, + bool ignore_duplicate, bool validate, + const std::function&, + const vector& )>& validator ); }; - fork_database::fork_database( const fc::path& data_dir ):my( new fork_database_impl() ) { - my->datadir = data_dir; + fork_database::fork_database( const fc::path& data_dir ) + :my( new fork_database_impl( *this, data_dir ) ) + {} + + void fork_database::open( const std::function&, + const vector& )>& validator ) + { if (!fc::is_directory(my->datadir)) fc::create_directories(my->datadir); @@ -113,7 +131,8 @@ namespace eosio { namespace chain { s.trxs.push_back( std::make_shared( std::make_shared(pt) ) ); } } - add( std::make_shared( move( s ) ) ); + s.header_exts = s.block->validate_and_extract_header_extensions(); + my->add( std::make_shared( move( s ) ), false, true, validator ); } block_id_type head_id; fc::raw::unpack( ds, head_id ); @@ -277,25 +296,52 @@ namespace eosio { namespace chain { return block_header_state_ptr(); } - void fork_database::add( const block_state_ptr& n, bool ignore_duplicate ) { - EOS_ASSERT( my->root, fork_database_exception, "root not yet set" ); + void fork_database_impl::add( const block_state_ptr& n, + bool ignore_duplicate, bool validate, + const std::function&, + const vector& )>& validator ) + { + EOS_ASSERT( root, fork_database_exception, "root not yet set" ); EOS_ASSERT( n, fork_database_exception, "attempt to add null block state" ); - EOS_ASSERT( get_block_header( n->header.previous ), unlinkable_block_exception, + auto prev_bh = self.get_block_header( n->header.previous ); + + EOS_ASSERT( prev_bh, unlinkable_block_exception, "unlinkable block", ("id", n->id)("previous", n->header.previous) ); - auto inserted = my->index.insert(n); + if( validate ) { + try { + const auto& exts = n->header_exts; + + if( exts.size() > 0 ) { + const auto& new_protocol_features = exts.front().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" ) + } + + auto inserted = index.insert(n); if( !inserted.second ) { if( ignore_duplicate ) return; EOS_THROW( fork_database_exception, "duplicate block added", ("id", n->id) ); } - auto candidate = my->index.get().begin(); + auto candidate = index.get().begin(); if( (*candidate)->is_valid() ) { - my->head = *candidate; + head = *candidate; } } + void fork_database::add( const block_state_ptr& n, bool ignore_duplicate ) { + my->add( n, ignore_duplicate, false, + []( block_timestamp_type timestamp, + const flat_set& cur_features, + const vector& new_features ) + {} + ); + } + const block_state_ptr& fork_database::root()const { return my->root; } const block_state_ptr& fork_database::head()const { return my->head; } diff --git a/libraries/chain/genesis_intrinsics.cpp b/libraries/chain/genesis_intrinsics.cpp new file mode 100644 index 00000000000..be6077acbb1 --- /dev/null +++ b/libraries/chain/genesis_intrinsics.cpp @@ -0,0 +1,181 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ + +#include + +namespace eosio { namespace chain { + +const std::vector genesis_intrinsics = { + "__ashrti3", + "__lshlti3", + "__lshrti3", + "__ashlti3", + "__divti3", + "__udivti3", + "__modti3", + "__umodti3", + "__multi3", + "__addtf3", + "__subtf3", + "__multf3", + "__divtf3", + "__eqtf2", + "__netf2", + "__getf2", + "__gttf2", + "__lttf2", + "__letf2", + "__cmptf2", + "__unordtf2", + "__negtf2", + "__floatsitf", + "__floatunsitf", + "__floatditf", + "__floatunditf", + "__floattidf", + "__floatuntidf", + "__floatsidf", + "__extendsftf2", + "__extenddftf2", + "__fixtfti", + "__fixtfdi", + "__fixtfsi", + "__fixunstfti", + "__fixunstfdi", + "__fixunstfsi", + "__fixsfti", + "__fixdfti", + "__fixunssfti", + "__fixunsdfti", + "__trunctfdf2", + "__trunctfsf2", + "is_feature_active", + "activate_feature", + "get_resource_limits", + "set_resource_limits", + "set_proposed_producers", + "get_blockchain_parameters_packed", + "set_blockchain_parameters_packed", + "is_privileged", + "set_privileged", + "get_active_producers", + "db_idx64_store", + "db_idx64_remove", + "db_idx64_update", + "db_idx64_find_primary", + "db_idx64_find_secondary", + "db_idx64_lowerbound", + "db_idx64_upperbound", + "db_idx64_end", + "db_idx64_next", + "db_idx64_previous", + "db_idx128_store", + "db_idx128_remove", + "db_idx128_update", + "db_idx128_find_primary", + "db_idx128_find_secondary", + "db_idx128_lowerbound", + "db_idx128_upperbound", + "db_idx128_end", + "db_idx128_next", + "db_idx128_previous", + "db_idx256_store", + "db_idx256_remove", + "db_idx256_update", + "db_idx256_find_primary", + "db_idx256_find_secondary", + "db_idx256_lowerbound", + "db_idx256_upperbound", + "db_idx256_end", + "db_idx256_next", + "db_idx256_previous", + "db_idx_double_store", + "db_idx_double_remove", + "db_idx_double_update", + "db_idx_double_find_primary", + "db_idx_double_find_secondary", + "db_idx_double_lowerbound", + "db_idx_double_upperbound", + "db_idx_double_end", + "db_idx_double_next", + "db_idx_double_previous", + "db_idx_long_double_store", + "db_idx_long_double_remove", + "db_idx_long_double_update", + "db_idx_long_double_find_primary", + "db_idx_long_double_find_secondary", + "db_idx_long_double_lowerbound", + "db_idx_long_double_upperbound", + "db_idx_long_double_end", + "db_idx_long_double_next", + "db_idx_long_double_previous", + "db_store_i64", + "db_update_i64", + "db_remove_i64", + "db_get_i64", + "db_next_i64", + "db_previous_i64", + "db_find_i64", + "db_lowerbound_i64", + "db_upperbound_i64", + "db_end_i64", + "assert_recover_key", + "recover_key", + "assert_sha256", + "assert_sha1", + "assert_sha512", + "assert_ripemd160", + "sha1", + "sha256", + "sha512", + "ripemd160", + "check_transaction_authorization", + "check_permission_authorization", + "get_permission_last_used", + "get_account_creation_time", + "current_time", + "publication_time", + "abort", + "eosio_assert", + "eosio_assert_message", + "eosio_assert_code", + "eosio_exit", + "read_action_data", + "action_data_size", + "current_receiver", + "require_recipient", + "require_auth", + "require_auth2", + "has_auth", + "is_account", + "prints", + "prints_l", + "printi", + "printui", + "printi128", + "printui128", + "printsf", + "printdf", + "printqf", + "printn", + "printhex", + "read_transaction", + "transaction_size", + "expiration", + "tapos_block_prefix", + "tapos_block_num", + "get_action", + "send_inline", + "send_context_free_inline", + "send_deferred", + "cancel_deferred", + "get_context_free_data", + "memcpy", + "memmove", + "memcmp", + "memset" +}; + +} } // namespace eosio::chain diff --git a/libraries/chain/include/eosio/chain/block_header.hpp b/libraries/chain/include/eosio/chain/block_header.hpp index 2849ee00f31..adbdb7d3def 100644 --- a/libraries/chain/include/eosio/chain/block_header.hpp +++ b/libraries/chain/include/eosio/chain/block_header.hpp @@ -1,9 +1,61 @@ #pragma once #include #include +#include + +#include namespace eosio { namespace chain { + namespace detail { + struct extract_match { + bool enforce_unique = false; + }; + + template + struct decompose; + + template<> + struct decompose<> { + template + static auto extract( uint16_t id, const vector& data, ResultVariant& result ) + -> fc::optional + { + return {}; + } + }; + + template + struct decompose { + using head_t = T; + using tail_t = decompose< Rest... >; + + template + static auto extract( uint16_t id, const vector& data, ResultVariant& result ) + -> fc::optional + { + if( id == head_t::extension_id() ) { + result = fc::raw::unpack( data ); + return { extract_match{ head_t::enforce_unique() } }; + } + + return tail_t::template extract( id, data, result ); + } + }; + + template + struct block_header_extension_types { + using block_header_extensions_t = fc::static_variant< Ts... >; + using decompose_t = decompose< Ts... >; + }; + } + + using block_header_extension_types = detail::block_header_extension_types< + protocol_feature_activation + >; + + using block_header_extensions = block_header_extension_types::block_header_extensions_t; + struct block_header { block_timestamp_type timestamp; @@ -41,6 +93,8 @@ namespace eosio { namespace chain { block_id_type id() const; 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; }; diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 2ec87664c28..41e19253138 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -28,25 +28,37 @@ namespace detail { } struct pending_block_header_state : public detail::block_header_state_common { - detail::schedule_info prev_pending_schedule; - bool was_pending_promoted = false; - block_id_type previous; - account_name producer; - block_timestamp_type timestamp; - uint32_t active_schedule_version = 0; - uint16_t confirmed = 1; + protocol_feature_activation_set_ptr prev_activated_protocol_features; + detail::schedule_info prev_pending_schedule; + bool was_pending_promoted = false; + block_id_type previous; + account_name producer; + block_timestamp_type timestamp; + uint32_t active_schedule_version = 0; + uint16_t confirmed = 1; signed_block_header make_block_header( const checksum256_type& transaction_mroot, const checksum256_type& action_mroot, - optional&& new_producers )const; + optional&& new_producers, + vector&& new_protocol_feature_activations )const; - block_header_state finish_next( const signed_block_header& h, bool skip_validate_signee = false )&&; + block_header_state finish_next( const signed_block_header& h, + const std::function&, + const vector& )>& validator, + bool skip_validate_signee = false )&&; block_header_state finish_next( signed_block_header& h, - const std::function& signer )&&; + const std::function&, + const vector& )>& validator, + const std::function& signer )&&; protected: - block_header_state _finish_next( const signed_block_header& h )&&; + block_header_state _finish_next( const signed_block_header& h, + const std::function&, + const vector& )>& validator )&&; }; @@ -55,9 +67,14 @@ struct pending_block_header_state : public detail::block_header_state_common { * @brief defines the minimum state necessary to validate transaction headers */ struct block_header_state : public detail::block_header_state_common { - block_id_type id; - signed_block_header header; - detail::schedule_info pending_schedule; + block_id_type id; + signed_block_header header; + detail::schedule_info pending_schedule; + protocol_feature_activation_set_ptr activated_protocol_features; + + /// this data is redundant with the data stored in header, but it acts as a cache that avoids + /// duplication of work + vector header_exts; block_header_state() = default; @@ -67,7 +84,11 @@ 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, bool skip_validate_signee = false )const; + block_header_state next( const signed_block_header& h, + const std::function&, + const vector& )>& validator, + bool skip_validate_signee = false )const; bool has_pending_producers()const { return pending_schedule.schedule.producers.size(); } uint32_t calc_dpos_last_irreversible( account_name producer_of_next_block )const; @@ -79,6 +100,8 @@ struct block_header_state : public detail::block_header_state_common { void sign( const std::function& signer ); public_key_type signee()const; void verify_signee(const public_key_type& signee)const; + + const vector& get_new_protocol_feature_activations()const; }; using block_header_state_ptr = std::shared_ptr; @@ -107,4 +130,5 @@ FC_REFLECT_DERIVED( eosio::chain::block_header_state, (eosio::chain::detail::bl (id) (header) (pending_schedule) + (activated_protocol_features) ) diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 1b8e659a083..e91161cf716 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -14,18 +14,27 @@ namespace eosio { namespace chain { struct block_state : public block_header_state { block_state( const block_header_state& prev, signed_block_ptr b, + const std::function&, + const vector& )>& validator, bool skip_validate_signee ); block_state( pending_block_header_state&& cur, signed_block_ptr&& b, // unsigned block vector&& trx_metas, + const std::function&, + const vector& )>& validator, const std::function& signer ); block_state( pending_block_header_state&& cur, const signed_block_ptr& b, // signed block vector&& trx_metas, + const std::function&, + const vector& )>& validator, bool skip_validate_signee ); diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index bdf4559f913..6b1c82faea4 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace chainbase { class database; @@ -97,16 +98,33 @@ namespace eosio { namespace chain { }; explicit controller( const config& cfg ); + controller( const config& cfg, protocol_feature_set&& pfs ); ~controller(); void add_indices(); void startup( std::function shutdown, const snapshot_reader_ptr& snapshot = nullptr ); + void preactivate_feature( const digest_type& feature_digest ); + + vector get_preactivated_protocol_features()const; + + void validate_protocol_features( const vector& features_to_activate )const; + + /** + * Starts a new pending block session upon which new transactions can + * be pushed. + * + * Will only activate protocol features that have been pre-activated. + */ + void start_block( block_timestamp_type time = block_timestamp_type(), uint16_t confirm_block_count = 0 ); + /** * Starts a new pending block session upon which new transactions can * be pushed. */ - void start_block( block_timestamp_type time = block_timestamp_type(), uint16_t confirm_block_count = 0 ); + void start_block( block_timestamp_type time, + uint16_t confirm_block_count, + const vector& new_protocol_feature_activations ); void abort_block(); @@ -153,6 +171,7 @@ namespace eosio { namespace chain { resource_limits_manager& get_mutable_resource_limits_manager(); const authorization_manager& get_authorization_manager()const; authorization_manager& get_mutable_authorization_manager(); + const protocol_feature_manager& get_protocol_feature_manager()const; const flat_set& get_actor_whitelist() const; const flat_set& get_actor_blacklist() const; @@ -230,6 +249,9 @@ namespace eosio { namespace chain { void validate_db_available_size() const; void validate_reversible_available_size() const; + bool is_protocol_feature_activated( const digest_type& feature_digest )const; + bool is_builtin_activated( builtin_protocol_feature_t f )const; + bool is_known_unexpired_transaction( const transaction_id_type& id) const; int64_t set_proposed_producers( vector producers ); diff --git a/libraries/chain/include/eosio/chain/exceptions.hpp b/libraries/chain/include/eosio/chain/exceptions.hpp index 6c3e504d349..a80213e0425 100644 --- a/libraries/chain/include/eosio/chain/exceptions.hpp +++ b/libraries/chain/include/eosio/chain/exceptions.hpp @@ -158,7 +158,10 @@ namespace eosio { namespace chain { 3030008, "Block is not signed with expected key" ) FC_DECLARE_DERIVED_EXCEPTION( wrong_producer, block_validate_exception, 3030009, "Block is not signed by expected producer" ) - + FC_DECLARE_DERIVED_EXCEPTION( invalid_block_header_extension, block_validate_exception, + 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" ) @@ -460,6 +463,8 @@ namespace eosio { namespace chain { 3170007, "The configured snapshot directory does not exist" ) FC_DECLARE_DERIVED_EXCEPTION( snapshot_exists_exception, producer_exception, 3170008, "The requested snapshot already exists" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_protocol_features_to_activate, producer_exception, + 3170009, "The protocol features to be activated were not valid" ) FC_DECLARE_DERIVED_EXCEPTION( reversible_blocks_exception, chain_exception, 3180000, "Reversible Blocks exception" ) @@ -519,4 +524,13 @@ namespace eosio { namespace chain { 3240000, "Snapshot exception" ) FC_DECLARE_DERIVED_EXCEPTION( snapshot_validation_exception, snapshot_exception, 3240001, "Snapshot Validation Exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( protocol_feature_exception, chain_exception, + 3250000, "Protocol feature exception" ) + FC_DECLARE_DERIVED_EXCEPTION( protocol_feature_validation_exception, protocol_feature_exception, + 3250001, "Protocol feature validation exception" ) + FC_DECLARE_DERIVED_EXCEPTION( protocol_feature_bad_block_exception, protocol_feature_exception, + 3250002, "Protocol feature exception (invalid block)" ) + FC_DECLARE_DERIVED_EXCEPTION( protocol_feature_iterator_exception, protocol_feature_exception, + 3250003, "Protocol feature iterator exception" ) } } // eosio::chain diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 900e06466f2..e225d0fdcd1 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -25,6 +25,9 @@ namespace eosio { namespace chain { explicit fork_database( const fc::path& data_dir ); ~fork_database(); + void open( const std::function&, + const vector& )>& validator ); void close(); block_header_state_ptr get_block_header( const block_id_type& id )const; diff --git a/libraries/chain/include/eosio/chain/genesis_intrinsics.hpp b/libraries/chain/include/eosio/chain/genesis_intrinsics.hpp new file mode 100644 index 00000000000..bd736d6a285 --- /dev/null +++ b/libraries/chain/include/eosio/chain/genesis_intrinsics.hpp @@ -0,0 +1,14 @@ + +/** + * @file + * @copyright defined in eos/LICENSE + */ +#pragma once + +#include + +namespace eosio { namespace chain { + +extern const std::vector genesis_intrinsics; + +} } // namespace eosio::chain diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index 7f3c09cccf5..14ed594c0bd 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -18,32 +18,36 @@ namespace eosio { namespace chain { /** * @class global_property_object - * @brief Maintains global state information (committee_member list, current fees) + * @brief Maintains global state information about block producer schedules and chain configuration parameters * @ingroup object * @ingroup implementation - * - * This is an implementation detail. The values here are set by committee_members to tune the blockchain parameters. */ class global_property_object : public chainbase::object { OBJECT_CTOR(global_property_object, (proposed_schedule)) - id_type id; - optional proposed_schedule_block_num; - shared_producer_schedule_type proposed_schedule; - chain_config configuration; + public: + id_type id; + optional proposed_schedule_block_num; + shared_producer_schedule_type proposed_schedule; + chain_config configuration; }; + using global_property_multi_index = chainbase::shared_multi_index_container< + global_property_object, + indexed_by< + ordered_unique, + BOOST_MULTI_INDEX_MEMBER(global_property_object, global_property_object::id_type, id) + > + > + >; /** * @class dynamic_global_property_object - * @brief Maintains global state information (committee_member list, current fees) + * @brief Maintains global state information that frequently change * @ingroup object * @ingroup implementation - * - * This is an implementation detail. The values here are calculated during normal chain operations and reflect the - * current values of global blockchain properties. */ class dynamic_global_property_object : public chainbase::object { @@ -53,15 +57,6 @@ namespace eosio { namespace chain { uint64_t global_action_sequence = 0; }; - using global_property_multi_index = chainbase::shared_multi_index_container< - global_property_object, - indexed_by< - ordered_unique, - BOOST_MULTI_INDEX_MEMBER(global_property_object, global_property_object::id_type, id) - > - > - >; - using dynamic_global_property_multi_index = chainbase::shared_multi_index_container< dynamic_global_property_object, indexed_by< @@ -77,10 +72,10 @@ CHAINBASE_SET_INDEX_TYPE(eosio::chain::global_property_object, eosio::chain::glo CHAINBASE_SET_INDEX_TYPE(eosio::chain::dynamic_global_property_object, eosio::chain::dynamic_global_property_multi_index) -FC_REFLECT(eosio::chain::dynamic_global_property_object, - (global_action_sequence) +FC_REFLECT(eosio::chain::global_property_object, + (proposed_schedule_block_num)(proposed_schedule)(configuration) ) -FC_REFLECT(eosio::chain::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/protocol_feature_activation.hpp b/libraries/chain/include/eosio/chain/protocol_feature_activation.hpp new file mode 100644 index 00000000000..03ab31be131 --- /dev/null +++ b/libraries/chain/include/eosio/chain/protocol_feature_activation.hpp @@ -0,0 +1,49 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ +#pragma once + +#include + +namespace eosio { namespace chain { + +struct protocol_feature_activation : fc::reflect_init { + static constexpr uint16_t extension_id() { return 0; } + static constexpr bool enforce_unique() { return true; } + + protocol_feature_activation() = default; + + protocol_feature_activation( const vector& pf ) + :protocol_features( pf ) + {} + + protocol_feature_activation( vector&& pf ) + :protocol_features( std::move(pf) ) + {} + + void reflector_init(); + + vector protocol_features; +}; + +struct protocol_feature_activation_set; + +using protocol_feature_activation_set_ptr = std::shared_ptr; + +struct protocol_feature_activation_set { + flat_set protocol_features; + + protocol_feature_activation_set() = default; + + protocol_feature_activation_set( const protocol_feature_activation_set& orig_pfa_set, + vector additional_features, + bool enforce_disjoint = true + ); +}; + + +} } // namespace eosio::chain + +FC_REFLECT(eosio::chain::protocol_feature_activation, (protocol_features)) +FC_REFLECT(eosio::chain::protocol_feature_activation_set, (protocol_features)) diff --git a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp new file mode 100644 index 00000000000..2f6f6cb3630 --- /dev/null +++ b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp @@ -0,0 +1,371 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ +#pragma once + +#include +#include + +namespace eosio { namespace chain { + +enum class protocol_feature_t : uint32_t { + builtin +}; + +enum class builtin_protocol_feature_t : uint32_t { + preactivate_feature, + only_link_to_existing_permission +}; + +struct protocol_feature_subjective_restrictions { + time_point earliest_allowed_activation_time; + bool preactivation_required = false; + bool enabled = false; +}; + +struct builtin_protocol_feature_spec { + const char* codename = nullptr; + digest_type description_digest; + flat_set builtin_dependencies; + protocol_feature_subjective_restrictions subjective_restrictions{time_point{}, true, true}; +}; + +extern const std::unordered_map> builtin_protocol_feature_codenames; + +const char* builtin_protocol_feature_codename( builtin_protocol_feature_t ); + +class protocol_feature_base : public fc::reflect_init { +public: + protocol_feature_base() = default; + + protocol_feature_base( protocol_feature_t feature_type, + const digest_type& description_digest, + flat_set&& dependencies, + const protocol_feature_subjective_restrictions& restrictions ); + + void reflector_init(); + + protocol_feature_t get_type()const { return _type; } + +public: + std::string protocol_feature_type; + digest_type description_digest; + flat_set dependencies; + protocol_feature_subjective_restrictions subjective_restrictions; +protected: + protocol_feature_t _type; +}; + +class builtin_protocol_feature : public protocol_feature_base { +public: + static const char* feature_type_string; + + builtin_protocol_feature() = default; + + builtin_protocol_feature( builtin_protocol_feature_t codename, + const digest_type& description_digest, + flat_set&& dependencies, + const protocol_feature_subjective_restrictions& restrictions ); + + void reflector_init(); + + digest_type digest()const; + + builtin_protocol_feature_t get_codename()const { return _codename; } + + friend class protocol_feature_set; + +public: + std::string builtin_feature_codename; +protected: + builtin_protocol_feature_t _codename; +}; + +struct protocol_feature { + digest_type feature_digest; + digest_type description_digest; + flat_set dependencies; + time_point earliest_allowed_activation_time; + bool preactivation_required = false; + bool enabled = false; + optional builtin_feature; + + fc::variant to_variant( bool include_subjective_restrictions = true, + fc::mutable_variant_object* additional_fields = nullptr )const; + + friend bool operator <( const protocol_feature& lhs, const protocol_feature& rhs ) { + return lhs.feature_digest < rhs.feature_digest; + } + + friend bool operator <( const digest_type& lhs, const protocol_feature& rhs ) { + return lhs < rhs.feature_digest; + } + + friend bool operator <( const protocol_feature& lhs, const digest_type& rhs ) { + return lhs.feature_digest < rhs; + } +}; + +class protocol_feature_set { +protected: + using protocol_feature_set_type = std::set< protocol_feature, std::less<> >; + +public: + protocol_feature_set(); + + enum class recognized_t { + unrecognized, + disabled, + too_early, + ready + }; + + recognized_t is_recognized( const digest_type& feature_digest, time_point now )const; + + optional get_builtin_digest( builtin_protocol_feature_t feature_codename )const; + + const protocol_feature& get_protocol_feature( const digest_type& feature_digest )const; + + bool validate_dependencies( const digest_type& feature_digest, + const std::function& validator )const; + + static builtin_protocol_feature + make_default_builtin_protocol_feature( + builtin_protocol_feature_t codename, + const std::function& handle_dependency + ); + + const protocol_feature& add_feature( const builtin_protocol_feature& f ); + + class const_iterator : public std::iterator { + protected: + protocol_feature_set_type::const_iterator _itr; + + protected: + explicit const_iterator( protocol_feature_set_type::const_iterator itr ) + :_itr(itr) + {} + + const protocol_feature* get_pointer()const { return &*_itr; } + + friend class protocol_feature_set; + + public: + const_iterator() = default; + + friend bool operator == ( const const_iterator& lhs, const const_iterator& rhs ) { + return (lhs._itr == rhs._itr); + } + + friend bool operator != ( const const_iterator& lhs, const const_iterator& rhs ) { + return (lhs._itr != rhs._itr); + } + + const protocol_feature& operator*()const { + return *get_pointer(); + } + + const protocol_feature* operator->()const { + return get_pointer(); + } + + const_iterator& operator++() { + ++_itr; + return *this; + } + + const_iterator& operator--() { + --_itr; + return *this; + } + + const_iterator operator++(int) { + const_iterator result(*this); + ++(*this); + return result; + } + + const_iterator operator--(int) { + const_iterator result(*this); + --(*this); + return result; + } + }; + + using const_reverse_iterator = std::reverse_iterator; + + const_iterator cbegin()const { return const_iterator( _recognized_protocol_features.cbegin() ); } + const_iterator begin()const { return cbegin(); } + + const_iterator cend()const { return const_iterator( _recognized_protocol_features.cend() ); } + const_iterator end()const { return cend(); } + + const_reverse_iterator crbegin()const { return std::make_reverse_iterator( cend() ); } + const_reverse_iterator rbegin()const { return crbegin(); } + + const_reverse_iterator crend()const { return std::make_reverse_iterator( cbegin() ); } + const_reverse_iterator rend()const { return crend(); } + + bool empty()const { return _recognized_protocol_features.empty(); } + std::size_t size()const { return _recognized_protocol_features.size(); } + std::size_t max_size()const { return _recognized_protocol_features.max_size(); } + + template + const_iterator find( const K& x )const { + return const_iterator( _recognized_protocol_features.find( x ) ); + } + + template + const_iterator lower_bound( const K& x )const { + return const_iterator( _recognized_protocol_features.lower_bound( x ) ); + } + + template + const_iterator upper_bound( const K& x )const { + return const_iterator( _recognized_protocol_features.upper_bound( x ) ); + } + + friend class protocol_feature_manager; + +protected: + protocol_feature_set_type _recognized_protocol_features; + vector _recognized_builtin_protocol_features; +}; + + +class protocol_feature_manager { +public: + + protocol_feature_manager( protocol_feature_set&& pfs ); + + class const_iterator : public std::iterator { + protected: + const protocol_feature_manager* _pfm = nullptr; + std::size_t _index = 0; + + protected: + static constexpr std::size_t end_index = std::numeric_limits::max(); + + explicit const_iterator( const protocol_feature_manager* pfm, std::size_t i = end_index ) + :_pfm(pfm) + ,_index(i) + {} + + const protocol_feature* get_pointer()const; + + friend class protocol_feature_manager; + + public: + const_iterator() = default; + + friend bool operator == ( const const_iterator& lhs, const const_iterator& rhs ) { + return std::tie( lhs._pfm, lhs._index ) == std::tie( rhs._pfm, rhs._index ); + } + + friend bool operator != ( const const_iterator& lhs, const const_iterator& rhs ) { + return !(lhs == rhs); + } + + uint32_t activation_ordinal()const; + + uint32_t activation_block_num()const; + + const protocol_feature& operator*()const { + return *get_pointer(); + } + + const protocol_feature* operator->()const { + return get_pointer(); + } + + const_iterator& operator++(); + + const_iterator& operator--(); + + const_iterator operator++(int) { + const_iterator result(*this); + ++(*this); + return result; + } + + const_iterator operator--(int) { + const_iterator result(*this); + --(*this); + return result; + } + }; + + friend class const_iterator; + + using const_reverse_iterator = std::reverse_iterator; + + void init( chainbase::database& db ); + + bool is_initialized()const { return _initialized; } + + const protocol_feature_set& get_protocol_feature_set()const { return _protocol_feature_set; } + + optional get_builtin_digest( builtin_protocol_feature_t feature_codename )const { + return _protocol_feature_set.get_builtin_digest( feature_codename ); + } + + // All methods below require is_initialized() as a precondition. + + const_iterator cbegin()const; + const_iterator begin()const { return cbegin(); } + + const_iterator cend()const { return const_iterator( this ); } + const_iterator end()const { return cend(); } + + const_reverse_iterator crbegin()const { return std::make_reverse_iterator( cend() ); } + const_reverse_iterator rbegin()const { return crbegin(); } + + const_reverse_iterator crend()const { return std::make_reverse_iterator( cbegin() ); } + const_reverse_iterator rend()const { return crend(); } + + const_iterator at_activation_ordinal( uint32_t activation_ordinal )const; + + const_iterator lower_bound( uint32_t block_num )const; + + const_iterator upper_bound( uint32_t block_num )const; + + + bool is_builtin_activated( builtin_protocol_feature_t feature_codename, uint32_t current_block_num )const; + + void activate_feature( const digest_type& feature_digest, uint32_t current_block_num ); + void popped_blocks_to( uint32_t block_num ); + +protected: + + struct protocol_feature_entry { + protocol_feature_set::const_iterator iterator_to_protocol_feature; + uint32_t activation_block_num; + }; + + struct builtin_protocol_feature_entry { + static constexpr size_t no_previous = std::numeric_limits::max(); + static constexpr uint32_t not_active = std::numeric_limits::max(); + + size_t previous = no_previous; + uint32_t activation_block_num = not_active; + }; + +protected: + protocol_feature_set _protocol_feature_set; + vector _activated_protocol_features; + vector _builtin_protocol_features; + size_t _head_of_builtin_activation_list = builtin_protocol_feature_entry::no_previous; + bool _initialized = false; +}; + +} } // namespace eosio::chain + +FC_REFLECT(eosio::chain::protocol_feature_subjective_restrictions, + (earliest_allowed_activation_time)(preactivation_required)(enabled)) + +FC_REFLECT(eosio::chain::protocol_feature_base, + (protocol_feature_type)(dependencies)(description_digest)(subjective_restrictions)) + +FC_REFLECT_DERIVED(eosio::chain::builtin_protocol_feature, (eosio::chain::protocol_feature_base), + (builtin_feature_codename)) diff --git a/libraries/chain/include/eosio/chain/protocol_state_object.hpp b/libraries/chain/include/eosio/chain/protocol_state_object.hpp new file mode 100644 index 00000000000..6be252a2638 --- /dev/null +++ b/libraries/chain/include/eosio/chain/protocol_state_object.hpp @@ -0,0 +1,89 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ +#pragma once + +#include +#include +#include +#include +#include "multi_index_includes.hpp" + +namespace eosio { namespace chain { + + /** + * @class protocol_state_object + * @brief Maintains global state information about consensus protocol rules + * @ingroup object + * @ingroup implementation + */ + class protocol_state_object : public chainbase::object + { + OBJECT_CTOR(protocol_state_object, (activated_protocol_features)(preactivated_protocol_features)(whitelisted_intrinsics)) + + public: + struct activated_protocol_feature { + digest_type feature_digest; + uint32_t activation_block_num = 0; + + activated_protocol_feature() = default; + + activated_protocol_feature( const digest_type& feature_digest, uint32_t activation_block_num ) + :feature_digest( feature_digest ) + ,activation_block_num( activation_block_num ) + {} + }; + + public: + id_type id; + shared_vector activated_protocol_features; + shared_vector preactivated_protocol_features; + whitelisted_intrinsics_type whitelisted_intrinsics; + }; + + using protocol_state_multi_index = chainbase::shared_multi_index_container< + protocol_state_object, + indexed_by< + ordered_unique, + BOOST_MULTI_INDEX_MEMBER(protocol_state_object, protocol_state_object::id_type, id) + > + > + >; + + struct snapshot_protocol_state_object { + vector activated_protocol_features; + vector preactivated_protocol_features; + std::set whitelisted_intrinsics; + }; + + namespace detail { + template<> + struct snapshot_row_traits { + using value_type = protocol_state_object; + using snapshot_type = snapshot_protocol_state_object; + + static snapshot_protocol_state_object to_snapshot_row( const protocol_state_object& value, + const chainbase::database& db ); + + static void from_snapshot_row( snapshot_protocol_state_object&& row, + protocol_state_object& value, + chainbase::database& db ); + }; + } + +}} + +CHAINBASE_SET_INDEX_TYPE(eosio::chain::protocol_state_object, eosio::chain::protocol_state_multi_index) + +FC_REFLECT(eosio::chain::protocol_state_object::activated_protocol_feature, + (feature_digest)(activation_block_num) + ) + +FC_REFLECT(eosio::chain::protocol_state_object, + (activated_protocol_features)(preactivated_protocol_features)(whitelisted_intrinsics) + ) + +FC_REFLECT(eosio::chain::snapshot_protocol_state_object, + (activated_protocol_features)(preactivated_protocol_features)(whitelisted_intrinsics) + ) diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index 8ee6827efe2..a3332271fbe 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -8,7 +8,7 @@ #include -#include +#include #include #include #include @@ -95,6 +95,8 @@ namespace eosio { namespace chain { using shared_vector = boost::interprocess::vector>; template using shared_set = boost::interprocess::set, allocator>; + template + using shared_flat_multimap = boost::interprocess::flat_multimap< K, V, std::less, allocator< std::pair > >; /** * For bugs in boost interprocess we moved our blob data to shared_string @@ -187,6 +189,7 @@ namespace eosio { namespace chain { account_history_object_type, ///< Defined by history_plugin action_history_object_type, ///< Defined by history_plugin reversible_block_object_type, + protocol_state_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -223,6 +226,48 @@ namespace eosio { namespace chain { typedef vector>> extensions_type; + template + class end_insert_iterator : public std::iterator< std::output_iterator_tag, void, void, void, void > + { + protected: + Container* container; + + public: + using container_type = Container; + + explicit end_insert_iterator( Container& c ) + :container(&c) + {} + + end_insert_iterator& operator=( typename Container::const_reference value ) { + container->insert( container->cend(), value ); + return *this; + } + + end_insert_iterator& operator*() { return *this; } + end_insert_iterator& operator++() { return *this; } + end_insert_iterator operator++(int) { return *this; } + }; + + template + inline end_insert_iterator end_inserter( Container& c ) { + return end_insert_iterator( c ); + } + + template + struct enum_hash + { + static_assert( std::is_enum::value, "enum_hash can only be used on enumeration types" ); + + using underlying_type = typename std::underlying_type::type; + + std::size_t operator()(T t) const + { + return std::hash{}( static_cast(t) ); + } + }; + // enum_hash needed to support old gcc compiler of Ubuntu 16.04 + } } // eosio::chain FC_REFLECT( eosio::chain::void_t, ) diff --git a/libraries/chain/include/eosio/chain/wasm_interface.hpp b/libraries/chain/include/eosio/chain/wasm_interface.hpp index 7e6991996af..3bc971cdd3d 100644 --- a/libraries/chain/include/eosio/chain/wasm_interface.hpp +++ b/libraries/chain/include/eosio/chain/wasm_interface.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include "Runtime/Linker.h" #include "Runtime/Runtime.h" @@ -17,31 +18,49 @@ namespace eosio { namespace chain { namespace webassembly { namespace common { class intrinsics_accessor; - struct root_resolver : Runtime::Resolver { - //when validating is true; only allow "env" imports. Otherwise allow any imports. This resolver is used - //in two cases: once by the generic validating code where we only want "env" to pass; and then second in the - //wavm runtime where we need to allow linkage to injected functions - root_resolver(bool validating = false) : validating(validating) {} - bool validating; + class root_resolver : public Runtime::Resolver { + public: + // The non-default constructor puts root_resolver in a mode where it does validation, i.e. only allows "env" imports. + // This mode is used by the generic validating code that runs during setcode, where we only want "env" to pass. + // The default constructor is used when no validation is required such as when the wavm runtime needs to + // allow linkage to the intrinsics and the injected functions. + + root_resolver() {} + + root_resolver( const whitelisted_intrinsics_type& whitelisted_intrinsics ) + :whitelisted_intrinsics(&whitelisted_intrinsics) + {} bool resolve(const string& mod_name, const string& export_name, IR::ObjectType type, - Runtime::ObjectInstance*& out) override { - try { - //protect access to "private" injected functions; so for now just simply allow "env" since injected functions - // are in a different module - if(validating && mod_name != "env") - EOS_ASSERT( false, wasm_exception, "importing from module that is not 'env': ${module}.${export}", ("module",mod_name)("export",export_name) ); - - // Try to resolve an intrinsic first. - if(Runtime::IntrinsicResolver::singleton.resolve(mod_name,export_name,type, out)) { - return true; - } - - EOS_ASSERT( false, wasm_exception, "${module}.${export} unresolveable", ("module",mod_name)("export",export_name) ); - return false; - } FC_CAPTURE_AND_RETHROW( (mod_name)(export_name) ) } + Runtime::ObjectInstance*& out) override + { try { + bool fail = false; + + if( whitelisted_intrinsics != nullptr ) { + // Protect access to "private" injected functions; so for now just simply allow "env" since injected + // functions are in a different module. + EOS_ASSERT( mod_name == "env", wasm_exception, + "importing from module that is not 'env': ${module}.${export}", + ("module",mod_name)("export",export_name) ); + + // Only consider imports that are in the whitelisted set of intrinsics + fail = !is_intrinsic_whitelisted( *whitelisted_intrinsics, export_name ); + } + + // Try to resolve an intrinsic first. + if( !fail && Runtime::IntrinsicResolver::singleton.resolve( mod_name, export_name, type, out ) ) { + return true; + } + + EOS_THROW( wasm_exception, "${module}.${export} unresolveable", + ("module",mod_name)("export",export_name) ); + return false; + } FC_CAPTURE_AND_RETHROW( (mod_name)(export_name) ) } + + protected: + const whitelisted_intrinsics_type* whitelisted_intrinsics = nullptr; }; } } diff --git a/libraries/chain/include/eosio/chain/whitelisted_intrinsics.hpp b/libraries/chain/include/eosio/chain/whitelisted_intrinsics.hpp new file mode 100644 index 00000000000..96fbf2e2195 --- /dev/null +++ b/libraries/chain/include/eosio/chain/whitelisted_intrinsics.hpp @@ -0,0 +1,27 @@ + +/** + * @file + * @copyright defined in eos/LICENSE + */ +#pragma once + +#include + +namespace eosio { namespace chain { + + using whitelisted_intrinsics_type = shared_flat_multimap; + + // TODO: Improve performance by using std::string_view when we switch to C++17. + + bool is_intrinsic_whitelisted( const whitelisted_intrinsics_type& whitelisted_intrinsics, const std::string& name ); + + void add_intrinsic_to_whitelist( whitelisted_intrinsics_type& whitelisted_intrinsics, const std::string& name ); + + void remove_intrinsic_from_whitelist( whitelisted_intrinsics_type& whitelisted_intrinsics, const std::string& name ); + + void reset_intrinsic_whitelist( whitelisted_intrinsics_type& whitelisted_intrinsics, + const std::set& s ); + + std::set convert_intrinsic_whitelist_to_set( const whitelisted_intrinsics_type& whitelisted_intrinsics ); + +} } // namespace eosio::chain diff --git a/libraries/chain/protocol_feature_activation.cpp b/libraries/chain/protocol_feature_activation.cpp new file mode 100644 index 00000000000..b0b7a563073 --- /dev/null +++ b/libraries/chain/protocol_feature_activation.cpp @@ -0,0 +1,55 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ + +#include +#include + +#include + +namespace eosio { namespace chain { + + void protocol_feature_activation::reflector_init() { + static_assert( fc::raw::has_feature_reflector_init_on_unpacked_reflected_types, + "protocol_feature_activation expects FC to support reflector_init" ); + + + EOS_ASSERT( protocol_features.size() > 0, ill_formed_protocol_feature_activation, + "Protocol feature activation extension must have at least one protocol feature digest", + ); + + set s; + + for( const auto& d : protocol_features ) { + auto res = s.insert( d ); + EOS_ASSERT( res.second, ill_formed_protocol_feature_activation, + "Protocol feature digest ${d} was repeated in the protocol feature activation extension", + ("d", d) + ); + } + } + + protocol_feature_activation_set::protocol_feature_activation_set( + const protocol_feature_activation_set& orig_pfa_set, + vector additional_features, + bool enforce_disjoint + ) + { + std::sort( additional_features.begin(), additional_features.end() ); + + const auto& s1 = orig_pfa_set.protocol_features; + const auto& s2 = additional_features; + + auto expected_size = s1.size() + s2.size(); + protocol_features.reserve( expected_size ); + + std::set_union( s1.cbegin(), s1.cend(), s2.cbegin(), s2.cend(), end_inserter( protocol_features ) ); + + EOS_ASSERT( !enforce_disjoint || protocol_features.size() == expected_size, + invalid_block_header_extension, + "duplication of protocol feature digests" + ); + } + +} } // eosio::chain diff --git a/libraries/chain/protocol_feature_manager.cpp b/libraries/chain/protocol_feature_manager.cpp new file mode 100644 index 00000000000..a7fe5ecb1ad --- /dev/null +++ b/libraries/chain/protocol_feature_manager.cpp @@ -0,0 +1,556 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ + +#include +#include +#include + +#include + +#include +#include + +namespace eosio { namespace chain { + + const std::unordered_map> + builtin_protocol_feature_codenames = + boost::assign::map_list_of + ( builtin_protocol_feature_t::preactivate_feature, builtin_protocol_feature_spec{ + "PREACTIVATE_FEATURE", + fc::variant("64fe7df32e9b86be2b296b3f81dfd527f84e82b98e363bc97e40bc7a83733310").as(), + // SHA256 hash of the raw message below within the comment delimiters (do not modify message below). +/* +Builtin protocol feature: PREACTIVATE_FEATURE + +Adds privileged intrinsic to enable a contract to pre-activate a protocol feature specified by its digest. +Pre-activated protocol features must be activated in the next block. +*/ + {}, + {time_point{}, false, true} // enabled without preactivation and ready to go at any time + } ) + ( builtin_protocol_feature_t::only_link_to_existing_permission, builtin_protocol_feature_spec{ + "ONLY_LINK_TO_EXISTING_PERMISSION", + fc::variant("f3c3d91c4603cde2397268bfed4e662465293aab10cd9416db0d442b8cec2949").as(), + // SHA256 hash of the raw message below within the comment delimiters (do not modify message below). +/* +Builtin protocol feature: ONLY_LINK_TO_EXISTING_PERMISSION + +Disallows linking an action to a non-existing permission. +*/ + {} + } ) + ; + + + const char* builtin_protocol_feature_codename( builtin_protocol_feature_t codename ) { + auto itr = builtin_protocol_feature_codenames.find( codename ); + EOS_ASSERT( itr != builtin_protocol_feature_codenames.end(), protocol_feature_validation_exception, + "Unsupported builtin_protocol_feature_t passed to builtin_protocol_feature_codename: ${codename}", + ("codename", static_cast(codename)) ); + + return itr->second.codename; + } + + protocol_feature_base::protocol_feature_base( protocol_feature_t feature_type, + const digest_type& description_digest, + flat_set&& dependencies, + const protocol_feature_subjective_restrictions& restrictions ) + :description_digest( description_digest ) + ,dependencies( std::move(dependencies) ) + ,subjective_restrictions( restrictions ) + ,_type( feature_type ) + { + switch( feature_type ) { + case protocol_feature_t::builtin: + protocol_feature_type = builtin_protocol_feature::feature_type_string; + break; + default: + { + EOS_THROW( protocol_feature_validation_exception, + "Unsupported protocol_feature_t passed to constructor: ${type}", + ("type", static_cast(feature_type)) ); + } + break; + } + } + + void protocol_feature_base::reflector_init() { + static_assert( fc::raw::has_feature_reflector_init_on_unpacked_reflected_types, + "protocol_feature_activation expects FC to support reflector_init" ); + + if( protocol_feature_type == builtin_protocol_feature::feature_type_string ) { + _type = protocol_feature_t::builtin; + } else { + EOS_THROW( protocol_feature_validation_exception, + "Unsupported protocol feature type: ${type}", ("type", protocol_feature_type) ); + } + } + + const char* builtin_protocol_feature::feature_type_string = "builtin"; + + builtin_protocol_feature::builtin_protocol_feature( builtin_protocol_feature_t codename, + const digest_type& description_digest, + flat_set&& dependencies, + const protocol_feature_subjective_restrictions& restrictions ) + :protocol_feature_base( protocol_feature_t::builtin, description_digest, std::move(dependencies), restrictions ) + ,_codename(codename) + { + auto itr = builtin_protocol_feature_codenames.find( codename ); + EOS_ASSERT( itr != builtin_protocol_feature_codenames.end(), protocol_feature_validation_exception, + "Unsupported builtin_protocol_feature_t passed to constructor: ${codename}", + ("codename", static_cast(codename)) ); + + builtin_feature_codename = itr->second.codename; + } + + void builtin_protocol_feature::reflector_init() { + protocol_feature_base::reflector_init(); + + for( const auto& p : builtin_protocol_feature_codenames ) { + if( builtin_feature_codename.compare( p.second.codename ) == 0 ) { + _codename = p.first; + return; + } + } + + EOS_THROW( protocol_feature_validation_exception, + "Unsupported builtin protocol feature codename: ${codename}", + ("codename", builtin_feature_codename) ); + } + + + digest_type builtin_protocol_feature::digest()const { + digest_type::encoder enc; + fc::raw::pack( enc, _type ); + fc::raw::pack( enc, description_digest ); + fc::raw::pack( enc, dependencies ); + fc::raw::pack( enc, _codename ); + + return enc.result(); + } + + fc::variant protocol_feature::to_variant( bool include_subjective_restrictions, + fc::mutable_variant_object* additional_fields )const + { + EOS_ASSERT( builtin_feature, protocol_feature_exception, "not a builtin protocol feature" ); + + fc::mutable_variant_object mvo; + + mvo( "feature_digest", feature_digest ); + + if( additional_fields ) { + for( const auto& e : *additional_fields ) { + if( e.key().compare( "feature_digest" ) != 0 ) + mvo( e.key(), e.value() ); + } + } + + if( include_subjective_restrictions ) { + fc::mutable_variant_object subjective_restrictions; + + subjective_restrictions( "enabled", enabled ); + subjective_restrictions( "preactivation_required", preactivation_required ); + subjective_restrictions( "earliest_allowed_activation_time", earliest_allowed_activation_time ); + + mvo( "subjective_restrictions", std::move( subjective_restrictions ) ); + } + + mvo( "description_digest", description_digest ); + mvo( "dependencies", dependencies ); + mvo( "protocol_feature_type", builtin_protocol_feature::feature_type_string ); + + fc::variants specification; + auto add_to_specification = [&specification]( const char* key_name, auto&& value ) { + fc::mutable_variant_object obj; + obj( "name", key_name ); + obj( "value", std::forward( value ) ); + specification.emplace_back( std::move(obj) ); + }; + + + add_to_specification( "builtin_feature_codename", builtin_protocol_feature_codename( *builtin_feature ) ); + + mvo( "specification", std::move( specification ) ); + + return fc::variant( std::move(mvo) ); + } + + protocol_feature_set::protocol_feature_set() + { + _recognized_builtin_protocol_features.reserve( builtin_protocol_feature_codenames.size() ); + } + + + protocol_feature_set::recognized_t + protocol_feature_set::is_recognized( const digest_type& feature_digest, time_point now )const { + auto itr = _recognized_protocol_features.find( feature_digest ); + + if( itr == _recognized_protocol_features.end() ) + return recognized_t::unrecognized; + + if( !itr->enabled ) + return recognized_t::disabled; + + if( itr->earliest_allowed_activation_time > now ) + return recognized_t::too_early; + + return recognized_t::ready; + } + + optional protocol_feature_set::get_builtin_digest( builtin_protocol_feature_t feature_codename )const { + uint32_t indx = static_cast( feature_codename ); + + if( indx >= _recognized_builtin_protocol_features.size() ) + return {}; + + if( _recognized_builtin_protocol_features[indx] == _recognized_protocol_features.end() ) + return {}; + + return _recognized_builtin_protocol_features[indx]->feature_digest; + } + + const protocol_feature& protocol_feature_set::get_protocol_feature( const digest_type& feature_digest )const { + auto itr = _recognized_protocol_features.find( feature_digest ); + + EOS_ASSERT( itr != _recognized_protocol_features.end(), protocol_feature_exception, + "unrecognized protocol feature with digest: ${digest}", + ("digest", feature_digest) + ); + + return *itr; + } + + bool protocol_feature_set::validate_dependencies( + const digest_type& feature_digest, + const std::function& validator + )const { + auto itr = _recognized_protocol_features.find( feature_digest ); + + if( itr == _recognized_protocol_features.end() ) return false; + + for( const auto& d : itr->dependencies ) { + if( !validator(d) ) return false; + } + + return true; + } + + builtin_protocol_feature + protocol_feature_set::make_default_builtin_protocol_feature( + builtin_protocol_feature_t codename, + const std::function& handle_dependency + ) { + auto itr = builtin_protocol_feature_codenames.find( codename ); + + EOS_ASSERT( itr != builtin_protocol_feature_codenames.end(), protocol_feature_validation_exception, + "Unsupported builtin_protocol_feature_t: ${codename}", + ("codename", static_cast(codename)) ); + + flat_set dependencies; + dependencies.reserve( itr->second.builtin_dependencies.size() ); + + for( const auto& d : itr->second.builtin_dependencies ) { + dependencies.insert( handle_dependency( d ) ); + } + + return {itr->first, itr->second.description_digest, std::move(dependencies), itr->second.subjective_restrictions}; + } + + const protocol_feature& protocol_feature_set::add_feature( const builtin_protocol_feature& f ) { + auto builtin_itr = builtin_protocol_feature_codenames.find( f._codename ); + EOS_ASSERT( builtin_itr != builtin_protocol_feature_codenames.end(), protocol_feature_validation_exception, + "Builtin protocol feature has unsupported builtin_protocol_feature_t: ${codename}", + ("codename", static_cast( f._codename )) ); + + uint32_t indx = static_cast( f._codename ); + + if( indx < _recognized_builtin_protocol_features.size() ) { + EOS_ASSERT( _recognized_builtin_protocol_features[indx] == _recognized_protocol_features.end(), + protocol_feature_exception, + "builtin protocol feature with codename '${codename}' already added", + ("codename", f.builtin_feature_codename) ); + } + + auto feature_digest = f.digest(); + + const auto& expected_builtin_dependencies = builtin_itr->second.builtin_dependencies; + flat_set satisfied_builtin_dependencies; + satisfied_builtin_dependencies.reserve( expected_builtin_dependencies.size() ); + + for( const auto& d : f.dependencies ) { + auto itr = _recognized_protocol_features.find( d ); + EOS_ASSERT( itr != _recognized_protocol_features.end(), protocol_feature_exception, + "builtin protocol feature with codename '${codename}' and digest of ${digest} has a dependency on a protocol feature with digest ${dependency_digest} that is not recognized", + ("codename", f.builtin_feature_codename) + ("digest", feature_digest) + ("dependency_digest", d ) + ); + + if( itr->builtin_feature + && expected_builtin_dependencies.find( *itr->builtin_feature ) + != expected_builtin_dependencies.end() ) + { + satisfied_builtin_dependencies.insert( *itr->builtin_feature ); + } + } + + if( expected_builtin_dependencies.size() > satisfied_builtin_dependencies.size() ) { + flat_set missing_builtins; + missing_builtins.reserve( expected_builtin_dependencies.size() - satisfied_builtin_dependencies.size() ); + std::set_difference( expected_builtin_dependencies.begin(), expected_builtin_dependencies.end(), + satisfied_builtin_dependencies.begin(), satisfied_builtin_dependencies.end(), + end_inserter( missing_builtins ) + ); + + vector missing_builtins_with_names; + missing_builtins_with_names.reserve( missing_builtins.size() ); + for( const auto& builtin_codename : missing_builtins ) { + auto itr = builtin_protocol_feature_codenames.find( builtin_codename ); + EOS_ASSERT( itr != builtin_protocol_feature_codenames.end(), + protocol_feature_exception, + "Unexpected error" + ); + missing_builtins_with_names.emplace_back( itr->second.codename ); + } + + EOS_THROW( protocol_feature_validation_exception, + "Not all the builtin dependencies of the builtin protocol feature with codename '${codename}' and digest of ${digest} were satisfied.", + ("missing_dependencies", missing_builtins_with_names) + ); + } + + auto res = _recognized_protocol_features.insert( protocol_feature{ + feature_digest, + f.description_digest, + f.dependencies, + f.subjective_restrictions.earliest_allowed_activation_time, + f.subjective_restrictions.preactivation_required, + f.subjective_restrictions.enabled, + f._codename + } ); + + EOS_ASSERT( res.second, protocol_feature_exception, + "builtin protocol feature with codename '${codename}' has a digest of ${digest} but another protocol feature with the same digest has already been added", + ("codename", f.builtin_feature_codename)("digest", feature_digest) ); + + if( indx >= _recognized_builtin_protocol_features.size() ) { + for( auto i =_recognized_builtin_protocol_features.size(); i <= indx; ++i ) { + _recognized_builtin_protocol_features.push_back( _recognized_protocol_features.end() ); + } + } + + _recognized_builtin_protocol_features[indx] = res.first; + return *res.first; + } + + + + protocol_feature_manager::protocol_feature_manager( protocol_feature_set&& pfs ) + :_protocol_feature_set( std::move(pfs) ) + { + _builtin_protocol_features.resize( _protocol_feature_set._recognized_builtin_protocol_features.size() ); + } + + void protocol_feature_manager::init( chainbase::database& db ) { + EOS_ASSERT( !is_initialized(), protocol_feature_exception, "cannot initialize protocol_feature_manager twice" ); + + + auto reset_initialized = fc::make_scoped_exit( [this]() { _initialized = false; } ); + _initialized = true; + + for( const auto& f : db.get().activated_protocol_features ) { + activate_feature( f.feature_digest, f.activation_block_num ); + } + + reset_initialized.cancel(); + } + + const protocol_feature* protocol_feature_manager::const_iterator::get_pointer()const { + //EOS_ASSERT( _pfm, protocol_feature_iterator_exception, "cannot dereference singular iterator" ); + //EOS_ASSERT( _index != end_index, protocol_feature_iterator_exception, "cannot dereference end iterator" ); + return &*(_pfm->_activated_protocol_features[_index].iterator_to_protocol_feature); + } + + uint32_t protocol_feature_manager::const_iterator::activation_ordinal()const { + EOS_ASSERT( _pfm, + protocol_feature_iterator_exception, + "called activation_ordinal() on singular iterator" + ); + EOS_ASSERT( _index != end_index, + protocol_feature_iterator_exception, + "called activation_ordinal() on end iterator" + ); + + return _index; + } + + uint32_t protocol_feature_manager::const_iterator::activation_block_num()const { + EOS_ASSERT( _pfm, + protocol_feature_iterator_exception, + "called activation_block_num() on singular iterator" + ); + EOS_ASSERT( _index != end_index, + protocol_feature_iterator_exception, + "called activation_block_num() on end iterator" + ); + + return _pfm->_activated_protocol_features[_index].activation_block_num; + } + + protocol_feature_manager::const_iterator& protocol_feature_manager::const_iterator::operator++() { + EOS_ASSERT( _pfm, protocol_feature_iterator_exception, "cannot increment singular iterator" ); + EOS_ASSERT( _index != end_index, protocol_feature_iterator_exception, "cannot increment end iterator" ); + + ++_index; + if( _index >= _pfm->_activated_protocol_features.size() ) { + _index = end_index; + } + + return *this; + } + + protocol_feature_manager::const_iterator& protocol_feature_manager::const_iterator::operator--() { + EOS_ASSERT( _pfm, protocol_feature_iterator_exception, "cannot decrement singular iterator" ); + if( _index == end_index ) { + EOS_ASSERT( _pfm->_activated_protocol_features.size() > 0, + protocol_feature_iterator_exception, + "cannot decrement end iterator when no protocol features have been activated" + ); + _index = _pfm->_activated_protocol_features.size() - 1; + } else { + EOS_ASSERT( _index > 0, + protocol_feature_iterator_exception, + "cannot decrement iterator at the beginning of protocol feature activation list" ) + ; + --_index; + } + return *this; + } + + protocol_feature_manager::const_iterator protocol_feature_manager::cbegin()const { + if( _activated_protocol_features.size() == 0 ) { + return cend(); + } else { + return const_iterator( this, 0 ); + } + } + + protocol_feature_manager::const_iterator + protocol_feature_manager::at_activation_ordinal( uint32_t activation_ordinal )const { + if( activation_ordinal >= _activated_protocol_features.size() ) { + return cend(); + } + + return const_iterator{this, static_cast(activation_ordinal)}; + } + + protocol_feature_manager::const_iterator + protocol_feature_manager::lower_bound( uint32_t block_num )const { + const auto begin = _activated_protocol_features.cbegin(); + const auto end = _activated_protocol_features.cend(); + auto itr = std::lower_bound( begin, end, block_num, []( const protocol_feature_entry& lhs, uint32_t rhs ) { + return lhs.activation_block_num < rhs; + } ); + + if( itr == end ) { + return cend(); + } + + return const_iterator{this, static_cast(itr - begin)}; + } + + protocol_feature_manager::const_iterator + protocol_feature_manager::upper_bound( uint32_t block_num )const { + const auto begin = _activated_protocol_features.cbegin(); + const auto end = _activated_protocol_features.cend(); + auto itr = std::upper_bound( begin, end, block_num, []( uint32_t lhs, const protocol_feature_entry& rhs ) { + return lhs < rhs.activation_block_num; + } ); + + if( itr == end ) { + return cend(); + } + + return const_iterator{this, static_cast(itr - begin)}; + } + + bool protocol_feature_manager::is_builtin_activated( builtin_protocol_feature_t feature_codename, + uint32_t current_block_num )const + { + uint32_t indx = static_cast( feature_codename ); + + if( indx >= _builtin_protocol_features.size() ) return false; + + return (_builtin_protocol_features[indx].activation_block_num <= current_block_num); + } + + void protocol_feature_manager::activate_feature( const digest_type& feature_digest, + uint32_t current_block_num ) + { + EOS_ASSERT( is_initialized(), protocol_feature_exception, "protocol_feature_manager is not yet initialized" ); + + auto itr = _protocol_feature_set.find( feature_digest ); + + EOS_ASSERT( itr != _protocol_feature_set.end(), protocol_feature_exception, + "unrecognized protocol feature digest: ${digest}", ("digest", feature_digest) ); + + if( _activated_protocol_features.size() > 0 ) { + const auto& last = _activated_protocol_features.back(); + EOS_ASSERT( last.activation_block_num <= current_block_num, + protocol_feature_exception, + "last protocol feature activation block num is ${last_activation_block_num} yet " + "attempting to activate protocol feature with a current block num of ${current_block_num}" + "protocol features is ${last_activation_block_num}", + ("current_block_num", current_block_num) + ("last_activation_block_num", last.activation_block_num) + ); + } + + EOS_ASSERT( itr->builtin_feature, + protocol_feature_exception, + "invariant failure: encountered non-builtin protocol feature which is not yet supported" + ); + + uint32_t indx = static_cast( *itr->builtin_feature ); + + EOS_ASSERT( indx < _builtin_protocol_features.size(), protocol_feature_exception, + "invariant failure while trying to activate feature with digest '${digest}': " + "unsupported builtin_protocol_feature_t ${codename}", + ("digest", feature_digest) + ("codename", indx) + ); + + EOS_ASSERT( _builtin_protocol_features[indx].activation_block_num == builtin_protocol_feature_entry::not_active, + protocol_feature_exception, + "cannot activate already activated builtin feature with digest: ${digest}", + ("digest", feature_digest) + ); + + _activated_protocol_features.push_back( protocol_feature_entry{itr, current_block_num} ); + _builtin_protocol_features[indx].previous = _head_of_builtin_activation_list; + _builtin_protocol_features[indx].activation_block_num = current_block_num; + _head_of_builtin_activation_list = indx; + } + + void protocol_feature_manager::popped_blocks_to( uint32_t block_num ) { + EOS_ASSERT( is_initialized(), protocol_feature_exception, "protocol_feature_manager is not yet initialized" ); + + while( _head_of_builtin_activation_list != builtin_protocol_feature_entry::no_previous ) { + auto& e = _builtin_protocol_features[_head_of_builtin_activation_list]; + if( e.activation_block_num <= block_num ) break; + + _head_of_builtin_activation_list = e.previous; + e.previous = builtin_protocol_feature_entry::no_previous; + e.activation_block_num = builtin_protocol_feature_entry::not_active; + } + + while( _activated_protocol_features.size() > 0 + && block_num < _activated_protocol_features.back().activation_block_num ) + { + _activated_protocol_features.pop_back(); + } + } + +} } // eosio::chain diff --git a/libraries/chain/protocol_state_object.cpp b/libraries/chain/protocol_state_object.cpp new file mode 100644 index 00000000000..8a860248a3b --- /dev/null +++ b/libraries/chain/protocol_state_object.cpp @@ -0,0 +1,54 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ +#include + +namespace eosio { namespace chain { + + namespace detail { + + snapshot_protocol_state_object + snapshot_row_traits::to_snapshot_row( const protocol_state_object& value, + const chainbase::database& db ) + { + snapshot_protocol_state_object res; + + res.activated_protocol_features.reserve( value.activated_protocol_features.size() ); + for( const auto& v : value.activated_protocol_features ) { + res.activated_protocol_features.emplace_back( v ); + } + + res.preactivated_protocol_features.reserve( value.preactivated_protocol_features.size() ); + for( const auto& v : value.preactivated_protocol_features ) { + res.preactivated_protocol_features.emplace_back( v ); + } + + res.whitelisted_intrinsics = convert_intrinsic_whitelist_to_set( value.whitelisted_intrinsics ); + + return res; + } + + void + snapshot_row_traits::from_snapshot_row( snapshot_protocol_state_object&& row, + protocol_state_object& value, + chainbase::database& db ) + { + value.activated_protocol_features.clear(); + value.activated_protocol_features.reserve( row.activated_protocol_features.size() ); + for( const auto& v : row.activated_protocol_features ) { + value.activated_protocol_features.emplace_back( v ); + } + + value.preactivated_protocol_features.clear(); + value.preactivated_protocol_features.reserve( row.preactivated_protocol_features.size() ); + for( const auto& v : row.preactivated_protocol_features ) { + value.preactivated_protocol_features.emplace_back( v ); + } + + reset_intrinsic_whitelist( value.whitelisted_intrinsics, row.whitelisted_intrinsics ); + } + + } + +}} diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index feb9efbef8a..98d82bbd335 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -46,7 +47,9 @@ namespace eosio { namespace chain { wasm_validations::wasm_binary_validation validator(control, module); validator.validate(); - root_resolver resolver(true); + const auto& pso = control.db().get(); + + root_resolver resolver( pso.whitelisted_intrinsics ); LinkResult link_result = linkModule(module, resolver); //there are a couple opportunties for improvement here-- @@ -130,6 +133,15 @@ class privileged_api : public context_aware_api { EOS_ASSERT( false, unsupported_feature, "Unsupported Hardfork Detected" ); } + /** + * Pre-activates the specified protocol feature. + * Fails if the feature is unrecognized, disabled, or not allowed to be activated at the current time. + * Also fails if the feature was already activated or pre-activated. + */ + void preactivate_feature( const digest_type& feature_digest ) { + context.control.preactivate_feature( feature_digest ); + } + /** * update the resource limits associated with an account. Note these new values will not take effect until the * next resource "tick" which is currently defined as a cycle boundary inside a block. @@ -903,6 +915,13 @@ class system_api : public context_aware_api { return static_cast( context.trx_context.published.time_since_epoch().count() ); } + /** + * Returns true if the specified protocol feature is activated, false if not. + */ + bool is_feature_activated( const digest_type& feature_digest ) { + return context.control.is_protocol_feature_activated( feature_digest ); + } + }; constexpr size_t max_assert_message = 1024; @@ -1702,6 +1721,7 @@ REGISTER_INTRINSICS(privileged_api, (set_blockchain_parameters_packed, void(int,int) ) (is_privileged, int(int64_t) ) (set_privileged, void(int64_t, int) ) + (preactivate_feature, void(int) ) ); REGISTER_INJECTED_INTRINSICS(transaction_context, @@ -1778,8 +1798,9 @@ REGISTER_INTRINSICS(permission_api, REGISTER_INTRINSICS(system_api, - (current_time, int64_t() ) - (publication_time, int64_t() ) + (current_time, int64_t() ) + (publication_time, int64_t() ) + (is_feature_activated, int(int) ) ); REGISTER_INTRINSICS(context_free_system_api, diff --git a/libraries/chain/whitelisted_intrinsics.cpp b/libraries/chain/whitelisted_intrinsics.cpp new file mode 100644 index 00000000000..6a4756bf502 --- /dev/null +++ b/libraries/chain/whitelisted_intrinsics.cpp @@ -0,0 +1,108 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ +#include +#include + +namespace eosio { namespace chain { + + template + bool find_intrinsic_helper( uint64_t h, const std::string& name, Iterator& itr, const Iterator& end ) { + for( ; itr != end && itr->first == h; ++itr ) { + if( itr->second.compare( 0, itr->second.size(), name.c_str(), name.size() ) == 0 ) { + return true; + } + } + + return false; + } + + whitelisted_intrinsics_type::iterator + find_intrinsic( whitelisted_intrinsics_type& whitelisted_intrinsics, uint64_t h, const std::string& name ) + { + auto itr = whitelisted_intrinsics.lower_bound( h ); + const auto end = whitelisted_intrinsics.end(); + + if( !find_intrinsic_helper( h, name, itr, end ) ) + return end; + + return itr; + } + + whitelisted_intrinsics_type::const_iterator + find_intrinsic( const whitelisted_intrinsics_type& whitelisted_intrinsics, uint64_t h, const std::string& name ) + { + auto itr = whitelisted_intrinsics.lower_bound( h ); + const auto end = whitelisted_intrinsics.end(); + + if( !find_intrinsic_helper( h, name, itr, end ) ) + return end; + + return itr; + } + + bool is_intrinsic_whitelisted( const whitelisted_intrinsics_type& whitelisted_intrinsics, const std::string& name ) + { + uint64_t h = static_cast( std::hash{}( name ) ); + auto itr = whitelisted_intrinsics.lower_bound( h ); + const auto end = whitelisted_intrinsics.end(); + + return find_intrinsic_helper( h, name, itr, end ); + } + + + void add_intrinsic_to_whitelist( whitelisted_intrinsics_type& whitelisted_intrinsics, const std::string& name ) + { + uint64_t h = static_cast( std::hash{}( name ) ); + auto itr = find_intrinsic( whitelisted_intrinsics, h, name ); + EOS_ASSERT( itr == whitelisted_intrinsics.end(), database_exception, + "cannot add intrinsic '${name}' since it already exists in the whitelist", + ("name", name) + ); + + whitelisted_intrinsics.emplace( std::piecewise_construct, + std::forward_as_tuple( h ), + std::forward_as_tuple( name.c_str(), name.size(), + whitelisted_intrinsics.get_allocator() ) + ); + } + + void remove_intrinsic_from_whitelist( whitelisted_intrinsics_type& whitelisted_intrinsics, const std::string& name ) + { + uint64_t h = static_cast( std::hash{}( name ) ); + auto itr = find_intrinsic( whitelisted_intrinsics, h, name ); + EOS_ASSERT( itr != whitelisted_intrinsics.end(), database_exception, + "cannot remove intrinsic '${name}' since it does not exist in the whitelist", + ("name", name) + ); + + whitelisted_intrinsics.erase( itr ); + } + + void reset_intrinsic_whitelist( whitelisted_intrinsics_type& whitelisted_intrinsics, + const std::set& s ) + { + whitelisted_intrinsics.clear(); + + for( const auto& name : s ) { + uint64_t h = static_cast( std::hash{}( name ) ); + whitelisted_intrinsics.emplace( std::piecewise_construct, + std::forward_as_tuple( h ), + std::forward_as_tuple( name.c_str(), name.size(), + whitelisted_intrinsics.get_allocator() ) + ); + } + } + + std::set convert_intrinsic_whitelist_to_set( const whitelisted_intrinsics_type& whitelisted_intrinsics ) { + std::set s; + + for( const auto& p : whitelisted_intrinsics ) { + s.emplace( p.second.c_str(), p.second.size() ); + } + + return s; + } + +} } diff --git a/libraries/fc b/libraries/fc index 0c348cc9af4..2295a0d05f0 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 0c348cc9af47d71af57e6926fd64848594a78658 +Subproject commit 2295a0d05f07058c5dd27a1682294fcf428e3346 diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index 4328bda7ee8..154ebef7410 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -56,6 +56,14 @@ namespace boost { namespace test_tools { namespace tt_detail { } } } namespace eosio { namespace testing { + enum class setup_policy { + none, + old_bios_only, + preactivate_feature_only, + preactivate_feature_and_new_bios, + full + }; + std::vector read_wasm( const char* fn ); std::vector read_abi( const char* fn ); std::string read_wast( const char* fn ); @@ -67,6 +75,10 @@ namespace eosio { namespace testing { bool expect_assert_message(const fc::exception& ex, string expected); + using subjective_restriction_map = std::map; + + protocol_feature_set make_protocol_feature_set(const subjective_restriction_map& custom_subjective_restrictions = {}); + /** * @class tester * @brief provides utility function to simplify the creation of unit tests @@ -82,11 +94,14 @@ namespace eosio { namespace testing { virtual ~base_tester() {}; - void init(bool push_genesis = true, db_read_mode read_mode = db_read_mode::SPECULATIVE); + void init(const setup_policy policy = setup_policy::full, db_read_mode read_mode = db_read_mode::SPECULATIVE); void init(controller::config config, const snapshot_reader_ptr& snapshot = nullptr); + void init(controller::config config, protocol_feature_set&& pfs, const snapshot_reader_ptr& snapshot = nullptr); + void execute_setup_policy(const setup_policy policy); void close(); - void open( const snapshot_reader_ptr& snapshot ); + void open( protocol_feature_set&& pfs, const snapshot_reader_ptr& snapshot); + void open( const snapshot_reader_ptr& snapshot); bool is_same_chain( base_tester& other ); virtual signed_block_ptr produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = 0/*skip_missed_block_penalty*/ ) = 0; @@ -152,7 +167,8 @@ namespace eosio { namespace testing { return traces; } - void push_genesis_block(); + 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); @@ -280,6 +296,10 @@ namespace eosio { namespace testing { return cfg; } + void schedule_protocol_features_wo_preactivation(const vector feature_digests); + void preactivate_protocol_features(const vector feature_digests); + void preactivate_all_builtin_protocol_features(); + protected: signed_block_ptr _produce_block( fc::microseconds skip_time, bool skip_pending_trxs = false, uint32_t skip_flag = 0 ); void _start_block(fc::time_point block_time); @@ -296,18 +316,24 @@ namespace eosio { namespace testing { controller::config cfg; map chain_transactions; map last_produced_block; + public: + vector protocol_features_to_be_activated_wo_preactivation; }; class tester : public base_tester { public: - tester(bool push_genesis = true, db_read_mode read_mode = db_read_mode::SPECULATIVE ) { - init(push_genesis, read_mode); + tester(setup_policy policy = setup_policy::full, db_read_mode read_mode = db_read_mode::SPECULATIVE) { + init(policy, read_mode); } tester(controller::config config) { init(config); } + tester(controller::config config, protocol_feature_set&& pfs) { + init(config, std::move(pfs)); + } + signed_block_ptr produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = 0/*skip_missed_block_penalty*/ )override { return _produce_block(skip_time, false, skip_flag); } @@ -327,6 +353,10 @@ namespace eosio { namespace testing { class validating_tester : public base_tester { public: virtual ~validating_tester() { + if( !validating_node ) { + elog( "~validating_tester() called with empty validating_node; likely in the middle of failure" ); + return; + } try { if( num_blocks_to_producer_before_shutdown > 0 ) produce_blocks( num_blocks_to_producer_before_shutdown ); @@ -366,11 +396,11 @@ namespace eosio { namespace testing { vcfg.trusted_producers = trusted_producers; - validating_node = std::make_unique(vcfg); + validating_node = std::make_unique(vcfg, make_protocol_feature_set()); validating_node->add_indices(); validating_node->startup( []() { return false; } ); - init(true); + init(); } validating_tester(controller::config config) { @@ -381,7 +411,7 @@ namespace eosio { namespace testing { vcfg.blocks_dir = vcfg.blocks_dir.parent_path() / std::string("v_").append( vcfg.blocks_dir.filename().generic_string() ); vcfg.state_dir = vcfg.state_dir.parent_path() / std::string("v_").append( vcfg.state_dir.filename().generic_string() ); - validating_node = std::make_unique(vcfg); + validating_node = std::make_unique(vcfg, make_protocol_feature_set()); validating_node->add_indices(); validating_node->startup( []() { return false; } ); @@ -431,7 +461,7 @@ namespace eosio { namespace testing { hbh.producer == vn_hbh.producer; validating_node.reset(); - validating_node = std::make_unique(vcfg); + validating_node = std::make_unique(vcfg, make_protocol_feature_set()); validating_node->add_indices(); validating_node->startup( []() { return false; } ); diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index b09f03ef38a..93dc4b5f61f 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -79,11 +79,50 @@ namespace eosio { namespace testing { memcpy( data.data(), obj.value.data(), obj.value.size() ); } + protocol_feature_set make_protocol_feature_set(const subjective_restriction_map& custom_subjective_restrictions) { + protocol_feature_set pfs; + + map< builtin_protocol_feature_t, optional > visited_builtins; + + std::function add_builtins = + [&pfs, &visited_builtins, &add_builtins, &custom_subjective_restrictions] + ( builtin_protocol_feature_t codename ) -> digest_type { + auto res = visited_builtins.emplace( codename, optional() ); + if( !res.second ) { + EOS_ASSERT( res.first->second, protocol_feature_exception, + "invariant failure: cycle found in builtin protocol feature dependencies" + ); + return *res.first->second; + } + + auto f = protocol_feature_set::make_default_builtin_protocol_feature( codename, + [&add_builtins]( builtin_protocol_feature_t d ) { + return add_builtins( d ); + } ); + + const auto itr = custom_subjective_restrictions.find(codename); + if( itr != custom_subjective_restrictions.end() ) { + f.subjective_restrictions = itr->second; + } + + const auto& pf = pfs.add_feature( f ); + res.first->second = pf.feature_digest; + + return pf.feature_digest; + }; + + for( const auto& p : builtin_protocol_feature_codenames ) { + add_builtins( p.first ); + } + + return pfs; + } + bool base_tester::is_same_chain( base_tester& other ) { return control->head_block_id() == other.control->head_block_id(); } - void base_tester::init(bool push_genesis, db_read_mode read_mode) { + void base_tester::init(const setup_policy policy, db_read_mode read_mode) { cfg.blocks_dir = tempdir.path() / config::default_blocks_dir_name; cfg.state_dir = tempdir.path() / config::default_state_dir_name; cfg.state_size = 1024*1024*8; @@ -104,26 +143,69 @@ namespace eosio { namespace testing { } open(nullptr); - - if (push_genesis) - push_genesis_block(); + execute_setup_policy(policy); } - void base_tester::init(controller::config config, const snapshot_reader_ptr& snapshot) { cfg = config; open(snapshot); } + void base_tester::init(controller::config config, protocol_feature_set&& pfs, const snapshot_reader_ptr& snapshot) { + cfg = config; + open(std::move(pfs), snapshot); + } + + void base_tester::execute_setup_policy(const setup_policy policy) { + const auto& pfm = control->get_protocol_feature_manager(); + + auto schedule_preactivate_protocol_feature = [&]() { + auto preactivate_feature_digest = pfm.get_builtin_digest(builtin_protocol_feature_t::preactivate_feature); + FC_ASSERT( preactivate_feature_digest, "PREACTIVATE_FEATURE not found" ); + schedule_protocol_features_wo_preactivation( { *preactivate_feature_digest } ); + }; + + switch (policy) { + case setup_policy::old_bios_only: { + set_before_preactivate_bios_contract(); + break; + } + case setup_policy::preactivate_feature_only: { + schedule_preactivate_protocol_feature(); + produce_block(); // block production is required to activate protocol feature + break; + } + case setup_policy::preactivate_feature_and_new_bios: { + schedule_preactivate_protocol_feature(); + produce_block(); + set_bios_contract(); + break; + } + case setup_policy::full: { + schedule_preactivate_protocol_feature(); + produce_block(); + set_bios_contract(); + preactivate_all_builtin_protocol_features(); + produce_block(); + break; + } + case setup_policy::none: + default: + break; + }; + } void base_tester::close() { control.reset(); chain_transactions.clear(); } + void base_tester::open( const snapshot_reader_ptr& snapshot ) { + open( make_protocol_feature_set(), snapshot ); + } - void base_tester::open( const snapshot_reader_ptr& snapshot) { - control.reset( new controller(cfg) ); + void base_tester::open( protocol_feature_set&& pfs, const snapshot_reader_ptr& snapshot ) { + control.reset( new controller(cfg, std::move(pfs)) ); control->add_indices(); control->startup( []() { return false; }, snapshot); chain_transactions.clear(); @@ -200,7 +282,26 @@ namespace eosio { namespace testing { } control->abort_block(); - control->start_block( block_time, head_block_number - last_produced_block_num ); + + vector feature_to_be_activated; + // First add protocol features to be activated WITHOUT preactivation + feature_to_be_activated.insert( + feature_to_be_activated.end(), + protocol_features_to_be_activated_wo_preactivation.begin(), + protocol_features_to_be_activated_wo_preactivation.end() + ); + // Then add protocol features to be activated WITH preactivation + const auto preactivated_protocol_features = control->get_preactivated_protocol_features(); + feature_to_be_activated.insert( + feature_to_be_activated.end(), + preactivated_protocol_features.begin(), + preactivated_protocol_features.end() + ); + + control->start_block( block_time, head_block_number - last_produced_block_num, feature_to_be_activated ); + + // Clear the list, if start block finishes successfuly, the protocol features should be assumed to be activated + protocol_features_to_be_activated_wo_preactivation.clear(); } signed_block_ptr base_tester::_finish_block() { @@ -826,11 +927,17 @@ namespace eosio { namespace testing { sync_dbs(other, *this); } - void base_tester::push_genesis_block() { + void base_tester::set_before_preactivate_bios_contract() { + set_code(config::system_account_name, contracts::before_preactivate_eosio_bios_wasm()); + set_abi(config::system_account_name, contracts::before_preactivate_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()); } + vector base_tester::get_producer_keys( const vector& producer_names )const { // Create producer schedule vector schedule; @@ -853,6 +960,58 @@ namespace eosio { namespace testing { return tid; } + void base_tester::schedule_protocol_features_wo_preactivation(const vector feature_digests) { + protocol_features_to_be_activated_wo_preactivation.insert( + protocol_features_to_be_activated_wo_preactivation.end(), + feature_digests.begin(), + feature_digests.end() + ); + } + + void base_tester::preactivate_protocol_features(const vector feature_digests) { + for( const auto& feature_digest: feature_digests ) { + push_action( config::system_account_name, N(preactivate), config::system_account_name, + fc::mutable_variant_object()("feature_digest", feature_digest) ); + } + } + + void base_tester::preactivate_all_builtin_protocol_features() { + const auto& pfm = control->get_protocol_feature_manager(); + const auto& pfs = pfm.get_protocol_feature_set(); + const auto current_block_num = control->head_block_num() + (control->is_building_block() ? 1 : 0); + const auto current_block_time = ( control->is_building_block() ? control->pending_block_time() + : control->head_block_time() + fc::milliseconds(config::block_interval_ms) ); + + set preactivation_set; + vector preactivations; + + std::function add_digests = + [&pfm, &pfs, current_block_num, current_block_time, &preactivation_set, &preactivations, &add_digests] + ( const digest_type& feature_digest ) { + const auto& pf = pfs.get_protocol_feature( feature_digest ); + FC_ASSERT( pf.builtin_feature, "called add_digests on a non-builtin protocol feature" ); + if( !pf.enabled || pf.earliest_allowed_activation_time > current_block_time + || pfm.is_builtin_activated( *pf.builtin_feature, current_block_num ) ) return; + + auto res = preactivation_set.emplace( feature_digest ); + if( !res.second ) return; + + for( const auto& dependency : pf.dependencies ) { + add_digests( dependency ); + } + + preactivations.emplace_back( feature_digest ); + }; + + for( const auto& f : builtin_protocol_feature_codenames ) { + auto digest = pfs.get_builtin_digest( f.first ); + if( !digest ) continue; + add_digests( *digest ); + } + + preactivate_protocol_features( preactivations ); + } + bool fc_exception_message_is::operator()( const fc::exception& ex ) { auto message = ex.get_log().at( 0 ).get_message(); bool match = (message == expected); diff --git a/plugins/chain_api_plugin/chain_api_plugin.cpp b/plugins/chain_api_plugin/chain_api_plugin.cpp index 8b9fd3f843c..3d89a74b21e 100644 --- a/plugins/chain_api_plugin/chain_api_plugin.cpp +++ b/plugins/chain_api_plugin/chain_api_plugin.cpp @@ -84,6 +84,7 @@ void chain_api_plugin::plugin_startup() { _http_plugin.add_api({ CHAIN_RO_CALL(get_info, 200l), + CHAIN_RO_CALL(get_activated_protocol_features, 200), CHAIN_RO_CALL(get_block, 200), CHAIN_RO_CALL(get_block_header_state, 200), CHAIN_RO_CALL(get_account, 200), diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 1a414022d48..aca508db776 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -209,6 +209,8 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip cfg.add_options() ("blocks-dir", bpo::value()->default_value("blocks"), "the location of the blocks directory (absolute path or relative to application data dir)") + ("protocol-features-dir", bpo::value()->default_value("protocol_features"), + "the location of the protocol_features directory (absolute path or relative to application config dir)") ("checkpoint", bpo::value>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.") ("wasm-runtime", bpo::value()->value_name("wavm/wabt"), "Override default WASM runtime") ("abi-serializer-max-time-ms", bpo::value()->default_value(config::default_abi_serializer_max_time_ms), @@ -351,6 +353,202 @@ void clear_chainbase_files( const fc::path& p ) { fc::remove( p / "shared_memory.meta" ); } +optional read_builtin_protocol_feature( const fc::path& p ) { + try { + return fc::json::from_file( p ); + } catch( const fc::exception& e ) { + wlog( "problem encountered while reading '${path}':\n${details}", + ("path", p.generic_string())("details",e.to_detail_string()) ); + } catch( ... ) { + dlog( "unknown problem encountered while reading '${path}'", + ("path", p.generic_string()) ); + } + return {}; +} + +protocol_feature_set initialize_protocol_features( const fc::path& p, bool populate_missing_builtins = true ) { + using boost::filesystem::directory_iterator; + + protocol_feature_set pfs; + + bool directory_exists = true; + + if( fc::exists( p ) ) { + EOS_ASSERT( fc::is_directory( p ), plugin_exception, + "Path to protocol-features is not a directory: ${path}", + ("path", p.generic_string()) + ); + } else { + if( populate_missing_builtins ) + bfs::create_directory( p ); + else + directory_exists = false; + } + + auto log_recognized_protocol_feature = []( const builtin_protocol_feature& f, const digest_type& feature_digest ) { + if( f.subjective_restrictions.enabled ) { + if( f.subjective_restrictions.preactivation_required ) { + if( f.subjective_restrictions.earliest_allowed_activation_time == time_point{} ) { + ilog( "Support for builtin protocol feature '${codename}' (with digest of '${digest}') is enabled with preactivation required", + ("codename", builtin_protocol_feature_codename(f.get_codename())) + ("digest", feature_digest) + ); + } else { + ilog( "Support for builtin protocol feature '${codename}' (with digest of '${digest}') is enabled with preactivation required and with an earliest allowed activation time of ${earliest_time}", + ("codename", builtin_protocol_feature_codename(f.get_codename())) + ("digest", feature_digest) + ("earliest_time", f.subjective_restrictions.earliest_allowed_activation_time) + ); + } + } else { + if( f.subjective_restrictions.earliest_allowed_activation_time == time_point{} ) { + ilog( "Support for builtin protocol feature '${codename}' (with digest of '${digest}') is enabled without activation restrictions", + ("codename", builtin_protocol_feature_codename(f.get_codename())) + ("digest", feature_digest) + ); + } else { + ilog( "Support for builtin protocol feature '${codename}' (with digest of '${digest}') is enabled without preactivation required but with an earliest allowed activation time of ${earliest_time}", + ("codename", builtin_protocol_feature_codename(f.get_codename())) + ("digest", feature_digest) + ("earliest_time", f.subjective_restrictions.earliest_allowed_activation_time) + ); + } + } + } else { + ilog( "Recognized builtin protocol feature '${codename}' (with digest of '${digest}') but support for it is not enabled", + ("codename", builtin_protocol_feature_codename(f.get_codename())) + ("digest", feature_digest) + ); + } + }; + + map found_builtin_protocol_features; + map > builtin_protocol_features_to_add; + // The bool in the pair is set to true if the builtin protocol feature has already been visited to add + map< builtin_protocol_feature_t, optional > visited_builtins; + + // Read all builtin protocol features + if( directory_exists ) { + for( directory_iterator enditr, itr{p}; itr != enditr; ++itr ) { + auto file_path = itr->path(); + if( !fc::is_regular_file( file_path ) || file_path.extension().generic_string().compare( ".json" ) != 0 ) + continue; + + auto f = read_builtin_protocol_feature( file_path ); + + if( !f ) continue; + + auto res = found_builtin_protocol_features.emplace( f->get_codename(), file_path ); + + EOS_ASSERT( res.second, plugin_exception, + "Builtin protocol feature '${codename}' was already included from a previous_file", + ("codename", builtin_protocol_feature_codename(f->get_codename())) + ("current_file", file_path.generic_string()) + ("previous_file", res.first->second.generic_string()) + ); + + const auto feature_digest = f->digest(); + + builtin_protocol_features_to_add.emplace( std::piecewise_construct, + std::forward_as_tuple( feature_digest ), + std::forward_as_tuple( *f, false ) ); + } + } + + // Add builtin protocol features to the protocol feature manager in the right order (to satisfy dependencies) + using itr_type = map>::iterator; + std::function add_protocol_feature = + [&pfs, &builtin_protocol_features_to_add, &visited_builtins, &log_recognized_protocol_feature, &add_protocol_feature]( const itr_type& itr ) -> void { + if( itr->second.second ) { + return; + } else { + itr->second.second = true; + visited_builtins.emplace( itr->second.first.get_codename(), itr->first ); + } + + for( const auto& d : itr->second.first.dependencies ) { + auto itr2 = builtin_protocol_features_to_add.find( d ); + if( itr2 != builtin_protocol_features_to_add.end() ) { + add_protocol_feature( itr2 ); + } + } + + pfs.add_feature( itr->second.first ); + + log_recognized_protocol_feature( itr->second.first, itr->first ); + }; + + for( auto itr = builtin_protocol_features_to_add.begin(); itr != builtin_protocol_features_to_add.end(); ++itr ) { + add_protocol_feature( itr ); + } + + auto output_protocol_feature = [&p]( const builtin_protocol_feature& f, const digest_type& feature_digest ) { + static constexpr int max_tries = 10; + + string filename_base( "BUILTIN-" ); + filename_base += builtin_protocol_feature_codename( f.get_codename() ); + + string filename = filename_base+ ".json"; + int i = 0; + for( ; + i < max_tries && fc::exists( p / filename ); + ++i, filename = filename_base + "-" + std::to_string(i) + ".json" ) + ; + + EOS_ASSERT( i < max_tries, plugin_exception, + "Could not save builtin protocol feature with codename '${codename}' due to file name conflicts", + ("codename", builtin_protocol_feature_codename( f.get_codename() )) + ); + + fc::json::save_to_file( f, p / filename ); + + ilog( "Saved default specification for builtin protocol feature '${codename}' (with digest of '${digest}') to: ${path}", + ("codename", builtin_protocol_feature_codename(f.get_codename())) + ("digest", feature_digest) + ("path", (p / filename).generic_string()) + ); + }; + + std::function add_missing_builtins = + [&pfs, &visited_builtins, &output_protocol_feature, &log_recognized_protocol_feature, &add_missing_builtins, populate_missing_builtins] + ( builtin_protocol_feature_t codename ) -> digest_type { + auto res = visited_builtins.emplace( codename, optional() ); + if( !res.second ) { + EOS_ASSERT( res.first->second, protocol_feature_exception, + "invariant failure: cycle found in builtin protocol feature dependencies" + ); + return *res.first->second; + } + + auto f = protocol_feature_set::make_default_builtin_protocol_feature( codename, + [&add_missing_builtins]( builtin_protocol_feature_t d ) { + return add_missing_builtins( d ); + } ); + + if( !populate_missing_builtins ) + f.subjective_restrictions.enabled = false; + + const auto& pf = pfs.add_feature( f ); + res.first->second = pf.feature_digest; + + log_recognized_protocol_feature( f, pf.feature_digest ); + + if( populate_missing_builtins ) + output_protocol_feature( f, pf.feature_digest ); + + return pf.feature_digest; + }; + + for( const auto& p : builtin_protocol_feature_codenames ) { + auto itr = found_builtin_protocol_features.find( p.first ); + if( itr != found_builtin_protocol_features.end() ) continue; + + add_missing_builtins( p.first ); + } + + return pfs; +} + void chain_plugin::plugin_initialize(const variables_map& options) { ilog("initializing chain plugin"); @@ -401,6 +599,18 @@ void chain_plugin::plugin_initialize(const variables_map& options) { my->blocks_dir = bld; } + protocol_feature_set pfs; + { + fc::path protocol_features_dir; + auto pfd = options.at( "protocol-features-dir" ).as(); + if( pfd.is_relative()) + protocol_features_dir = app().config_dir() / pfd; + else + protocol_features_dir = pfd; + + pfs = initialize_protocol_features( protocol_features_dir ); + } + if( options.count("checkpoint") ) { auto cps = options.at("checkpoint").as>(); my->loaded_checkpoints.reserve(cps.size()); @@ -676,7 +886,7 @@ void chain_plugin::plugin_initialize(const variables_map& options) { my->chain_config->db_hugepage_paths = options.at("database-hugepage-path").as>(); #endif - my->chain.emplace( *my->chain_config ); + my->chain.emplace( *my->chain_config, std::move(pfs) ); my->chain_id.emplace( my->chain->get_chain_id()); // set up method providers @@ -1082,6 +1292,74 @@ read_only::get_info_results read_only::get_info(const read_only::get_info_params }; } +read_only::get_activated_protocol_features_results +read_only::get_activated_protocol_features( const read_only::get_activated_protocol_features_params& params )const { + read_only::get_activated_protocol_features_results result; + const auto& pfm = db.get_protocol_feature_manager(); + + uint32_t lower_bound_value = std::numeric_limits::lowest(); + uint32_t upper_bound_value = std::numeric_limits::max(); + + if( params.lower_bound ) { + lower_bound_value = *params.lower_bound; + } + + if( params.upper_bound ) { + upper_bound_value = *params.upper_bound; + } + + if( upper_bound_value < lower_bound_value ) + return result; + + auto walk_range = [&]( auto itr, auto end_itr, auto&& convert_iterator ) { + fc::mutable_variant_object mvo; + mvo( "activation_ordinal", 0 ); + mvo( "activation_block_num", 0 ); + + auto& activation_ordinal_value = mvo["activation_ordinal"]; + auto& activation_block_num_value = mvo["activation_block_num"]; + + auto cur_time = fc::time_point::now(); + auto end_time = cur_time + fc::microseconds(1000 * 10); /// 10ms max time + for( unsigned int count = 0; + cur_time <= end_time && count < params.limit && itr != end_itr; + ++itr, cur_time = fc::time_point::now() ) + { + const auto& conv_itr = convert_iterator( itr ); + activation_ordinal_value = conv_itr.activation_ordinal(); + activation_block_num_value = conv_itr.activation_block_num(); + + result.activated_protocol_features.emplace_back( conv_itr->to_variant( false, &mvo ) ); + ++count; + } + if( itr != end_itr ) { + result.more = convert_iterator( itr ).activation_ordinal() ; + } + }; + + auto get_next_if_not_end = [&pfm]( auto&& itr ) { + if( itr == pfm.cend() ) return itr; + + ++itr; + return itr; + }; + + auto lower = ( params.search_by_block_num ? pfm.lower_bound( lower_bound_value ) + : pfm.at_activation_ordinal( lower_bound_value ) ); + + auto upper = ( params.search_by_block_num ? pfm.upper_bound( lower_bound_value ) + : get_next_if_not_end( pfm.at_activation_ordinal( upper_bound_value ) ) ); + + if( params.reverse ) { + walk_range( std::make_reverse_iterator(upper), std::make_reverse_iterator(lower), + []( auto&& ritr ) { return --(ritr.base()); } ); + } else { + walk_range( lower, upper, []( auto&& itr ) { return itr; } ); + } + + return result; +} + uint64_t read_only::get_table_index_name(const read_only::get_table_rows_params& p, bool& primary) { using boost::algorithm::starts_with; // see multi_index packing of index name diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index 48b44895b8e..733bc32825f 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -104,11 +104,26 @@ class read_only { //string recent_slots; //double participation_rate = 0; optional server_version_string; - optional fork_db_head_block_num = 0; + optional fork_db_head_block_num; optional fork_db_head_block_id; }; get_info_results get_info(const get_info_params&) const; + struct get_activated_protocol_features_params { + optional lower_bound; + optional upper_bound; + uint32_t limit = 10; + bool search_by_block_num = false; + bool reverse = false; + }; + + struct get_activated_protocol_features_results { + fc::variants activated_protocol_features; + optional more; + }; + + get_activated_protocol_features_results get_activated_protocol_features( const get_activated_protocol_features_params& params )const; + struct producer_info { name producer_name; }; @@ -711,6 +726,8 @@ FC_REFLECT( eosio::chain_apis::permission, (perm_name)(parent)(required_auth) ) FC_REFLECT(eosio::chain_apis::empty, ) FC_REFLECT(eosio::chain_apis::read_only::get_info_results, (server_version)(chain_id)(head_block_num)(last_irreversible_block_num)(last_irreversible_block_id)(head_block_id)(head_block_time)(head_block_producer)(virtual_block_cpu_limit)(virtual_block_net_limit)(block_cpu_limit)(block_net_limit)(server_version_string)(fork_db_head_block_num)(fork_db_head_block_id) ) +FC_REFLECT(eosio::chain_apis::read_only::get_activated_protocol_features_params, (lower_bound)(upper_bound)(limit)(search_by_block_num)(reverse) ) +FC_REFLECT(eosio::chain_apis::read_only::get_activated_protocol_features_results, (activated_protocol_features)(more) ) FC_REFLECT(eosio::chain_apis::read_only::get_block_params, (block_num_or_id)) FC_REFLECT(eosio::chain_apis::read_only::get_block_header_state_params, (block_num_or_id)) diff --git a/plugins/producer_api_plugin/producer_api_plugin.cpp b/plugins/producer_api_plugin/producer_api_plugin.cpp index 7fcde1ac98c..611979ed6a4 100644 --- a/plugins/producer_api_plugin/producer_api_plugin.cpp +++ b/plugins/producer_api_plugin/producer_api_plugin.cpp @@ -79,17 +79,24 @@ void producer_api_plugin::plugin_startup() { CALL(producer, producer, add_greylist_accounts, INVOKE_V_R(producer, add_greylist_accounts, producer_plugin::greylist_params), 201), CALL(producer, producer, remove_greylist_accounts, - INVOKE_V_R(producer, remove_greylist_accounts, producer_plugin::greylist_params), 201), + INVOKE_V_R(producer, remove_greylist_accounts, producer_plugin::greylist_params), 201), CALL(producer, producer, get_greylist, - INVOKE_R_V(producer, get_greylist), 201), + INVOKE_R_V(producer, get_greylist), 201), CALL(producer, producer, get_whitelist_blacklist, INVOKE_R_V(producer, get_whitelist_blacklist), 201), - CALL(producer, producer, set_whitelist_blacklist, - INVOKE_V_R(producer, set_whitelist_blacklist, producer_plugin::whitelist_blacklist), 201), + CALL(producer, producer, set_whitelist_blacklist, + INVOKE_V_R(producer, set_whitelist_blacklist, producer_plugin::whitelist_blacklist), 201), CALL(producer, producer, get_integrity_hash, INVOKE_R_V(producer, get_integrity_hash), 201), CALL(producer, producer, create_snapshot, INVOKE_R_V(producer, create_snapshot), 201), + CALL(producer, producer, get_scheduled_protocol_feature_activations, + INVOKE_R_V(producer, get_scheduled_protocol_feature_activations), 201), + CALL(producer, producer, schedule_protocol_feature_activations, + INVOKE_V_R(producer, schedule_protocol_feature_activations, producer_plugin::scheduled_protocol_feature_activations), 201), + CALL(producer, producer, get_supported_protocol_features, + INVOKE_R_R(producer, get_supported_protocol_features, + producer_plugin::get_supported_protocol_features_params), 201), }); } 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 66030cc587e..55a73279dd2 100644 --- a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp +++ b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp @@ -51,6 +51,15 @@ class producer_plugin : public appbase::plugin { std::string snapshot_name; }; + struct scheduled_protocol_feature_activations { + std::vector protocol_features_to_activate; + }; + + struct get_supported_protocol_features_params { + bool exclude_disabled = false; + bool exclude_unactivatable = false; + }; + producer_plugin(); virtual ~producer_plugin(); @@ -83,6 +92,11 @@ class producer_plugin : public appbase::plugin { integrity_hash_information get_integrity_hash() const; snapshot_information create_snapshot() const; + scheduled_protocol_feature_activations get_scheduled_protocol_feature_activations() const; + void schedule_protocol_feature_activations(const scheduled_protocol_feature_activations& schedule); + + fc::variants get_supported_protocol_features( const get_supported_protocol_features_params& params ) const; + signal confirmed_block; private: std::shared_ptr my; @@ -95,4 +109,5 @@ FC_REFLECT(eosio::producer_plugin::greylist_params, (accounts)); FC_REFLECT(eosio::producer_plugin::whitelist_blacklist, (actor_whitelist)(actor_blacklist)(contract_whitelist)(contract_blacklist)(action_blacklist)(key_blacklist) ) FC_REFLECT(eosio::producer_plugin::integrity_hash_information, (head_block_id)(integrity_hash)) FC_REFLECT(eosio::producer_plugin::snapshot_information, (head_block_id)(snapshot_name)) - +FC_REFLECT(eosio::producer_plugin::scheduled_protocol_feature_activations, (protocol_features_to_activate)) +FC_REFLECT(eosio::producer_plugin::get_supported_protocol_features_params, (exclude_disabled)(exclude_unactivatable)) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index b3af3360940..cf39d0bb8df 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -142,6 +142,9 @@ 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; @@ -964,6 +967,67 @@ producer_plugin::snapshot_information producer_plugin::create_snapshot() const { return {head_id, snapshot_path}; } +producer_plugin::scheduled_protocol_feature_activations +producer_plugin::get_scheduled_protocol_feature_activations()const { + return {my->_protocol_features_to_activate}; +} + +void producer_plugin::schedule_protocol_feature_activations( const scheduled_protocol_feature_activations& schedule ) { + const chain::controller& chain = my->chain_plug->chain(); + std::set set_of_features_to_activate( schedule.protocol_features_to_activate.begin(), + schedule.protocol_features_to_activate.end() ); + EOS_ASSERT( set_of_features_to_activate.size() == schedule.protocol_features_to_activate.size(), + invalid_protocol_features_to_activate, "duplicate digests" ); + chain.validate_protocol_features( schedule.protocol_features_to_activate ); + const auto& pfs = chain.get_protocol_feature_manager().get_protocol_feature_set(); + for (auto &feature_digest : set_of_features_to_activate) { + const auto& pf = pfs.get_protocol_feature(feature_digest); + EOS_ASSERT( !pf.preactivation_required, protocol_feature_exception, + "protocol feature requires preactivation: ${digest}", + ("digest", feature_digest)); + } + my->_protocol_features_to_activate = schedule.protocol_features_to_activate; + my->_protocol_features_signaled = false; +} + +fc::variants producer_plugin::get_supported_protocol_features( const get_supported_protocol_features_params& params ) const { + fc::variants results; + const chain::controller& chain = my->chain_plug->chain(); + const auto& pfs = chain.get_protocol_feature_manager().get_protocol_feature_set(); + const auto next_block_time = chain.head_block_time() + fc::milliseconds(config::block_interval_ms); + + flat_map visited_protocol_features; + visited_protocol_features.reserve( pfs.size() ); + + std::function add_feature = + [&results, &pfs, ¶ms, next_block_time, &visited_protocol_features, &add_feature] + ( const protocol_feature& pf ) -> bool { + if( ( params.exclude_disabled || params.exclude_unactivatable ) && !pf.enabled ) return false; + if( params.exclude_unactivatable && ( next_block_time < pf.earliest_allowed_activation_time ) ) return false; + + auto res = visited_protocol_features.emplace( pf.feature_digest, false ); + if( !res.second ) return res.first->second; + + const auto original_size = results.size(); + for( const auto& dependency : pf.dependencies ) { + if( !add_feature( pfs.get_protocol_feature( dependency ) ) ) { + results.resize( original_size ); + return false; + } + } + + res.first->second = true; + results.emplace_back( pf.to_variant(true) ); + return true; + }; + + for( const auto& pf : pfs ) { + add_feature( pf ); + } + + return results; +} + optional producer_plugin_impl::calculate_next_block_time(const account_name& producer_name, const block_timestamp_type& current_block_time) const { chain::controller& chain = chain_plug->chain(); const auto& hbs = chain.head_block_state(); @@ -1123,7 +1187,43 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { } chain.abort_block(); - chain.start_block(block_time, blocks_to_confirm); + + auto features_to_activate = chain.get_preactivated_protocol_features(); + if( _pending_block_mode == pending_block_mode::producing && _protocol_features_to_activate.size() > 0 ) { + bool drop_features_to_activate = false; + try { + chain.validate_protocol_features( _protocol_features_to_activate ); + } catch( const fc::exception& e ) { + wlog( "protocol features to activate are no longer all valid: ${details}", + ("details",e.to_detail_string()) ); + drop_features_to_activate = true; + } + + if( drop_features_to_activate ) { + _protocol_features_to_activate.clear(); + } else { + auto protocol_features_to_activate = _protocol_features_to_activate; // do a copy as pending_block might be aborted + if( features_to_activate.size() > 0 ) { + protocol_features_to_activate.reserve( protocol_features_to_activate.size() + + features_to_activate.size() ); + std::set set_of_features_to_activate( protocol_features_to_activate.begin(), + protocol_features_to_activate.end() ); + for( const auto& f : features_to_activate ) { + auto res = set_of_features_to_activate.insert( f ); + if( res.second ) { + protocol_features_to_activate.push_back( f ); + } + } + features_to_activate.clear(); + } + std::swap( features_to_activate, protocol_features_to_activate ); + _protocol_features_signaled = true; + ilog( "signaling activation of the following protocol features in block ${num}: ${features_to_activate}", + ("num", hbs->block_num + 1)("features_to_activate", features_to_activate) ); + } + } + + chain.start_block( block_time, blocks_to_confirm, features_to_activate ); } FC_LOG_AND_DROP(); if( chain.is_building_block() ) { @@ -1572,6 +1672,11 @@ void producer_plugin_impl::produce_block() { 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"); + if (_protocol_features_signaled) { + _protocol_features_to_activate.clear(); // clear _protocol_features_to_activate as it is already set in pending_block + _protocol_features_signaled = false; + } + //idump( (fc::time_point::now() - chain.pending_block_time()) ); chain.finalize_block( [&]( const digest_type& d ) { auto debug_logger = maybe_make_debug_time_logger(); diff --git a/testnet.template b/testnet.template index f9e793a5c89..ab9051f0601 100644 --- a/testnet.template +++ b/testnet.template @@ -12,6 +12,13 @@ if [ -z "$biosport" ]; then biosport=9776 fi +bioscontractpath=$BIOS_CONTRACT_PATH +if [ -z "$bioscontractpath" ]; then + bioscontractpath="unittests/contracts/eosio.bios" +fi + +featuredigests=($FEATURE_DIGESTS) + wddir=eosio-ignition-wd wdaddr=localhost:8899 wdurl=http://$wdaddr @@ -75,7 +82,13 @@ wcmd create --to-console -n ignition # ------ DO NOT ALTER THE NEXT LINE ------- ###INSERT prodkeys -ecmd set contract eosio unittests/contracts/eosio.bios eosio.bios.wasm eosio.bios.abi +ecmd set contract eosio $bioscontractpath eosio.bios.wasm eosio.bios.abi + +# Preactivate all digests +for digest in "${featuredigests[@]}"; +do +ecmd push action eosio preactivate "{\"feature_digest\":\"$digest\"}" -p eosio +done # Create required system accounts ecmd create key --to-console diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9142e47726d..f357261b627 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,10 +41,12 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_run_remote_test.py ${CMAKE_CUR configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_under_min_avail_ram.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_under_min_avail_ram.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_voting_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_voting_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_irreversible_mode_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_irreversible_mode_test.py COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_protocol_feature_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_protocol_feature_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/consensus-validation-malicious-producers.py ${CMAKE_CURRENT_BINARY_DIR}/consensus-validation-malicious-producers.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/validate-dirty-db.py ${CMAKE_CURRENT_BINARY_DIR}/validate-dirty-db.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/launcher_test.py ${CMAKE_CURRENT_BINARY_DIR}/launcher_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/db_modes_test.sh ${CMAKE_CURRENT_BINARY_DIR}/db_modes_test.sh COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/prod_preactivation_test.py ${CMAKE_CURRENT_BINARY_DIR}/prod_preactivation_test.py COPYONLY) #To run plugin_test with all log from blockchain displayed, put --verbose after --, i.e. plugin_test -- --verbose add_test(NAME plugin_test COMMAND plugin_test --report_level=detailed --color_output) @@ -65,6 +67,11 @@ if(BUILD_MONGO_DB_PLUGIN) set_property(TEST nodeos_run_test-mongodb PROPERTY LABELS nonparallelizable_tests) endif() +add_test(NAME producer-preactivate-feature-test COMMAND tests/prod_preactivation_test.py --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST producer-preactivate-feature-test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME nodeos_protocol_feature_test COMMAND tests/nodeos_protocol_feature_test.py -v --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_protocol_feature_test PROPERTY LABELS nonparallelizable_tests) + add_test(NAME distributed-transactions-test COMMAND tests/distributed-transactions-test.py -d 2 -p 4 -n 6 -v --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST distributed-transactions-test PROPERTY LABELS nonparallelizable_tests) add_test(NAME distributed-transactions-bnet-test COMMAND tests/distributed-transactions-test.py -d 2 -p 1 -n 4 --p2p-plugin bnet -v --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/tests/Cluster.py b/tests/Cluster.py index e7740d19cd3..e5d669ce6e8 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -19,6 +19,21 @@ from Node import Node from WalletMgr import WalletMgr +# Protocol Feature Setup Policy +class PFSetupPolicy: + NONE = 0 + PREACTIVATE_FEATURE_ONLY = 1 + FULL = 2 # This will only happen if the cluster is bootstrapped (i.e. dontBootstrap == False) + @staticmethod + def hasPreactivateFeature(policy): + return policy == PFSetupPolicy.PREACTIVATE_FEATURE_ONLY or \ + policy == PFSetupPolicy.FULL + @staticmethod + def isValid(policy): + return policy == PFSetupPolicy.NONE or \ + policy == PFSetupPolicy.PREACTIVATE_FEATURE_ONLY or \ + policy == PFSetupPolicy.FULL + # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-public-methods class Cluster(object): @@ -99,7 +114,8 @@ def setWalletMgr(self, walletMgr): # pylint: disable=too-many-branches # pylint: disable=too-many-statements def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", p2pPlugin="net", delay=1, onlyBios=False, dontBootstrap=False, - totalProducers=None, extraNodeosArgs=None, useBiosBootFile=True, specificExtraNodeosArgs=None): + totalProducers=None, extraNodeosArgs=None, useBiosBootFile=True, specificExtraNodeosArgs=None, + pfSetupPolicy = PFSetupPolicy.FULL): """Launch cluster. pnodes: producer nodes count totalNodes: producer + non-producer nodes count @@ -108,15 +124,17 @@ def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", p2pPlugin="ne delay: delay between individual nodes launch (as defined by launcher) delay 0 exposes a bootstrap bug where producer handover may have a large gap confusing nodes and bringing system to a halt. onlyBios: When true, only loads the bios contract (and not more full bootstrapping). - dontBootstrap: When true, don't do any bootstrapping at all. + dontBootstrap: When true, don't do any bootstrapping at all. (even bios is not uploaded) extraNodeosArgs: string of arguments to pass through to each nodoes instance (via --nodeos flag on launcher) useBiosBootFile: determines which of two bootstrap methods is used (when both dontBootstrap and onlyBios are false). The default value of true uses the bios_boot.sh file generated by the launcher. A value of false uses manual bootstrapping in this script, which does not do things like stake votes for producers. specificExtraNodeosArgs: dictionary of arguments to pass to a specific node (via --specific-num and --specific-nodeos flags on launcher), example: { "5" : "--plugin eosio::test_control_api_plugin" } + pfSetupPolicy: determine the protocol feature setup policy (none, preactivate_feature_only, or full) """ assert(isinstance(topo, str)) + assert PFSetupPolicy.isValid(pfSetupPolicy) if not self.localCluster: Utils.Print("WARNING: Cluster not local, not launching %s." % (Utils.EosServerName)) @@ -161,6 +179,8 @@ def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", p2pPlugin="ne nodeosArgs += extraNodeosArgs if Utils.Debug: nodeosArgs += " --contracts-console" + if PFSetupPolicy.hasPreactivateFeature(pfSetupPolicy): + nodeosArgs += " --plugin eosio::producer_api_plugin" if nodeosArgs: cmdArr.append("--nodeos") @@ -320,12 +340,12 @@ def connectGroup(group, producerNodes, bridgeNodes) : self.nodes=nodes - if onlyBios: - biosNode=Node(Cluster.__BiosHost, Cluster.__BiosPort, walletMgr=self.walletMgr) - if not biosNode.checkPulse(): - Utils.Print("ERROR: Bios node doesn't appear to be running...") - return False + biosNode=self.discoverBiosNode() + if not biosNode or not biosNode.checkPulse(): + Utils.Print("ERROR: Bios node doesn't appear to be running...") + return False + if onlyBios: self.nodes=[biosNode] # ensure cluster node are inter-connected by ensuring everyone has block 1 @@ -334,24 +354,25 @@ def connectGroup(group, producerNodes, bridgeNodes) : Utils.Print("ERROR: Cluster doesn't seem to be in sync. Some nodes missing block 1") return False + if PFSetupPolicy.hasPreactivateFeature(pfSetupPolicy): + Utils.Print("Activate Preactivate Feature.") + biosNode.activatePreactivateFeature() + if dontBootstrap: Utils.Print("Skipping bootstrap.") + self.biosNode=biosNode return True Utils.Print("Bootstrap cluster.") if onlyBios or not useBiosBootFile: - self.biosNode=Cluster.bootstrap(totalNodes, prodCount, totalProducers, Cluster.__BiosHost, Cluster.__BiosPort, self.walletMgr, onlyBios) - if self.biosNode is None: - Utils.Print("ERROR: Bootstrap failed.") - return False + self.biosNode=self.bootstrap(biosNode, totalNodes, prodCount, totalProducers, pfSetupPolicy, onlyBios) else: self.useBiosBootFile=True - self.biosNode=Cluster.bios_bootstrap(totalNodes, Cluster.__BiosHost, Cluster.__BiosPort, self.walletMgr) - if self.biosNode is None: - Utils.Print("ERROR: Bootstrap failed.") - return False + self.biosNode=self.bios_bootstrap(biosNode, totalNodes, pfSetupPolicy) - self.discoverBiosNodePid() + if self.biosNode is None: + Utils.Print("ERROR: Bootstrap failed.") + return False # validate iniX accounts can be retrieved @@ -834,19 +855,26 @@ def parseClusterKeys(totalNodes): return producerKeys - @staticmethod - def bios_bootstrap(totalNodes, biosHost, biosPort, walletMgr, silent=False): + def bios_bootstrap(self, biosNode, totalNodes, pfSetupPolicy, silent=False): """Bootstrap cluster using the bios_boot.sh script generated by eosio-launcher.""" Utils.Print("Starting cluster bootstrap.") - biosNode=Node(biosHost, biosPort, walletMgr=walletMgr) - if not biosNode.checkPulse(): - Utils.Print("ERROR: Bios node doesn't appear to be running...") - return None + assert PFSetupPolicy.isValid(pfSetupPolicy) cmd="bash bios_boot.sh" if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - if 0 != subprocess.call(cmd.split(), stdout=Utils.FNull): + env = { + "BIOS_CONTRACT_PATH": "unittests/contracts/old_versions/v1.6.0-rc3/eosio.bios", + "FEATURE_DIGESTS": "" + } + if PFSetupPolicy.hasPreactivateFeature(pfSetupPolicy): + env["BIOS_CONTRACT_PATH"] = "unittests/contracts/eosio.bios" + + if pfSetupPolicy == PFSetupPolicy.FULL: + allBuiltinProtocolFeatureDigests = biosNode.getAllBuiltinFeatureDigestsToPreactivate() + env["FEATURE_DIGESTS"] = " ".join(allBuiltinProtocolFeatureDigests) + + if 0 != subprocess.call(cmd.split(), stdout=Utils.FNull, env=env): if not silent: Utils.Print("Launcher failed to shut down eos cluster.") return None @@ -864,14 +892,14 @@ def bios_bootstrap(totalNodes, biosHost, biosPort, walletMgr, silent=False): Utils.Print("ERROR: Failed to parse private keys from cluster config files.") return None - walletMgr.killall() - walletMgr.cleanup() + self.walletMgr.killall() + self.walletMgr.cleanup() - if not walletMgr.launch(): + if not self.walletMgr.launch(): Utils.Print("ERROR: Failed to launch bootstrap wallet.") return None - ignWallet=walletMgr.create("ignition") + ignWallet=self.walletMgr.create("ignition") if ignWallet is None: Utils.Print("ERROR: Failed to create ignition wallet.") return None @@ -885,7 +913,7 @@ def bios_bootstrap(totalNodes, biosHost, biosPort, walletMgr, silent=False): eosioAccount.activePublicKey=eosioKeys["public"] producerKeys.pop(eosioName) - if not walletMgr.importKey(eosioAccount, ignWallet): + if not self.walletMgr.importKey(eosioAccount, ignWallet): Utils.Print("ERROR: Failed to import %s account keys into ignition wallet." % (eosioName)) return None @@ -915,20 +943,15 @@ def bios_bootstrap(totalNodes, biosHost, biosPort, walletMgr, silent=False): return biosNode - @staticmethod - def bootstrap(totalNodes, prodCount, totalProducers, biosHost, biosPort, walletMgr, onlyBios=False): + def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPolicy, onlyBios=False): """Create 'prodCount' init accounts and deposits 10000000000 SYS in each. If prodCount is -1 will initialize all possible producers. Ensure nodes are inter-connected prior to this call. One way to validate this will be to check if every node has block 1.""" Utils.Print("Starting cluster bootstrap.") + assert PFSetupPolicy.isValid(pfSetupPolicy) if totalProducers is None: totalProducers=totalNodes - biosNode=Node(biosHost, biosPort, walletMgr=walletMgr) - if not biosNode.checkPulse(): - Utils.Print("ERROR: Bios node doesn't appear to be running...") - return None - producerKeys=Cluster.parseClusterKeys(totalNodes) # should have totalNodes node plus bios node if producerKeys is None: @@ -938,14 +961,14 @@ def bootstrap(totalNodes, prodCount, totalProducers, biosHost, biosPort, walletM Utils.Print("ERROR: Failed to parse %d producer keys from cluster config files, only found %d." % (totalProducers+1,len(producerKeys))) return None - walletMgr.killall() - walletMgr.cleanup() + self.walletMgr.killall() + self.walletMgr.cleanup() - if not walletMgr.launch(): + if not self.walletMgr.launch(): Utils.Print("ERROR: Failed to launch bootstrap wallet.") return None - ignWallet=walletMgr.create("ignition") + ignWallet=self.walletMgr.create("ignition") eosioName="eosio" eosioKeys=producerKeys[eosioName] @@ -955,12 +978,16 @@ def bootstrap(totalNodes, prodCount, totalProducers, biosHost, biosPort, walletM eosioAccount.activePrivateKey=eosioKeys["private"] eosioAccount.activePublicKey=eosioKeys["public"] - if not walletMgr.importKey(eosioAccount, ignWallet): + if not self.walletMgr.importKey(eosioAccount, ignWallet): Utils.Print("ERROR: Failed to import %s account keys into ignition wallet." % (eosioName)) return None contract="eosio.bios" contractDir="unittests/contracts/%s" % (contract) + if PFSetupPolicy.hasPreactivateFeature(pfSetupPolicy): + contractDir="unittests/contracts/%s" % (contract) + else: + contractDir="unittests/contracts/old_versions/v1.6.0-rc3/%s" % (contract) wasmFile="%s.wasm" % (contract) abiFile="%s.abi" % (contract) Utils.Print("Publish %s contract" % (contract)) @@ -969,6 +996,9 @@ def bootstrap(totalNodes, prodCount, totalProducers, biosHost, biosPort, walletM Utils.Print("ERROR: Failed to publish contract %s." % (contract)) return None + if pfSetupPolicy == PFSetupPolicy.FULL: + biosNode.preactivateAllBuiltinProtocolFeature() + Node.validateTransaction(trans) Utils.Print("Creating accounts: %s " % ", ".join(producerKeys.keys())) @@ -1199,7 +1229,7 @@ def myFunc(): @staticmethod def pgrepEosServerPattern(nodeInstance): dataLocation=Cluster.__dataDir + Cluster.nodeExtensionToName(nodeInstance) - return r"[\n]?(\d+) (.* --data-dir %s .*)\n" % (dataLocation) + return r"[\n]?(\d+) (.* --data-dir %s.*)\n" % (dataLocation) # Populates list of EosInstanceInfo objects, matched to actual running instances def discoverLocalNodes(self, totalNodes, timeout=None): @@ -1228,15 +1258,16 @@ def discoverLocalNodes(self, totalNodes, timeout=None): if Utils.Debug: Utils.Print("Found %d nodes" % (len(nodes))) return nodes - def discoverBiosNodePid(self, timeout=None): + def discoverBiosNode(self, timeout=None): psOut=Cluster.pgrepEosServers(timeout=timeout) pattern=Cluster.pgrepEosServerPattern("bios") Utils.Print("pattern={\n%s\n}, psOut=\n%s\n" % (pattern,psOut)) m=re.search(pattern, psOut, re.MULTILINE) if m is None: Utils.Print("ERROR: Failed to find %s pid. Pattern %s" % (Utils.EosServerName, pattern)) + return None else: - self.biosNode.pid=int(m.group(1)) + return Node(Cluster.__BiosHost, Cluster.__BiosPort, pid=int(m.group(1)), cmd=m.group(2), walletMgr=self.walletMgr) # Kills a percentange of Eos instances starting from the tail and update eosInstanceInfos state def killSomeEosInstances(self, killCount, killSignalStr=Utils.SigKillTag): @@ -1569,4 +1600,14 @@ def stripValues(lowestMaxes,greaterThan): @staticmethod def getDataDir(nodeId): - return os.path.abspath(os.path.join(Cluster.__dataDir, "node_%02d" % (nodeId))) + assert isinstance(nodeId, int) or (isinstance(nodeId, str) and nodeId == "bios"), "Invalid Node ID is passed" + extName = nodeId + if isinstance(nodeId, int): extName = "%02d" % (nodeId) + return os.path.abspath(os.path.join(Cluster.__dataDir, "node_{}".format(extName))) + + @staticmethod + def getConfigDir(nodeId): + assert isinstance(nodeId, int) or (isinstance(nodeId, str) and nodeId == "bios"), "Invalid Node ID is passed" + extName = nodeId + if isinstance(nodeId, int): extName = "%02d" % (nodeId) + return os.path.abspath(os.path.join(Cluster.__configDir, "node_{}".format(extName))) diff --git a/tests/Node.py b/tests/Node.py index 16eede1b46a..cf65e2282f0 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -7,6 +7,10 @@ import datetime import json import signal +import urllib.request +import urllib.parse +from urllib.error import HTTPError +import tempfile from core_symbol import CORE_SYMBOL from testUtils import Utils @@ -1303,8 +1307,9 @@ def relaunch(self, nodeId, chainArg, newChain=False, timeout=Utils.systemWaitTim assert(self.pid is None) assert(self.killed) + assert isinstance(nodeId, int) or (isinstance(nodeId, str) and nodeId == "bios"), "Invalid Node ID is passed" - if Utils.Debug: Utils.Print("Launching node process, Id: %d" % (nodeId)) + if Utils.Debug: Utils.Print("Launching node process, Id: {}".format(nodeId)) cmdArr=[] myCmd=self.cmd @@ -1336,7 +1341,10 @@ def relaunch(self, nodeId, chainArg, newChain=False, timeout=Utils.systemWaitTim myCmd=" ".join(cmdArr) - dataDir="var/lib/node_%02d" % (nodeId) + if nodeId == "bios": + dataDir="var/lib/node_bios" + else: + dataDir="var/lib/node_%02d" % (nodeId) dt = datetime.datetime.now() dateStr="%d_%02d_%02d_%02d_%02d_%02d" % ( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) @@ -1406,3 +1414,111 @@ def reportStatus(self): status="last getInfo returned None" if not self.infoValid else "at last call to getInfo" Utils.Print(" hbn : %s (%s)" % (self.lastRetrievedHeadBlockNum, status)) Utils.Print(" lib : %s (%s)" % (self.lastRetrievedLIB, status)) + + def sendRpcApi(self, relativeUrl, data={}): + url = urllib.parse.urljoin(self.endpointHttp, relativeUrl) + req = urllib.request.Request(url) + req.add_header('Content-Type', 'application/json; charset=utf-8') + reqData = json.dumps(data).encode("utf-8") + rpcApiResult = None + try: + response = urllib.request.urlopen(req, reqData) + rpcApiResult = json.loads(response.read().decode("utf-8")) + except HTTPError as e: + Utils.Print("Fail to send RPC API to {} with data {} ({})".format(url, data, e.read())) + raise e + except Exception as e: + Utils.Print("Fail to send RPC API to {} with data {} ({})".format(url, data, e)) + raise e + return rpcApiResult + + # Require producer_api_plugin + def scheduleProtocolFeatureActivations(self, featureDigests=[]): + self.sendRpcApi("v1/producer/schedule_protocol_feature_activations", {"protocol_features_to_activate": featureDigests}) + + # Require producer_api_plugin + def getSupportedProtocolFeatures(self, excludeDisabled=False, excludeUnactivatable=False): + param = { + "exclude_disabled": excludeDisabled, + "exclude_unactivatable": excludeUnactivatable + } + res = self.sendRpcApi("v1/producer/get_supported_protocol_features", param) + return res + + # This will return supported protocol features in a dict (feature codename as the key), i.e. + # { + # "PREACTIVATE_FEATURE": {...}, + # "ONLY_LINK_TO_EXISTING_PERMISSION": {...}, + # } + # Require producer_api_plugin + def getSupportedProtocolFeatureDict(self, excludeDisabled=False, excludeUnactivatable=False): + protocolFeatureDigestDict = {} + supportedProtocolFeatures = self.getSupportedProtocolFeatures(excludeDisabled, excludeUnactivatable) + for protocolFeature in supportedProtocolFeatures: + for spec in protocolFeature["specification"]: + if (spec["name"] == "builtin_feature_codename"): + codename = spec["value"] + protocolFeatureDigestDict[codename] = protocolFeature + break + return protocolFeatureDigestDict + + def waitForHeadToAdvance(self, timeout=6): + currentHead = self.getHeadBlockNum() + def isHeadAdvancing(): + return self.getHeadBlockNum() > currentHead + Utils.waitForBool(isHeadAdvancing, timeout) + + def waitForLibToAdvance(self, timeout=6): + currentLib = self.getIrreversibleBlockNum() + def isLibAdvancing(): + return self.getIrreversibleBlockNum() > currentLib + Utils.waitForBool(isLibAdvancing, timeout) + + # Require producer_api_plugin + def activatePreactivateFeature(self): + protocolFeatureDigestDict = self.getSupportedProtocolFeatureDict() + preactivateFeatureDigest = protocolFeatureDigestDict["PREACTIVATE_FEATURE"]["feature_digest"] + assert preactivateFeatureDigest + + self.scheduleProtocolFeatureActivations([preactivateFeatureDigest]) + + # Wait for the next block to be produced so the scheduled protocol feature is activated + self.waitForHeadToAdvance() + + # Return an array of feature digests to be preactivated + # Require producer_api_plugin + def getAllBuiltinFeatureDigestsToPreactivate(self): + protocolFeatures = [] + protocolFeatureDict = self.getSupportedProtocolFeatureDict() + for k, v in protocolFeatureDict.items(): + # Filter out "PREACTIVATE_FEATURE" + if k != "PREACTIVATE_FEATURE": + protocolFeatures.append(v["feature_digest"]) + return protocolFeatures + + # Require PREACTIVATE_FEATURE to be activated and require eosio.bios with preactivate_feature + def preactivateProtocolFeatures(self, featureDigests:list): + for digest in featureDigests: + Utils.Print("push preactivate action with digest {}".format(digest)) + data="{{\"feature_digest\":{}}}".format(digest) + opts="--permission eosio@active" + trans=self.pushMessage("eosio", "preactivate", data, opts) + if trans is None or not trans[0]: + Utils.Print("ERROR: Failed to preactive digest {}".format(digest)) + return None + self.waitForHeadToAdvance() + + # Require PREACTIVATE_FEATURE to be activated and require eosio.bios with preactivate_feature + def preactivateAllBuiltinProtocolFeature(self): + allBuiltinProtocolFeatureDigests = self.getAllBuiltinFeatureDigestsToPreactivate() + self.preactivateProtocolFeatures(allBuiltinProtocolFeatureDigests) + + def getLatestBlockHeaderState(self): + headBlockNum = self.getHeadBlockNum() + cmdDesc = "get block {} --header-state".format(headBlockNum) + latestBlockHeaderState = self.processCleosCmd(cmdDesc, cmdDesc) + return latestBlockHeaderState + + def getActivatedProtocolFeatures(self): + latestBlockHeaderState = self.getLatestBlockHeaderState() + return latestBlockHeaderState["activated_protocol_features"]["protocol_features"] diff --git a/tests/nodeos_forked_chain_test.py b/tests/nodeos_forked_chain_test.py index 4ef22ab082f..77b1f96e28d 100755 --- a/tests/nodeos_forked_chain_test.py +++ b/tests/nodeos_forked_chain_test.py @@ -156,7 +156,7 @@ def getMinHeadAndLib(prodNodes): # "bridge" shape connects defprocera through defproducerk (in node0) to each other and defproducerl through defproduceru (in node01) # and the only connection between those 2 groups is through the bridge node - if cluster.launch(prodCount=prodCount, onlyBios=False, topo="bridge", pnodes=totalProducerNodes, + if cluster.launch(prodCount=prodCount, topo="bridge", pnodes=totalProducerNodes, totalNodes=totalNodes, totalProducers=totalProducers, p2pPlugin=p2pPlugin, useBiosBootFile=False, specificExtraNodeosArgs=specificExtraNodeosArgs) is False: Utils.cmdError("launcher") diff --git a/tests/nodeos_protocol_feature_test.py b/tests/nodeos_protocol_feature_test.py new file mode 100755 index 00000000000..2c581b39dbd --- /dev/null +++ b/tests/nodeos_protocol_feature_test.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +from testUtils import Utils +from Cluster import Cluster, PFSetupPolicy +from TestHelper import TestHelper +from WalletMgr import WalletMgr +from Node import Node + +import signal +import json +from os.path import join +from datetime import datetime + +# Parse command line arguments +args = TestHelper.parse_args({"-v","--clean-run","--dump-error-details","--leave-running","--keep-logs"}) +Utils.Debug = args.v +killAll=args.clean_run +dumpErrorDetails=args.dump_error_details +dontKill=args.leave_running +killEosInstances=not dontKill +killWallet=not dontKill +keepLogs=args.keep_logs + +# The following test case will test the Protocol Feature JSON reader of the blockchain +def modifyPFSubjectiveRestrictions(nodeId, featureCodename, subjectiveRestrictions): + jsonPath = join(Cluster.getConfigDir(nodeId), "protocol_features", "BUILTIN-{}.json".format(featureCodename)) + protocolFeatureJson = [] + with open(jsonPath) as f: + protocolFeatureJson = json.load(f) + protocolFeatureJson["subjective_restrictions"] = subjectiveRestrictions + with open(jsonPath, "w") as f: + json.dump(protocolFeatureJson, f, indent=2) + +def restartNode(node: Node, nodeId, chainArg=None, addOrSwapFlags=None): + if not node.killed: + node.kill(signal.SIGTERM) + isRelaunchSuccess = node.relaunch(nodeId, chainArg, addOrSwapFlags=addOrSwapFlags, timeout=5, cachePopen=True) + assert isRelaunchSuccess, "Fail to relaunch" + +walletMgr=WalletMgr(True) +cluster=Cluster(walletd=True) +cluster.setWalletMgr(walletMgr) + +# List to contain the test result message +testSuccessful = False +try: + TestHelper.printSystemInfo("BEGIN") + cluster.killall(allInstances=killAll) + cluster.cleanup() + cluster.launch(extraNodeosArgs=" --plugin eosio::producer_api_plugin ", + dontBootstrap=True, + pfSetupPolicy=PFSetupPolicy.NONE) + biosNode = cluster.biosNode + + # Modify the JSON file and then restart the node so it updates the internal state + newSubjectiveRestrictions = { + "earliest_allowed_activation_time": "2030-01-01T00:00:00.000", + "preactivation_required": True, + "enabled": False + } + modifyPFSubjectiveRestrictions("bios", "PREACTIVATE_FEATURE", newSubjectiveRestrictions) + restartNode(biosNode, "bios") + + supportedProtocolFeatureDict = biosNode.getSupportedProtocolFeatureDict() + preactivateFeatureSubjectiveRestrictions = supportedProtocolFeatureDict["PREACTIVATE_FEATURE"]["subjective_restrictions"] + + # Ensure that the PREACTIVATE_FEATURE subjective restrictions match the value written in the JSON + assert preactivateFeatureSubjectiveRestrictions == newSubjectiveRestrictions,\ + "PREACTIVATE_FEATURE subjective restrictions are not updated according to the JSON" + + testSuccessful = True +finally: + TestHelper.shutdown(cluster, walletMgr, testSuccessful, killEosInstances, killWallet, keepLogs, killAll, dumpErrorDetails) + +exitCode = 0 if testSuccessful else 1 +exit(exitCode) diff --git a/tests/prod_preactivation_test.py b/tests/prod_preactivation_test.py new file mode 100755 index 00000000000..3fc31853524 --- /dev/null +++ b/tests/prod_preactivation_test.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 + +from testUtils import Utils +from Cluster import Cluster, PFSetupPolicy +from WalletMgr import WalletMgr +from Node import Node +from Node import ReturnType +from TestHelper import TestHelper + +import decimal +import re +import time + +############################################################### +# prod_preactivation_test +# --dump-error-details +# --keep-logs +############################################################### + +Print=Utils.Print +errorExit=Utils.errorExit +cmdError=Utils.cmdError +from core_symbol import CORE_SYMBOL + +args = TestHelper.parse_args({"--host","--port","--defproducera_prvt_key","--defproducerb_prvt_key","--mongodb" + ,"--dump-error-details","--dont-launch","--keep-logs","-v","--leave-running","--only-bios","--clean-run" + ,"--sanity-test","--p2p-plugin","--wallet-port"}) +server=args.host +port=args.port +debug=args.v +enableMongo=args.mongodb +defproduceraPrvtKey=args.defproducera_prvt_key +defproducerbPrvtKey=args.defproducerb_prvt_key +dumpErrorDetails=args.dump_error_details +keepLogs=args.keep_logs +dontLaunch=args.dont_launch +dontKill=args.leave_running +prodCount=2 +onlyBios=args.only_bios +killAll=args.clean_run +sanityTest=args.sanity_test +p2pPlugin=args.p2p_plugin +walletPort=args.wallet_port + +Utils.Debug=debug +localTest=True +cluster=Cluster(host=server, port=port, walletd=True, enableMongo=enableMongo, defproduceraPrvtKey=defproduceraPrvtKey, defproducerbPrvtKey=defproducerbPrvtKey) +walletMgr=WalletMgr(True, port=walletPort) +testSuccessful=False +killEosInstances=not dontKill +killWallet=not dontKill +dontBootstrap=sanityTest + +WalletdName=Utils.EosWalletName +ClientName="cleos" + +try: + TestHelper.printSystemInfo("BEGIN prod_preactivation_test.py") + cluster.setWalletMgr(walletMgr) + Print("SERVER: %s" % (server)) + Print("PORT: %d" % (port)) + + if enableMongo and not cluster.isMongodDbRunning(): + errorExit("MongoDb doesn't seem to be running.") + + if localTest and not dontLaunch: + cluster.killall(allInstances=killAll) + cluster.cleanup() + Print("Stand up cluster") + if cluster.launch(pnodes=prodCount, totalNodes=prodCount, prodCount=1, onlyBios=onlyBios, + dontBootstrap=dontBootstrap, p2pPlugin=p2pPlugin, useBiosBootFile=False, + pfSetupPolicy=PFSetupPolicy.NONE, extraNodeosArgs=" --plugin eosio::producer_api_plugin") is False: + cmdError("launcher") + errorExit("Failed to stand up eos cluster.") + + Print("Validating system accounts after bootstrap") + cluster.validateAccounts(None) + + node = cluster.getNode(0) + cmd = "curl %s/v1/producer/get_supported_protocol_features" % (node.endpointHttp) + Print("try to get supported feature list from Node 0 with cmd: %s" % (cmd)) + feature0=Utils.runCmdReturnJson(cmd) + + node = cluster.getNode(1) + cmd = "curl %s/v1/producer/get_supported_protocol_features" % (node.endpointHttp) + Print("try to get supported feature list from Node 1 with cmd: %s" % (cmd)) + feature1=Utils.runCmdReturnJson(cmd) + + if feature0 != feature1: + errorExit("feature list mismatch between node 0 and node 1") + else: + Print("feature list from node 0 matches with that from node 1") + + if len(feature0) == 0: + errorExit("No supported feature list") + + digest = "" + for i in range(0, len(feature0)): + feature = feature0[i] + if feature["specification"][0]["value"] != "PREACTIVATE_FEATURE": + continue + else: + digest = feature["feature_digest"] + + if len(digest) == 0: + errorExit("code name PREACTIVATE_FEATURE not found") + + Print("found digest ", digest, " of PREACTIVATE_FEATURE") + + node0 = cluster.getNode(0) + contract="eosio.bios" + contractDir="unittests/contracts/%s" % (contract) + wasmFile="%s.wasm" % (contract) + abiFile="%s.abi" % (contract) + + Print("publish a new bios contract %s should fails because env.is_feature_activated unresolveable" % (contractDir)) + retMap = node0.publishContract("eosio", contractDir, wasmFile, abiFile, True, shouldFail=True) + + if retMap["output"].decode("utf-8").find("unresolveable") < 0: + errorExit("bios contract not result in expected unresolveable error") + + secwait = 30 + Print("Wait for node 1 to produce...") + node = cluster.getNode(1) + while secwait > 0: + info = node.getInfo() + if info["head_block_producer"] >= "defproducerl" and info["head_block_producer"] <= "defproduceru": + break + time.sleep(1) + secwait = secwait - 1 + + secwait = 30 + Print("Waiting until node 0 start to produce...") + node = cluster.getNode(1) + while secwait > 0: + info = node.getInfo() + if info["head_block_producer"] >= "defproducera" and info["head_block_producer"] <= "defproducerk": + break + time.sleep(1) + secwait = secwait - 1 + + if secwait <= 0: + errorExit("No producer of node 0") + + cmd = "curl --data-binary '{\"protocol_features_to_activate\":[\"%s\"]}' %s/v1/producer/schedule_protocol_feature_activations" % (digest, node.endpointHttp) + + Print("try to preactivate feature on node 1, cmd: %s" % (cmd)) + result = Utils.runCmdReturnJson(cmd) + + if result["result"] != "ok": + errorExit("failed to preactivate feature from producer plugin on node 1") + else: + Print("feature PREACTIVATE_FEATURE (%s) preactivation success" % (digest)) + + time.sleep(0.6) + Print("publish a new bios contract %s should fails because node1 is not producing block yet" % (contractDir)) + retMap = node0.publishContract("eosio", contractDir, wasmFile, abiFile, True, shouldFail=True) + if retMap["output"].decode("utf-8").find("unresolveable") < 0: + errorExit("bios contract not result in expected unresolveable error") + + Print("now wait for node 1 produce a block...") + secwait = 30 # wait for node 1 produce a block + while secwait > 0: + info = node.getInfo() + if info["head_block_producer"] >= "defproducerl" and info["head_block_producer"] <= "defproduceru": + break + time.sleep(1) + secwait = secwait - 1 + + if secwait <= 0: + errorExit("No blocks produced by node 1") + + time.sleep(0.6) + retMap = node0.publishContract("eosio", contractDir, wasmFile, abiFile, True) + Print("sucessfully set new contract with new intrinsic!!!") + + testSuccessful=True +finally: + TestHelper.shutdown(cluster, walletMgr, testSuccessful, killEosInstances, killWallet, keepLogs, killAll, dumpErrorDetails) + +exit(0) diff --git a/unittests/contracts.hpp.in b/unittests/contracts.hpp.in index 8dd1f2b4dcf..bc61854d403 100644 --- a/unittests/contracts.hpp.in +++ b/unittests/contracts.hpp.in @@ -36,6 +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) + // Contracts in `eos/unittests/unittests/test-contracts' directory MAKE_READ_WASM_ABI(asserter, asserter, test-contracts) MAKE_READ_WASM_ABI(deferred_test, deferred_test, test-contracts) diff --git a/unittests/contracts/CMakeLists.txt b/unittests/contracts/CMakeLists.txt index 59ea1c1ca26..f64c79de062 100644 --- a/unittests/contracts/CMakeLists.txt +++ b/unittests/contracts/CMakeLists.txt @@ -6,3 +6,5 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/eosio.msig/ DESTINATION ${CMAKE_CURRENT_BI file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/eosio.system/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/eosio.system/) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/eosio.token/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/eosio.token/) 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/) diff --git a/unittests/contracts/eosio.bios/eosio.bios.abi b/unittests/contracts/eosio.bios/eosio.bios.abi index 0d5749b981b..3f9749263ce 100644 --- a/unittests/contracts/eosio.bios/eosio.bios.abi +++ b/unittests/contracts/eosio.bios/eosio.bios.abi @@ -1,6 +1,7 @@ { - "____comment": "This file was generated with eosio-abigen. DO NOT EDIT Mon Dec 3 17:06:17 2018", + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", "version": "eosio::abi/1.1", + "types": [], "structs": [ { "name": "abi_hash", @@ -240,6 +241,16 @@ } ] }, + { + "name": "preactivate", + "base": "", + "fields": [ + { + "name": "feature_digest", + "type": "checksum256" + } + ] + }, { "name": "producer_key", "base": "", @@ -254,6 +265,16 @@ } ] }, + { + "name": "reqactivated", + "base": "", + "fields": [ + { + "name": "feature_digest", + "type": "checksum256" + } + ] + }, { "name": "reqauth", "base": "", @@ -429,7 +450,6 @@ ] } ], - "types": [], "actions": [ { "name": "canceldelay", @@ -456,6 +476,16 @@ "type": "onerror", "ricardian_contract": "" }, + { + "name": "preactivate", + "type": "preactivate", + "ricardian_contract": "" + }, + { + "name": "reqactivated", + "type": "reqactivated", + "ricardian_contract": "" + }, { "name": "reqauth", "type": "reqauth", @@ -517,6 +547,5 @@ } ], "ricardian_clauses": [], - "variants": [], - "abi_extensions": [] + "variants": [] } \ 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 ea62431344e..9d15da6fc49 100755 Binary files a/unittests/contracts/eosio.bios/eosio.bios.wasm and b/unittests/contracts/eosio.bios/eosio.bios.wasm differ diff --git a/unittests/contracts/old_versions/v1.6.0-rc3/eosio.bios/eosio.bios.abi b/unittests/contracts/old_versions/v1.6.0-rc3/eosio.bios/eosio.bios.abi new file mode 100644 index 00000000000..ebdfccd0704 --- /dev/null +++ b/unittests/contracts/old_versions/v1.6.0-rc3/eosio.bios/eosio.bios.abi @@ -0,0 +1,522 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT Mon Mar 11 20:20:06 2019", + "version": "eosio::abi/1.1", + "structs": [ + { + "name": "abi_hash", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "hash", + "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": "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" + } + ] + } + ], + "types": [], + "actions": [ + { + "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": "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": [], + "abi_extensions": [] +} \ No newline at end of file diff --git a/unittests/contracts/old_versions/v1.6.0-rc3/eosio.bios/eosio.bios.wasm b/unittests/contracts/old_versions/v1.6.0-rc3/eosio.bios/eosio.bios.wasm new file mode 100755 index 00000000000..fd6478776ed Binary files /dev/null and b/unittests/contracts/old_versions/v1.6.0-rc3/eosio.bios/eosio.bios.wasm differ diff --git a/unittests/database_tests.cpp b/unittests/database_tests.cpp index 21dabc36c56..bd00e7b60eb 100644 --- a/unittests/database_tests.cpp +++ b/unittests/database_tests.cpp @@ -64,37 +64,23 @@ BOOST_AUTO_TEST_SUITE(database_tests) BOOST_TEST(test.control->fetch_block_by_number(i + 1)->id() == block_ids.back()); } - // Utility function to check expected irreversible block - auto calc_exp_last_irr_block_num = [&](uint32_t head_block_num) -> uint32_t { - const auto producers_size = test.control->head_block_state()->active_schedule.producers.size(); - const auto max_reversible_rounds = EOS_PERCENT(producers_size, config::percent_100 - config::irreversible_threshold_percent); - if( max_reversible_rounds == 0) { - return head_block_num; - } else { - const auto current_round = head_block_num / config::producer_repetitions; - const auto irreversible_round = current_round - max_reversible_rounds; - return (irreversible_round + 1) * config::producer_repetitions - 1; - } - }; - - // Check the last irreversible block number is set correctly - const auto expected_last_irreversible_block_number = calc_exp_last_irr_block_num(num_of_blocks_to_prod); + // Check the last irreversible block number is set correctly, with one producer, irreversibility should only just 1 block before + const auto expected_last_irreversible_block_number = test.control->head_block_num() - 1; BOOST_TEST(test.control->head_block_state()->dpos_irreversible_blocknum == expected_last_irreversible_block_number); - // Check that block 201 cannot be found (only 20 blocks exist) - BOOST_TEST(test.control->fetch_block_by_number(num_of_blocks_to_prod + 1 + 1) == nullptr); + // Ensure that future block doesn't exist + const auto nonexisting_future_block_num = test.control->head_block_num() + 1; + BOOST_TEST(test.control->fetch_block_by_number(nonexisting_future_block_num) == nullptr); const uint32_t next_num_of_blocks_to_prod = 100; - // Produce 100 blocks and check their IDs should match the above test.produce_blocks(next_num_of_blocks_to_prod); - const auto next_expected_last_irreversible_block_number = calc_exp_last_irr_block_num( - num_of_blocks_to_prod + next_num_of_blocks_to_prod); + const auto next_expected_last_irreversible_block_number = test.control->head_block_num() - 1; // Check the last irreversible block number is updated correctly BOOST_TEST(test.control->head_block_state()->dpos_irreversible_blocknum == next_expected_last_irreversible_block_number); - // Check that block 201 can now be found - BOOST_CHECK_NO_THROW(test.control->fetch_block_by_number(num_of_blocks_to_prod + 1)); + // Previous nonexisting future block should exist by now + BOOST_CHECK_NO_THROW(test.control->fetch_block_by_number(nonexisting_future_block_num)); // Check the latest head block match - BOOST_TEST(test.control->fetch_block_by_number(num_of_blocks_to_prod + next_num_of_blocks_to_prod + 1)->id() == + BOOST_TEST(test.control->fetch_block_by_number(test.control->head_block_num())->id() == test.control->head_block_id()); } FC_LOG_AND_RETHROW() } diff --git a/unittests/fork_test_utilities.cpp b/unittests/fork_test_utilities.cpp new file mode 100644 index 00000000000..a8caaeeb233 --- /dev/null +++ b/unittests/fork_test_utilities.cpp @@ -0,0 +1,42 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ +#include "fork_test_utilities.hpp" + +private_key_type get_private_key( name keyname, string role ) { + return private_key_type::regenerate(fc::sha256::hash(string(keyname)+role)); +} + +public_key_type get_public_key( name keyname, string role ){ + return get_private_key( keyname, role ).get_public_key(); +} + +void push_blocks( tester& from, tester& to, uint32_t block_num_limit ) { + while( to.control->fork_db_pending_head_block_num() + < std::min( from.control->fork_db_pending_head_block_num(), block_num_limit ) ) + { + auto fb = from.control->fetch_block_by_number( to.control->fork_db_pending_head_block_num()+1 ); + to.push_block( fb ); + } +} + +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; + }; + + for( uint32_t blocks_produced = 0; + blocks_produced < max_num_blocks_to_produce; + t.produce_block(), ++blocks_produced ) + { + if( condition_satisfied() ) + return true; + } + + return condition_satisfied(); +} diff --git a/unittests/fork_test_utilities.hpp b/unittests/fork_test_utilities.hpp new file mode 100644 index 00000000000..f5ae33ae718 --- /dev/null +++ b/unittests/fork_test_utilities.hpp @@ -0,0 +1,21 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ +#pragma once + +#include + +using namespace eosio::chain; +using namespace eosio::testing; + +private_key_type get_private_key( name keyname, string role ); + +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() ); diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index 0e2d03cdd2f..feeebf7664f 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -16,46 +16,11 @@ #include +#include "fork_test_utilities.hpp" + using namespace eosio::chain; using namespace eosio::testing; -private_key_type get_private_key( name keyname, string role ) { - return private_key_type::regenerate(fc::sha256::hash(string(keyname)+role)); -} - -public_key_type get_public_key( name keyname, string role ){ - return get_private_key( keyname, role ).get_public_key(); -} - -void push_blocks( tester& from, tester& to, uint32_t block_num_limit = std::numeric_limits::max() ) { - while( to.control->fork_db_pending_head_block_num() - < std::min( from.control->fork_db_pending_head_block_num(), block_num_limit ) ) - { - auto fb = from.control->fetch_block_by_number( to.control->fork_db_pending_head_block_num()+1 ); - to.push_block( fb ); - } -} - -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() ) -{ - auto condition_satisfied = [&t, last_producer, next_producer]() { - return t.control->pending_block_producer() == next_producer && t.control->head_block_producer() == last_producer; - }; - - for( uint32_t blocks_produced = 0; - blocks_produced < max_num_blocks_to_produce; - t.produce_block(), ++blocks_produced ) - { - if( condition_satisfied() ) - return true; - } - - return condition_satisfied(); -} - BOOST_AUTO_TEST_SUITE(forked_tests) BOOST_AUTO_TEST_CASE( irrblock ) try { @@ -171,8 +136,9 @@ BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { BOOST_AUTO_TEST_CASE( forking ) try { tester c; - c.produce_block(); - c.produce_block(); + while (c.control->head_block_num() < 3) { + c.produce_block(); + } auto r = c.create_accounts( {N(dan),N(sam),N(pam)} ); wdump((fc::json::to_pretty_string(r))); c.produce_block(); @@ -322,7 +288,9 @@ BOOST_AUTO_TEST_CASE( forking ) try { */ BOOST_AUTO_TEST_CASE( prune_remove_branch ) try { tester c; - c.produce_blocks(10); + while (c.control->head_block_num() < 11) { + c.produce_block(); + } 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)} ); wlog("set producer schedule to [dan,sam,pam,scott]"); @@ -392,9 +360,7 @@ BOOST_AUTO_TEST_CASE( validator_accepts_valid_blocks ) try { block_state_ptr first_block; auto c = n2.control->accepted_block.connect( [&]( const block_state_ptr& bsp) { - if( bsp->block_num == 2 ) { - first_block = bsp; - } + first_block = bsp; } ); push_blocks( n1, n2 ); @@ -426,17 +392,17 @@ 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(true, db_read_mode::HEAD); + tester head(setup_policy::old_bios_only, 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()); - tester read_only(false, db_read_mode::READ_ONLY); + tester read_only(setup_policy::none, db_read_mode::READ_ONLY); push_blocks(c, read_only); 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(true, db_read_mode::IRREVERSIBLE); + tester irreversible(setup_policy::old_bios_only, 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()); @@ -510,7 +476,7 @@ BOOST_AUTO_TEST_CASE( irreversible_mode ) try { BOOST_REQUIRE( hbn4 > hbn3 ); BOOST_REQUIRE( lib4 < hbn1 ); - tester irreversible(false, db_read_mode::IRREVERSIBLE); + tester irreversible(setup_policy::none, db_read_mode::IRREVERSIBLE); push_blocks( main, irreversible, hbn1 ); diff --git a/unittests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp index de103654380..9003f8555bd 100644 --- a/unittests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -8,6 +8,8 @@ #include #include +#include "fork_test_utilities.hpp" + #ifdef NON_VALIDATING_TEST #define TESTER tester #else @@ -204,7 +206,9 @@ BOOST_AUTO_TEST_SUITE(producer_schedule_tests) BOOST_FIXTURE_TEST_CASE( producer_schedule_promotion_test, TESTER ) try { create_accounts( {N(alice),N(bob),N(carol)} ); - produce_block(); + while (control->head_block_num() < 3) { + produce_block(); + } auto compare_schedules = [&]( const vector& a, const producer_schedule_type& b ) { return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); @@ -228,7 +232,7 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_promotion_test, TESTER ) try { 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(7); + produce_blocks(6); res = set_producers( {N(alice),N(bob),N(carol)} ); vector sch2 = { @@ -267,7 +271,9 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_promotion_test, TESTER ) try { BOOST_FIXTURE_TEST_CASE( producer_schedule_reduction, tester ) try { create_accounts( {N(alice),N(bob),N(carol)} ); - produce_block(); + while (control->head_block_num() < 3) { + produce_block(); + } auto compare_schedules = [&]( const vector& a, const producer_schedule_type& b ) { return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); @@ -291,7 +297,7 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_reduction, tester ) try { 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(7); + produce_blocks(6); res = set_producers( {N(alice),N(bob)} ); vector sch2 = { @@ -324,7 +330,9 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_reduction, tester ) try { BOOST_FIXTURE_TEST_CASE( empty_producer_schedule_has_no_effect, tester ) try { create_accounts( {N(alice),N(bob),N(carol)} ); - produce_block(); + while (control->head_block_num() < 3) { + produce_block(); + } auto compare_schedules = [&]( const vector& a, const producer_schedule_type& b ) { return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); @@ -350,7 +358,7 @@ BOOST_FIXTURE_TEST_CASE( empty_producer_schedule_has_no_effect, tester ) try { produce_block(); BOOST_CHECK_EQUAL( control->active_producers().version, 1u ); BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->active_producers() ) ); - produce_blocks(7); + produce_blocks(6); res = set_producers( {} ); wlog("set producer schedule to []"); @@ -397,4 +405,99 @@ BOOST_FIXTURE_TEST_CASE( empty_producer_schedule_has_no_effect, tester ) try { BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() +BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { + tester c; + + c.create_accounts( {N(alice),N(bob),N(carol)} ); + c.produce_block(); + + auto compare_schedules = [&]( const vector& a, const producer_schedule_type& 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")} + }; + wlog("set producer schedule to [alice,bob,carol]"); + BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *c.control->proposed_producers() ) ); + BOOST_CHECK_EQUAL( c.control->pending_producers().version, 0u ); + c.produce_block(); // Starts new block which promotes the proposed schedule to pending + BOOST_CHECK_EQUAL( c.control->pending_producers().version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, c.control->pending_producers() ) ); + BOOST_CHECK_EQUAL( c.control->active_producers().version, 0u ); + c.produce_block(); + c.produce_block(); // Starts new block which promotes the pending schedule to active + 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) ); + c.produce_block(); + 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")} + }; + wlog("set producer schedule to [alice,bob]"); + 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) ); + 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) ); + 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) ); + BOOST_CHECK_EQUAL( c.control->pending_block_producer(), N(carol) ); + BOOST_REQUIRE_EQUAL( c.control->active_producers().version, 2u ); + + auto carol_last_produced_block_num = c.control->head_block_num() + 1; + wdump((carol_last_produced_block_num)); + + c.produce_block(); + BOOST_CHECK( c.control->pending_block_producer() == N(alice) ); + + res = c.set_producers( {N(alice),N(bob),N(carol)} ); + wlog("set producer schedule to [alice,bob,carol]"); + 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) ); + 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) ); + BOOST_REQUIRE_EQUAL( c.control->active_producers().version, 3u ); + + produce_empty_blocks_until( c, N(alice), N(bob) ); + c.produce_blocks(11); + BOOST_CHECK_EQUAL( c.control->pending_block_producer(), N(bob) ); + c.finish_block(); + + auto carol_block_num = c.control->head_block_num() + 1; + auto carol_block_time = c.control->head_block_time() + fc::milliseconds(config::block_interval_ms); + auto confirmed = carol_block_num - carol_last_produced_block_num - 1; + + c.control->start_block( carol_block_time, confirmed ); + BOOST_CHECK_EQUAL( c.control->pending_block_producer(), N(carol) ); + c.produce_block(); + auto h = c.control->head_block_header(); + + BOOST_CHECK_EQUAL( h.producer, N(carol) ); + BOOST_CHECK_EQUAL( h.confirmed, confirmed ); + + produce_empty_blocks_until( c, N(carol), N(alice) ); + +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp new file mode 100644 index 00000000000..10648136c0c --- /dev/null +++ b/unittests/protocol_feature_tests.cpp @@ -0,0 +1,351 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#include +#include +#include + +#include + +#include + +#include + +#include + +#include "fork_test_utilities.hpp" + +using namespace eosio::chain; +using namespace eosio::testing; + +BOOST_AUTO_TEST_SUITE(protocol_feature_tests) + +BOOST_AUTO_TEST_CASE( activate_preactivate_feature ) try { + tester c( setup_policy::none ); + const auto& pfm = c.control->get_protocol_feature_manager(); + + c.produce_block(); + + // Cannot set latest bios contract since it requires intrinsics that have not yet been whitelisted. + BOOST_CHECK_EXCEPTION( c.set_code( config::system_account_name, contracts::eosio_bios_wasm() ), + wasm_exception, fc_exception_message_is("env.is_feature_activated unresolveable") + ); + + // But the old bios contract can still be set. + c.set_code( config::system_account_name, contracts::before_preactivate_eosio_bios_wasm() ); + c.set_abi( config::system_account_name, contracts::before_preactivate_eosio_bios_abi().data() ); + + auto t = c.control->pending_block_time(); + c.control->abort_block(); + BOOST_REQUIRE_EXCEPTION( c.control->start_block( t, 0, {digest_type()} ), protocol_feature_exception, + fc_exception_message_is( "protocol feature with digest '0000000000000000000000000000000000000000000000000000000000000000' is unrecognized" ) + ); + + auto d = pfm.get_builtin_digest( builtin_protocol_feature_t::preactivate_feature ); + + BOOST_REQUIRE( d ); + + // Activate PREACTIVATE_FEATURE. + c.schedule_protocol_features_wo_preactivation({ *d }); + 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.produce_block(); + + BOOST_CHECK_EXCEPTION( c.push_action( config::system_account_name, N(reqactivated), config::system_account_name, + mutable_variant_object()("feature_digest", digest_type()) ), + eosio_assert_message_exception, + eosio_assert_message_is( "protocol feature is not activated" ) + ); + + c.push_action( config::system_account_name, N(reqactivated), config::system_account_name, mutable_variant_object() + ("feature_digest", *d ) + ); + + c.produce_block(); + + // Ensure validator node accepts the blockchain + + tester c2(setup_policy::none, db_read_mode::SPECULATIVE); + push_blocks( c, c2 ); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE( activate_and_restart ) try { + tester c( setup_policy::none ); + const auto& pfm = c.control->get_protocol_feature_manager(); + + auto pfs = pfm.get_protocol_feature_set(); // make copy of protocol feature set + + auto d = pfm.get_builtin_digest( builtin_protocol_feature_t::preactivate_feature ); + BOOST_REQUIRE( d ); + + BOOST_CHECK( !c.control->is_builtin_activated( builtin_protocol_feature_t::preactivate_feature ) ); + + // Activate PREACTIVATE_FEATURE. + c.schedule_protocol_features_wo_preactivation({ *d }); + c.produce_blocks(2); + + auto head_block_num = c.control->head_block_num(); + + BOOST_CHECK( c.control->is_builtin_activated( builtin_protocol_feature_t::preactivate_feature ) ); + + c.close(); + c.open( std::move( pfs ), nullptr ); + + BOOST_CHECK_EQUAL( head_block_num, c.control->head_block_num() ); + + BOOST_CHECK( c.control->is_builtin_activated( builtin_protocol_feature_t::preactivate_feature ) ); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE( double_preactivation ) try { + tester c( setup_policy::preactivate_feature_and_new_bios ); + const auto& pfm = c.control->get_protocol_feature_manager(); + + auto d = pfm.get_builtin_digest( builtin_protocol_feature_t::only_link_to_existing_permission ); + BOOST_REQUIRE( d ); + + c.push_action( config::system_account_name, N(preactivate), config::system_account_name, + fc::mutable_variant_object()("feature_digest", *d), 10 ); + + std::string expected_error_msg("protocol feature with digest '"); + { + fc::variant v; + to_variant( *d, v ); + expected_error_msg += v.get_string(); + expected_error_msg += "' is already pre-activated"; + } + + BOOST_CHECK_EXCEPTION( c.push_action( config::system_account_name, N(preactivate), config::system_account_name, + fc::mutable_variant_object()("feature_digest", *d), 20 ), + protocol_feature_exception, + fc_exception_message_is( expected_error_msg ) + ); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE( double_activation ) try { + tester c( setup_policy::preactivate_feature_and_new_bios ); + const auto& pfm = c.control->get_protocol_feature_manager(); + + auto d = pfm.get_builtin_digest( builtin_protocol_feature_t::only_link_to_existing_permission ); + BOOST_REQUIRE( d ); + + BOOST_CHECK( !c.control->is_builtin_activated( builtin_protocol_feature_t::only_link_to_existing_permission ) ); + + c.preactivate_protocol_features( {*d} ); + + BOOST_CHECK( !c.control->is_builtin_activated( builtin_protocol_feature_t::only_link_to_existing_permission ) ); + + c.schedule_protocol_features_wo_preactivation( {*d} ); + + BOOST_CHECK_EXCEPTION( c.produce_block();, + block_validate_exception, + fc_exception_message_starts_with( "attempted duplicate activation within a single block:" ) + ); + + c.protocol_features_to_be_activated_wo_preactivation.clear(); + + BOOST_CHECK( !c.control->is_builtin_activated( builtin_protocol_feature_t::only_link_to_existing_permission ) ); + + c.produce_block(); + + BOOST_CHECK( c.control->is_builtin_activated( builtin_protocol_feature_t::only_link_to_existing_permission ) ); + + c.produce_block(); + + BOOST_CHECK( c.control->is_builtin_activated( builtin_protocol_feature_t::only_link_to_existing_permission ) ); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE( require_preactivation_test ) try { + tester c( setup_policy::preactivate_feature_and_new_bios ); + const auto& pfm = c.control->get_protocol_feature_manager(); + + auto d = pfm.get_builtin_digest( builtin_protocol_feature_t::only_link_to_existing_permission ); + BOOST_REQUIRE( d ); + + BOOST_CHECK( !c.control->is_builtin_activated( builtin_protocol_feature_t::only_link_to_existing_permission ) ); + + c.schedule_protocol_features_wo_preactivation( {*d} ); + BOOST_CHECK_EXCEPTION( c.produce_block(), + protocol_feature_exception, + fc_exception_message_starts_with( "attempted to activate protocol feature without prior required preactivation:" ) + ); +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE( only_link_to_existing_permission_test ) try { + tester c( setup_policy::preactivate_feature_and_new_bios ); + const auto& pfm = c.control->get_protocol_feature_manager(); + + auto d = pfm.get_builtin_digest( builtin_protocol_feature_t::only_link_to_existing_permission ); + BOOST_REQUIRE( d ); + + c.create_accounts( {N(alice), N(bob), N(charlie)} ); + + BOOST_CHECK_EXCEPTION( c.push_action( config::system_account_name, N(linkauth), N(bob), fc::mutable_variant_object() + ("account", "bob") + ("code", name(config::system_account_name)) + ("type", "") + ("requirement", "test" ) + ), permission_query_exception, + fc_exception_message_is( "Failed to retrieve permission: test" ) + ); + + BOOST_CHECK_EXCEPTION( c.push_action( config::system_account_name, N(linkauth), N(charlie), fc::mutable_variant_object() + ("account", "charlie") + ("code", name(config::system_account_name)) + ("type", "") + ("requirement", "test" ) + ), permission_query_exception, + fc_exception_message_is( "Failed to retrieve permission: test" ) + ); + + c.push_action( config::system_account_name, N(updateauth), N(alice), fc::mutable_variant_object() + ("account", "alice") + ("permission", "test") + ("parent", "active") + ("auth", authority(get_public_key("testapi", "test"))) + ); + + c.produce_block(); + + // Verify the incorrect behavior prior to ONLY_LINK_TO_EXISTING_PERMISSION activation. + c.push_action( config::system_account_name, N(linkauth), N(bob), fc::mutable_variant_object() + ("account", "bob") + ("code", name(config::system_account_name)) + ("type", "") + ("requirement", "test" ) + ); + + c.preactivate_protocol_features( {*d} ); + c.produce_block(); + + // Verify the correct behavior after ONLY_LINK_TO_EXISTING_PERMISSION activation. + BOOST_CHECK_EXCEPTION( c.push_action( config::system_account_name, N(linkauth), N(charlie), fc::mutable_variant_object() + ("account", "charlie") + ("code", name(config::system_account_name)) + ("type", "") + ("requirement", "test" ) + ), permission_query_exception, + fc_exception_message_is( "Failed to retrieve permission: test" ) + ); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE( subjective_restrictions_test ) try { + tester c( setup_policy::none ); + const auto& pfm = c.control->get_protocol_feature_manager(); + + auto restart_with_new_pfs = [&c]( protocol_feature_set&& pfs ) { + c.close(); + c.open(std::move(pfs), nullptr); + }; + + auto get_builtin_digest = [&pfm]( builtin_protocol_feature_t codename ) -> digest_type { + auto res = pfm.get_builtin_digest( codename ); + BOOST_REQUIRE( res ); + return *res; + }; + + auto preactivate_feature_digest = get_builtin_digest( builtin_protocol_feature_t::preactivate_feature ); + auto only_link_to_existing_permission_digest = get_builtin_digest( builtin_protocol_feature_t::only_link_to_existing_permission ); + + auto invalid_act_time = fc::time_point::from_iso_string( "2200-01-01T00:00:00" ); + auto valid_act_time = fc::time_point{}; + + // First, test subjective_restrictions on feature that can be activated WITHOUT preactivation (PREACTIVATE_FEATURE) + + c.schedule_protocol_features_wo_preactivation({ preactivate_feature_digest }); + // schedule PREACTIVATE_FEATURE activation (persists until next successful start_block) + + subjective_restriction_map custom_subjective_restrictions = { + { builtin_protocol_feature_t::preactivate_feature, {invalid_act_time, false, true} } + }; + restart_with_new_pfs( make_protocol_feature_set(custom_subjective_restrictions) ); + // When a block is produced, the protocol feature activation should fail and throw an error + BOOST_CHECK_EXCEPTION( c.produce_block(), + protocol_feature_exception, + fc_exception_message_starts_with( + std::string(c.control->head_block_time()) + + " is too early for the earliest allowed activation time of the protocol feature" + ) + ); + BOOST_CHECK_EQUAL( c.protocol_features_to_be_activated_wo_preactivation.size(), 1 ); + + // Revert to the valid earliest allowed activation time, however with enabled == false + custom_subjective_restrictions = { + { builtin_protocol_feature_t::preactivate_feature, {valid_act_time, false, false} } + }; + restart_with_new_pfs( make_protocol_feature_set(custom_subjective_restrictions) ); + // This should also fail, but with different exception + BOOST_CHECK_EXCEPTION( c.produce_block(), + protocol_feature_exception, + fc_exception_message_is( + std::string("protocol feature with digest '") + + std::string(preactivate_feature_digest) + + "' is disabled" + ) + ); + BOOST_CHECK_EQUAL( c.protocol_features_to_be_activated_wo_preactivation.size(), 1 ); + + // Revert to the valid earliest allowed activation time, however with subjective_restrictions enabled == true + custom_subjective_restrictions = { + { builtin_protocol_feature_t::preactivate_feature, {valid_act_time, false, true} } + }; + restart_with_new_pfs( make_protocol_feature_set(custom_subjective_restrictions) ); + // Now it should be fine, the feature should be activated after the block is produced + BOOST_CHECK_NO_THROW( c.produce_block() ); + BOOST_CHECK( c.control->is_builtin_activated( builtin_protocol_feature_t::preactivate_feature ) ); + BOOST_CHECK_EQUAL( c.protocol_features_to_be_activated_wo_preactivation.size(), 0 ); + + // Second, test subjective_restrictions on feature that need to be activated WITH preactivation (ONLY_LINK_TO_EXISTING_PERMISSION) + + c.set_bios_contract(); + c.produce_block(); + + custom_subjective_restrictions = { + { builtin_protocol_feature_t::only_link_to_existing_permission, {invalid_act_time, true, true} } + }; + restart_with_new_pfs( make_protocol_feature_set(custom_subjective_restrictions) ); + // It should fail + BOOST_CHECK_EXCEPTION( c.preactivate_protocol_features({only_link_to_existing_permission_digest}), + subjective_block_production_exception, + fc_exception_message_starts_with( + std::string(c.control->head_block_time() + fc::milliseconds(config::block_interval_ms)) + + " is too early for the earliest allowed activation time of the protocol feature" + ) + ); + + // Revert with valid time and subjective_restrictions enabled == false + custom_subjective_restrictions = { + { builtin_protocol_feature_t::only_link_to_existing_permission, {valid_act_time, true, false} } + }; + restart_with_new_pfs( make_protocol_feature_set(custom_subjective_restrictions) ); + // It should fail but with different exception + BOOST_CHECK_EXCEPTION( c.preactivate_protocol_features({only_link_to_existing_permission_digest}), + subjective_block_production_exception, + fc_exception_message_is( + std::string("protocol feature with digest '") + + std::string(only_link_to_existing_permission_digest)+ + "' is disabled" + ) + ); + + // Revert with valid time and subjective_restrictions enabled == true + custom_subjective_restrictions = { + { builtin_protocol_feature_t::only_link_to_existing_permission, {valid_act_time, true, true} } + }; + restart_with_new_pfs( make_protocol_feature_set(custom_subjective_restrictions) ); + // Should be fine now, and activated in the next block + BOOST_CHECK_NO_THROW( c.preactivate_protocol_features({only_link_to_existing_permission_digest}) ); + c.produce_block(); + BOOST_CHECK( c.control->is_builtin_activated( builtin_protocol_feature_t::only_link_to_existing_permission ) ); +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END()