Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Implement DISALLOW_EMPTY_PRODUCER_SCHEDULE protocol feature #7026

Merged
merged 4 commits into from
Mar 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2670,6 +2670,10 @@ int64_t controller::set_proposed_producers( vector<producer_key> producers ) {
const auto& gpo = get_global_properties();
auto cur_block_num = head_block_num() + 1;

if( producers.size() == 0 && is_builtin_activated( builtin_protocol_feature_t::disallow_empty_producer_schedule ) ) {
return -1;
}

if( gpo.proposed_schedule_block_num.valid() ) {
if( *gpo.proposed_schedule_block_num != cur_block_num )
return -1; // there is already a proposed schedule set in a previous block, wait for it to become pending
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ enum class builtin_protocol_feature_t : uint32_t {
preactivate_feature,
only_link_to_existing_permission,
replace_deferred,
fix_linkauth_restriction
fix_linkauth_restriction,
disallow_empty_producer_schedule
};

struct protocol_feature_subjective_restrictions {
Expand Down
11 changes: 11 additions & 0 deletions libraries/chain/protocol_feature_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ Builtin protocol feature: FIX_LINKAUTH_RESTRICTION

Removes the restriction on eosio::linkauth for non-native actions named one of the five special action names:
updateauth, deleteauth, linkauth, unlinkauth, or canceldelay.
*/
{}
} )
( builtin_protocol_feature_t::disallow_empty_producer_schedule, builtin_protocol_feature_spec{
"DISALLOW_EMPTY_PRODUCER_SCHEDULE",
fc::variant("2853617cec3eabd41881eb48882e6fc5e81a0db917d375057864b3befbe29acd").as<digest_type>(),
// SHA256 hash of the raw message below within the comment delimiters (do not modify message below).
/*
Builtin protocol feature: DISALLOW_EMPTY_PRODUCER_SCHEDULE

Disallows proposing an empty producer schedule.
*/
{}
} )
Expand Down
7 changes: 7 additions & 0 deletions libraries/chain/wasm_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,13 @@ class privileged_api : public context_aware_api {
datastream<const char*> ds( packed_producer_schedule, datalen );
vector<producer_key> producers;
fc::raw::unpack(ds, producers);
EOS_ASSERT( producers.size() > 0
|| !context.control.is_builtin_activated(
builtin_protocol_feature_t::disallow_empty_producer_schedule
),
wasm_execution_error,
"Producer schedule cannot be empty"
);
EOS_ASSERT(producers.size() <= config::max_producers, wasm_execution_error, "Producer schedule exceeds the maximum producer count for this chain");
// check that producers are unique
std::set<account_name> unique_producers;
Expand Down
83 changes: 43 additions & 40 deletions unittests/producer_schedule_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,81 +328,84 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_reduction, tester ) try {
BOOST_REQUIRE_EQUAL( validate(), true );
} FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE( empty_producer_schedule_has_no_effect, tester ) try {
create_accounts( {N(alice),N(bob),N(carol)} );
while (control->head_block_num() < 3) {
produce_block();
BOOST_AUTO_TEST_CASE( empty_producer_schedule_has_no_effect ) try {
validating_tester c( validating_tester::default_config() );
c.execute_setup_policy( setup_policy::preactivate_feature_and_new_bios );

c.create_accounts( {N(alice),N(bob),N(carol)} );
while (c.control->head_block_num() < 3) {
c.produce_block();
}

auto compare_schedules = [&]( const vector<producer_key>& a, const producer_schedule_type& b ) {
return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() );
};

auto res = set_producers( {N(alice),N(bob)} );
auto res = c.set_producers( {N(alice),N(bob)} );
vector<producer_key> sch1 = {
{N(alice), get_public_key(N(alice), "active")},
{N(bob), get_public_key(N(bob), "active")}
};
wlog("set producer schedule to [alice,bob]");
BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() );
BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *control->proposed_producers() ) );
BOOST_CHECK_EQUAL( control->pending_producers().producers.size(), 0u );
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().producers.size(), 0u );

// Start a new block which promotes the proposed schedule to pending
produce_block();
BOOST_CHECK_EQUAL( control->pending_producers().version, 1u );
BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->pending_producers() ) );
BOOST_CHECK_EQUAL( control->active_producers().version, 0u );
c.produce_block();
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 );

// Start a new block which promotes the pending schedule to active
produce_block();
BOOST_CHECK_EQUAL( control->active_producers().version, 1u );
BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->active_producers() ) );
produce_blocks(6);
c.produce_block();
BOOST_CHECK_EQUAL( c.control->active_producers().version, 1u );
BOOST_CHECK_EQUAL( true, compare_schedules( sch1, c.control->active_producers() ) );
c.produce_blocks(6);

res = set_producers( {} );
res = c.set_producers( {} );
wlog("set producer schedule to []");
BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() );
BOOST_CHECK_EQUAL( control->proposed_producers()->producers.size(), 0u );
BOOST_CHECK_EQUAL( control->proposed_producers()->version, 2u );
BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() );
BOOST_CHECK_EQUAL( c.control->proposed_producers()->producers.size(), 0u );
BOOST_CHECK_EQUAL( c.control->proposed_producers()->version, 2u );

produce_blocks(12);
BOOST_CHECK_EQUAL( control->pending_producers().version, 1u );
c.produce_blocks(12);
BOOST_CHECK_EQUAL( c.control->pending_producers().version, 1u );

// Empty producer schedule does get promoted from proposed to pending
produce_block();
BOOST_CHECK_EQUAL( control->pending_producers().version, 2u );
BOOST_CHECK_EQUAL( false, control->proposed_producers().valid() );
c.produce_block();
BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u );
BOOST_CHECK_EQUAL( false, c.control->proposed_producers().valid() );

// However it should not get promoted from pending to active
produce_blocks(24);
BOOST_CHECK_EQUAL( control->active_producers().version, 1u );
BOOST_CHECK_EQUAL( control->pending_producers().version, 2u );
c.produce_blocks(24);
BOOST_CHECK_EQUAL( c.control->active_producers().version, 1u );
BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u );

// Setting a new producer schedule should still use version 2
res = set_producers( {N(alice),N(bob),N(carol)} );
res = c.set_producers( {N(alice),N(bob),N(carol)} );
vector<producer_key> sch2 = {
{N(alice), get_public_key(N(alice), "active")},
{N(bob), get_public_key(N(bob), "active")},
{N(carol), get_public_key(N(carol), "active")}
};
wlog("set producer schedule to [alice,bob,carol]");
BOOST_REQUIRE_EQUAL( true, control->proposed_producers().valid() );
BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *control->proposed_producers() ) );
BOOST_CHECK_EQUAL( control->proposed_producers()->version, 2u );
BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().valid() );
BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *c.control->proposed_producers() ) );
BOOST_CHECK_EQUAL( c.control->proposed_producers()->version, 2u );

// Produce enough blocks to promote the proposed schedule to pending, which it can do because the existing pending has zero producers
produce_blocks(24);
BOOST_CHECK_EQUAL( control->active_producers().version, 1u );
BOOST_CHECK_EQUAL( control->pending_producers().version, 2u );
BOOST_CHECK_EQUAL( true, compare_schedules( sch2, control->pending_producers() ) );
c.produce_blocks(24);
BOOST_CHECK_EQUAL( c.control->active_producers().version, 1u );
BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u );
BOOST_CHECK_EQUAL( true, compare_schedules( sch2, c.control->pending_producers() ) );

// Produce enough blocks to promote the pending schedule to active
produce_blocks(24);
BOOST_CHECK_EQUAL( control->active_producers().version, 2u );
BOOST_CHECK_EQUAL( true, compare_schedules( sch2, control->active_producers() ) );
c.produce_blocks(24);
BOOST_CHECK_EQUAL( c.control->active_producers().version, 2u );
BOOST_CHECK_EQUAL( true, compare_schedules( sch2, c.control->active_producers() ) );

BOOST_REQUIRE_EQUAL( validate(), true );
BOOST_REQUIRE_EQUAL( c.validate(), true );
} FC_LOG_AND_RETHROW()

BOOST_AUTO_TEST_CASE( producer_watermark_test ) try {
Expand Down
29 changes: 28 additions & 1 deletion unittests/protocol_feature_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ BOOST_AUTO_TEST_CASE( fix_linkauth_restriction ) { try {
chain.create_account(N(currency));
chain.create_account(tester_account);
chain.produce_blocks();

chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object()
("account", name(tester_account).to_string())
("permission", "first")
Expand Down Expand Up @@ -555,4 +555,31 @@ BOOST_AUTO_TEST_CASE( fix_linkauth_restriction ) { try {

} FC_LOG_AND_RETHROW() }

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

const auto& pfm = c.control->get_protocol_feature_manager();
const auto& d = pfm.get_builtin_digest( builtin_protocol_feature_t::disallow_empty_producer_schedule );
BOOST_REQUIRE( d );

// Before activation, it is allowed to set empty producer schedule
c.set_producers( {} );

// After activation, it should not be allowed
c.preactivate_protocol_features( {*d} );
c.produce_block();
BOOST_REQUIRE_EXCEPTION( c.set_producers( {} ),
wasm_execution_error,
fc_exception_message_is( "Producer schedule cannot be empty" ) );

// Setting non empty producer schedule should still be fine
vector<name> producer_names = {N(alice),N(bob),N(carol)};
c.create_accounts( producer_names );
c.set_producers( producer_names );
c.produce_blocks(2);
const auto& schedule = c.get_producer_keys( producer_names );
BOOST_CHECK( std::equal( schedule.begin(), schedule.end(), c.control->active_producers().producers.begin()) );

} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_SUITE_END()