From 1e95f1548d69ad5da10263704b452c0c7d18ccf4 Mon Sep 17 00:00:00 2001 From: oldcold Date: Tue, 19 Mar 2019 16:22:01 +0800 Subject: [PATCH 01/59] resolve conflicts of net message sending, enqueue pbft msgs into sync queue. --- libraries/chain/CMakeLists.txt | 2 + libraries/chain/block_header_state.cpp | 42 +- libraries/chain/controller.cpp | 206 ++- libraries/chain/fork_database.cpp | 207 ++- .../include/eosio/chain/block_header.hpp | 2 +- .../eosio/chain/block_header_state.hpp | 2 + .../chain/include/eosio/chain/block_state.hpp | 4 +- .../chain/include/eosio/chain/config.hpp | 7 +- .../chain/include/eosio/chain/controller.hpp | 37 + .../chain/include/eosio/chain/exceptions.hpp | 2 + .../include/eosio/chain/fork_database.hpp | 13 +- libraries/chain/include/eosio/chain/pbft.hpp | 232 ++++ .../include/eosio/chain/pbft_database.hpp | 616 +++++++++ libraries/chain/pbft.cpp | 601 ++++++++ libraries/chain/pbft_database.cpp | 1223 +++++++++++++++++ .../testing/include/eosio/testing/tester.hpp | 2 + plugins/CMakeLists.txt | 1 + .../include/eosio/chain/plugin_interface.hpp | 21 + plugins/chain_plugin/chain_plugin.cpp | 180 ++- .../eosio/chain_plugin/chain_plugin.hpp | 19 +- .../include/eosio/net_plugin/net_plugin.hpp | 2 + .../include/eosio/net_plugin/protocol.hpp | 25 +- plugins/net_plugin/net_plugin.cpp | 556 +++++++- plugins/pbft_plugin/CMakeLists.txt | 7 + .../include/eosio/pbft_plugin/pbft_plugin.hpp | 28 + plugins/pbft_plugin/pbft_plugin.cpp | 134 ++ .../producer_api_plugin.cpp | 2 + .../eosio/producer_plugin/producer_plugin.hpp | 2 + plugins/producer_plugin/producer_plugin.cpp | 16 +- programs/nodeos/CMakeLists.txt | 1 + tests/chain_plugin_tests.cpp | 2 +- tests/get_table_tests.cpp | 6 +- unittests/pbft_tests.cpp | 59 + 33 files changed, 4168 insertions(+), 91 deletions(-) create mode 100644 libraries/chain/include/eosio/chain/pbft.hpp create mode 100644 libraries/chain/include/eosio/chain/pbft_database.hpp create mode 100644 libraries/chain/pbft.cpp create mode 100644 libraries/chain/pbft_database.cpp create mode 100644 plugins/pbft_plugin/CMakeLists.txt create mode 100644 plugins/pbft_plugin/include/eosio/pbft_plugin/pbft_plugin.hpp create mode 100644 plugins/pbft_plugin/pbft_plugin.cpp create mode 100644 unittests/pbft_tests.cpp diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 2c430fecea0..8f765f91ec1 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -14,6 +14,8 @@ add_library( eosio_chain block_header_state.cpp block_state.cpp fork_database.cpp + pbft_database.cpp + pbft.cpp controller.cpp authorization_manager.cpp resource_limits.cpp diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 7189073f975..d9d55e5b5be 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -60,36 +60,38 @@ namespace eosio { namespace chain { result.active_schedule = active_schedule; result.pending_schedule = pending_schedule; - result.dpos_proposed_irreversible_blocknum = dpos_proposed_irreversible_blocknum; +// result.dpos_proposed_irreversible_blocknum = dpos_proposed_irreversible_blocknum; result.bft_irreversible_blocknum = bft_irreversible_blocknum; + result.pbft_stable_checkpoint_blocknum = pbft_stable_checkpoint_blocknum; result.producer_to_last_implied_irb[prokey.producer_name] = result.dpos_proposed_irreversible_blocknum; - result.dpos_irreversible_blocknum = result.calc_dpos_last_irreversible(); +// result.dpos_irreversible_blocknum = result.calc_dpos_last_irreversible(); /// grow the confirmed count static_assert(std::numeric_limits::max() >= (config::max_producers * 2 / 3) + 1, "8bit confirmations may not be able to hold all of the needed confirmations"); // This uses the previous block active_schedule because thats the "schedule" that signs and therefore confirms _this_ block - auto num_active_producers = active_schedule.producers.size(); - uint32_t required_confs = (uint32_t)(num_active_producers * 2 / 3) + 1; - - if( confirm_count.size() < config::maximum_tracked_dpos_confirmations ) { - result.confirm_count.reserve( confirm_count.size() + 1 ); - result.confirm_count = confirm_count; - result.confirm_count.resize( confirm_count.size() + 1 ); - result.confirm_count.back() = (uint8_t)required_confs; - } else { - result.confirm_count.resize( confirm_count.size() ); - memcpy( &result.confirm_count[0], &confirm_count[1], confirm_count.size() - 1 ); - result.confirm_count.back() = (uint8_t)required_confs; - } +// auto num_active_producers = active_schedule.producers.size(); +// uint32_t required_confs = (uint32_t)(num_active_producers * 2 / 3) + 1; + +// if( confirm_count.size() < config::maximum_tracked_dpos_confirmations ) { +// result.confirm_count.reserve( confirm_count.size() + 1 ); +// result.confirm_count = confirm_count; +// result.confirm_count.resize( confirm_count.size() + 1 ); +// result.confirm_count.back() = (uint8_t)required_confs; +// } else { +// result.confirm_count.resize( confirm_count.size() ); +// memcpy( &result.confirm_count[0], &confirm_count[1], confirm_count.size() - 1 ); +// result.confirm_count.back() = (uint8_t)required_confs; +// } return result; } /// generate_next bool block_header_state::maybe_promote_pending() { - if( pending_schedule.producers.size() && - dpos_irreversible_blocknum >= pending_schedule_lib_num ) + if (pending_schedule.producers.size()) + //TODO: is this actually safe? +// bft_irreversible_blocknum >= pending_schedule_lib_num ) { active_schedule = move( pending_schedule ); @@ -99,7 +101,7 @@ namespace eosio { namespace chain { if( existing != producer_to_last_produced.end() ) { new_producer_to_last_produced[pro.producer_name] = existing->second; } else { - new_producer_to_last_produced[pro.producer_name] = dpos_irreversible_blocknum; + new_producer_to_last_produced[pro.producer_name] = bft_irreversible_blocknum; } } @@ -109,7 +111,7 @@ namespace eosio { namespace chain { if( existing != producer_to_last_implied_irb.end() ) { new_producer_to_last_implied_irb[pro.producer_name] = existing->second; } else { - new_producer_to_last_implied_irb[pro.producer_name] = dpos_irreversible_blocknum; + new_producer_to_last_implied_irb[pro.producer_name] = bft_irreversible_blocknum; } } @@ -161,7 +163,7 @@ namespace eosio { namespace chain { /// below this point is state changes that cannot be validated with headers alone, but never-the-less, /// must result in header state changes - result.set_confirmed( h.confirmed ); +// result.set_confirmed( h.confirmed ); auto was_pending_promoted = result.maybe_promote_pending(); diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 2d2af25f0c1..5cb65370155 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -108,6 +108,8 @@ struct pending_state { optional _producer_block_id; + std::function _signer; + void push() { _db_session.push(); } @@ -119,6 +121,12 @@ struct controller_impl { chainbase::database reversible_blocks; ///< a special database to persist blocks that have successfully been applied but are still reversible block_log blog; optional pending; + optional pending_pbft_lib; + optional pending_pbft_checkpoint; + optional last_proposed_schedule_block_num; + optional last_promoted_proposed_schedule_block_num; + optional pbft_prepared; + optional my_prepare; block_state_ptr head; fork_database fork_db; wasm_interface wasmif; @@ -681,13 +689,22 @@ struct controller_impl { void commit_block( bool add_to_fork_db ) { auto reset_pending_on_exit = fc::make_scoped_exit([this]{ pending.reset(); + set_pbft_lib(); + set_pbft_lscb(); }); try { + set_pbft_lib(); + set_pbft_lscb(); if (add_to_fork_db) { pending->_pending_block_state->validated = true; auto new_bsp = fork_db.add(pending->_pending_block_state, true); emit(self.accepted_block_header, pending->_pending_block_state); + fork_db.mark_pbft_prepared_fork(head->id); + fork_db.mark_pbft_my_prepare_fork(head->id); + + if (pbft_prepared) fork_db.mark_pbft_prepared_fork(*pbft_prepared); + if (my_prepare) fork_db.mark_pbft_my_prepare_fork(*my_prepare); head = fork_db.head(); EOS_ASSERT(new_bsp == head, fork_database_exception, "committed block did not become the new head in fork database"); } @@ -1125,7 +1142,7 @@ struct controller_impl { pending->_pending_block_state = std::make_shared( *head, when ); // promotes pending schedule (if any) to active pending->_pending_block_state->in_current_chain = true; - pending->_pending_block_state->set_confirmed(confirm_block_count); +// pending->_pending_block_state->set_confirmed(confirm_block_count); auto was_pending_promoted = pending->_pending_block_state->maybe_promote_pending(); @@ -1133,18 +1150,25 @@ struct controller_impl { if ( read_mode == db_read_mode::SPECULATIVE || pending->_block_status != controller::block_status::incomplete ) { const auto& gpo = db.get(); + if (gpo.proposed_schedule_block_num) { + last_proposed_schedule_block_num.reset(); + last_proposed_schedule_block_num.emplace(*gpo.proposed_schedule_block_num); + } if( gpo.proposed_schedule_block_num.valid() && // if there is a proposed schedule that was proposed in a block ... - ( *gpo.proposed_schedule_block_num <= pending->_pending_block_state->dpos_irreversible_blocknum ) && // ... that has now become irreversible ... +// ( *gpo.proposed_schedule_block_num <= pending->_pending_block_state->pbft_stable_checkpoint_blocknum ) && // ... that has now become irreversible ... pending->_pending_block_state->pending_schedule.producers.size() == 0 && // ... and there is room for a new pending schedule ... - !was_pending_promoted // ... and not just because it was promoted to active at the start of this block, then: + !was_pending_promoted && // ... and not just because it was promoted to active at the start of this block, then: + pending->_pending_block_state->block_num > *gpo.proposed_schedule_block_num //TODO: to be optimised. ) { // Promote proposed schedule to pending schedule. if( !replaying ) { ilog( "promoting proposed schedule (set in block ${proposed_num}) to pending; current block: ${n} lib: ${lib} schedule: ${schedule} ", ("proposed_num", *gpo.proposed_schedule_block_num)("n", pending->_pending_block_state->block_num) - ("lib", pending->_pending_block_state->dpos_irreversible_blocknum) + ("lib", pending->_pending_block_state->bft_irreversible_blocknum) ("schedule", static_cast(gpo.proposed_schedule) ) ); + last_promoted_proposed_schedule_block_num.reset(); + last_promoted_proposed_schedule_block_num.emplace(pending->_pending_block_state->block_num); } pending->_pending_block_state->set_new_producers( gpo.proposed_schedule ); db.modify( gpo, [&]( auto& gp ) { @@ -1189,7 +1213,7 @@ struct controller_impl { void apply_block( const signed_block_ptr& b, controller::block_status s ) { try { try { - EOS_ASSERT( b->block_extensions.size() == 0, block_validate_exception, "no supported extensions" ); +// EOS_ASSERT( b->block_extensions.size() == 0, block_validate_exception, "no supported extensions" ); auto producer_block_id = b->id(); start_block( b->timestamp, b->confirmed, s , producer_block_id); @@ -1296,15 +1320,22 @@ struct controller_impl { auto& b = new_header_state->block; emit( self.pre_accepted_block, b ); - fork_db.add( new_header_state, false ); + auto current_head = b->id(); + + fork_db.add( new_header_state, false ); if (conf.trusted_producers.count(b->producer)) { - trusted_producer_light_validation = true; + trusted_producer_light_validation = true; }; emit( self.accepted_block_header, new_header_state ); + set_pbft_lib(); + set_pbft_lscb(); + fork_db.mark_pbft_my_prepare_fork(current_head); + fork_db.mark_pbft_prepared_fork(current_head); + if ( read_mode != db_read_mode::IRREVERSIBLE ) { - maybe_switch_forks( s ); + maybe_switch_forks( s ); } } FC_LOG_AND_RETHROW( ) @@ -1326,10 +1357,19 @@ struct controller_impl { emit( self.accepted_block_header, new_header_state ); - if ( read_mode != db_read_mode::IRREVERSIBLE ) { + if ( read_mode != db_read_mode::IRREVERSIBLE) { maybe_switch_forks( s ); } +// // apply stable checkpoint when there is a valid one +// // TODO:// verify required one more time? + if (b->block_extensions.size() >0 && b->block_extensions.back().first == 0) { + pbft_commit_local(b->id()); + set_pbft_lib(); + set_pbft_latest_checkpoint(b->id()); + set_pbft_lscb(); + } + // on replay irreversible is not emitted by fork database, so emit it explicitly here if( s == controller::block_status::irreversible ) emit( self.irreversible_block, new_header_state ); @@ -1337,7 +1377,49 @@ struct controller_impl { } FC_LOG_AND_RETHROW( ) } - void maybe_switch_forks( controller::block_status s ) { + void push_confirmation( const header_confirmation& c ) { + EOS_ASSERT(!pending, block_validate_exception, "it is not valid to push a confirmation when there is a pending block"); + fork_db.add( c ); + emit( self.accepted_confirmation, c ); + if ( read_mode != db_read_mode::IRREVERSIBLE ) { + maybe_switch_forks(); + } + } + + void pbft_commit_local( const block_id_type& id ) { + pending_pbft_lib.reset(); + pending_pbft_lib.emplace(id); + } + + void set_pbft_lib() { + + if ((!pending || pending->_block_status != controller::block_status::incomplete) && pending_pbft_lib ) { + fork_db.set_bft_irreversible(*pending_pbft_lib); + pending_pbft_lib.reset(); + + if (read_mode != db_read_mode::IRREVERSIBLE) { + maybe_switch_forks(); + } + } + } + + void set_pbft_latest_checkpoint( const block_id_type& id ) { + pending_pbft_checkpoint.reset(); + pending_pbft_checkpoint.emplace(id); + } + + void set_pbft_lscb() { + if ((!pending || pending->_block_status != controller::block_status::incomplete) && pending_pbft_checkpoint ) { + fork_db.set_latest_checkpoint(*pending_pbft_checkpoint); + pending_pbft_checkpoint.reset(); + } + } + + void maybe_switch_forks( controller::block_status s = controller::block_status::complete ) { + + if (pbft_prepared) fork_db.mark_pbft_prepared_fork(*pbft_prepared); + if (my_prepare) fork_db.mark_pbft_my_prepare_fork(*my_prepare); + auto new_head = fork_db.head(); if( new_head->header.previous == head->id ) { @@ -1747,6 +1829,13 @@ chainbase::database& controller::mutable_db()const { return my->db; } const fork_database& controller::fork_db()const { return my->fork_db; } +std::map controller::my_signature_providers()const{ + return my->conf.my_signature_providers; +} + +void controller::set_my_signature_providers(std::map msp){ + my->conf.my_signature_providers = msp; +} void controller::start_block( block_timestamp_type when, uint16_t confirm_block_count) { validate_db_available_size(); @@ -1786,6 +1875,24 @@ void controller::push_block( std::future& block_state_future ) my->push_block( block_state_future ); } +void controller::pbft_commit_local( const block_id_type& id ) { + validate_db_available_size(); + my->pbft_commit_local(id); +} + +bool controller::pending_pbft_lib() { + if (my->pending_pbft_lib) return true; + return false; +} + +void controller::set_pbft_latest_checkpoint( const block_id_type& id ) { + my->set_pbft_latest_checkpoint(id); +} + +//void controller::set_pbft_prepared_block_id(optional bid){ +// my->pbft_prepared_block_id = bid; +//} + transaction_trace_ptr controller::push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline, uint32_t billed_cpu_time_us ) { validate_db_available_size(); EOS_ASSERT( get_read_mode() != chain::db_read_mode::READ_ONLY, transaction_type_exception, "push transaction not allowed in read-only mode" ); @@ -1905,6 +2012,39 @@ block_id_type controller::last_irreversible_block_id() const { } +uint32_t controller::last_stable_checkpoint_block_num() const { + return my->head->pbft_stable_checkpoint_blocknum; +} + +block_id_type controller::last_stable_checkpoint_block_id() const { + auto lscb_num = last_stable_checkpoint_block_num(); + const auto& tapos_block_summary = db().get((uint16_t)lscb_num); + + if( block_header::num_from_id(tapos_block_summary.block_id) == lscb_num ) + return tapos_block_summary.block_id; + + return fetch_block_by_number(lscb_num)->id(); +} + + +uint32_t controller::last_proposed_schedule_block_num() const { + if (my->last_proposed_schedule_block_num) { + return *my->last_proposed_schedule_block_num; + } + return block_num_type{}; +} + +uint32_t controller::last_promoted_proposed_schedule_block_num() const { + if (my->last_promoted_proposed_schedule_block_num) { + return *my->last_promoted_proposed_schedule_block_num; + } + return block_num_type{}; +} + +bool controller::is_replaying() const { + return my->replaying; +} + const dynamic_global_property_object& controller::get_dynamic_global_properties()const { return my->db.get(); } @@ -2079,6 +2219,33 @@ chain_id_type controller::get_chain_id()const { return my->chain_id; } +void controller::set_pbft_prepared(const block_id_type& id) const { + my->pbft_prepared.reset(); + my->pbft_prepared.emplace(id); + my->fork_db.mark_pbft_prepared_fork(id); + +// dlog("fork_db head ${h}", ("h", fork_db().head()->id)); +// dlog("prepared block id ${b}", ("b", id)); +} + +void controller::set_pbft_my_prepare(const block_id_type& id) const { + my->my_prepare.reset(); + my->my_prepare.emplace(id); + my->fork_db.mark_pbft_my_prepare_fork(id); +// dlog("fork_db head ${h}", ("h", fork_db().head()->id)); +// dlog("my prepare block id ${b}", ("b", id)); +} + +block_id_type controller::get_pbft_my_prepare() const { + if (my->my_prepare) return *my->my_prepare; + return block_id_type{}; +} + +void controller::reset_pbft_my_prepare() const { + if (my->my_prepare) my->fork_db.remove_pbft_my_prepare_fork(*my->my_prepare); + my->my_prepare.reset(); +} + db_read_mode controller::get_read_mode()const { return my->read_mode; } @@ -2181,6 +2348,19 @@ void controller::validate_reversible_available_size() const { EOS_ASSERT(free >= guard, reversible_guard_exception, "reversible free: ${f}, guard size: ${g}", ("f", free)("g",guard)); } +path controller::state_dir() const { + return my->conf.state_dir; +} + +path controller::blocks_dir() const { + return my->conf.blocks_dir; +} + +producer_schedule_type controller::initial_schedule() const { + return producer_schedule_type{ 0, {{eosio::chain::config::system_account_name, my->conf.genesis.initial_key}} }; +} + + bool controller::is_known_unexpired_transaction( const transaction_id_type& id) const { return db().find(id); } @@ -2205,4 +2385,10 @@ const flat_set &controller::get_resource_greylist() const { return my->conf.resource_greylist; } + +void controller::set_lib() const { + my->set_pbft_lib(); + my->set_pbft_lscb(); +} + } } /// eosio::chain diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 52b49fff449..c5ef9c1e878 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -31,12 +31,14 @@ namespace eosio { namespace chain { composite_key_compare< std::less, std::greater > >, ordered_non_unique< tag, - composite_key< block_header_state, - member, + composite_key< block_state, +// member, member, + member, + member, member >, - composite_key_compare< std::greater, std::greater, std::greater > + composite_key_compare< std::greater, std::greater, std::greater, std::greater > > > > fork_multi_index_type; @@ -95,9 +97,10 @@ namespace eosio { namespace chain { /// we cannot normally prune the lib if it is the head block because /// the next block needs to build off of the head block. We are exiting /// now so we can prune this block as irreversible before exiting. - auto lib = my->head->dpos_irreversible_blocknum; + auto lib = my->head->bft_irreversible_blocknum; + auto checkpoint = my->head->pbft_stable_checkpoint_blocknum; auto oldest = *my->index.get().begin(); - if( oldest->block_num <= lib ) { + if( oldest->block_num < lib && oldest->block_num < checkpoint ) { prune( oldest ); } @@ -138,11 +141,12 @@ namespace eosio { namespace chain { my->head = *my->index.get().begin(); - auto lib = my->head->dpos_irreversible_blocknum; + auto lib = my->head->bft_irreversible_blocknum; + auto checkpoint = my->head->pbft_stable_checkpoint_blocknum; auto oldest = *my->index.get().begin(); - if( oldest->block_num < lib ) { - prune( oldest ); + if( oldest->block_num < lib && oldest->block_num < checkpoint ) { + prune( oldest ); } return n; @@ -277,16 +281,110 @@ namespace eosio { namespace chain { } } - block_state_ptr fork_database::get_block(const block_id_type& id)const { + block_state_ptr fork_database::get_block(const block_id_type& id) const { auto itr = my->index.find( id ); if( itr != my->index.end() ) return *itr; return block_state_ptr(); } + void fork_database::mark_pbft_prepared_fork(const block_id_type &id) const { + auto& by_id_idx = my->index.get(); + auto itr = by_id_idx.find( id ); + EOS_ASSERT( itr != by_id_idx.end(), fork_db_block_not_found, "could not find block in fork database" ); + by_id_idx.modify( itr, [&]( auto& bsp ) { bsp->pbft_prepared = true; }); + + auto update = [&]( const vector& in ) { + vector updated; + + for( const auto& i : in ) { + auto& pidx = my->index.get(); + auto pitr = pidx.lower_bound( i ); + auto epitr = pidx.upper_bound( i ); + while( pitr != epitr ) { + pidx.modify( pitr, [&]( auto& bsp ) { + bsp->pbft_prepared = true; + updated.push_back( bsp->id ); + }); + ++pitr; + } + } + return updated; + }; + + vector queue{id}; + while(!queue.empty()) { + queue = update( queue ); + } + my->head = *my->index.get().begin(); + } + + void fork_database::mark_pbft_my_prepare_fork(const block_id_type &id) const { + auto& by_id_idx = my->index.get(); + auto itr = by_id_idx.find( id ); + EOS_ASSERT( itr != by_id_idx.end(), fork_db_block_not_found, "could not find block in fork database" ); + by_id_idx.modify( itr, [&]( auto& bsp ) { bsp->pbft_my_prepare = true; }); + + auto update = [&]( const vector& in ) { + vector updated; + + for( const auto& i : in ) { + auto& pidx = my->index.get(); + auto pitr = pidx.lower_bound( i ); + auto epitr = pidx.upper_bound( i ); + while( pitr != epitr ) { + pidx.modify( pitr, [&]( auto& bsp ) { + bsp->pbft_my_prepare = true; + updated.push_back( bsp->id ); + }); + ++pitr; + } + } + return updated; + }; + + vector queue{id}; + while(!queue.empty()) { + queue = update( queue ); + } + my->head = *my->index.get().begin(); + } + + void fork_database::remove_pbft_my_prepare_fork(const block_id_type &id) const { + auto& by_id_idx = my->index.get(); + auto itr = by_id_idx.find( id ); + EOS_ASSERT( itr != by_id_idx.end(), fork_db_block_not_found, "could not find block in fork database" ); + by_id_idx.modify( itr, [&]( auto& bsp ) { bsp->pbft_my_prepare = false; }); + + auto update = [&]( const vector& in ) { + vector updated; + + for( const auto& i : in ) { + auto& pidx = my->index.get(); + auto pitr = pidx.lower_bound( i ); + auto epitr = pidx.upper_bound( i ); + while( pitr != epitr ) { + pidx.modify( pitr, [&]( auto& bsp ) { + bsp->pbft_my_prepare = false; + updated.push_back( bsp->id ); + }); + ++pitr; + } + } + return updated; + }; + + vector queue{id}; + while(!queue.empty()) { + queue = update( queue ); + } + my->head = *my->index.get().begin(); + } + block_state_ptr fork_database::get_block_in_current_chain_by_num( uint32_t n )const { const auto& numidx = my->index.get(); auto nitr = numidx.lower_bound( n ); + // following asserts removed so null can be returned //FC_ASSERT( nitr != numidx.end() && (*nitr)->block_num == n, // "could not find block in fork database with block number ${block_num}", ("block_num", n) ); @@ -316,10 +414,13 @@ namespace eosio { namespace chain { * This will require a search over all forks */ void fork_database::set_bft_irreversible( block_id_type id ) { - auto& idx = my->index.get(); - auto itr = idx.find(id); - uint32_t block_num = (*itr)->block_num; - idx.modify( itr, [&]( auto& bsp ) { + auto b = get_block( id ); + EOS_ASSERT( b, fork_db_block_not_found, "unable to find block id ${id}", ("id",id)); + + auto& idx = my->index.get(); + auto itr = idx.find(id); + uint32_t block_num = (*itr)->block_num; + idx.modify( itr, [&]( auto& bsp ) { bsp->bft_irreversible_blocknum = bsp->block_num; }); @@ -332,27 +433,65 @@ namespace eosio { namespace chain { auto update = [&]( const vector& in ) { vector updated; - for( const auto& i : in ) { - auto& pidx = my->index.get(); - auto pitr = pidx.lower_bound( i ); - auto epitr = pidx.upper_bound( i ); - while( pitr != epitr ) { - pidx.modify( pitr, [&]( auto& bsp ) { - if( bsp->bft_irreversible_blocknum < block_num ) { - bsp->bft_irreversible_blocknum = block_num; - updated.push_back( bsp->id ); - } - }); - ++pitr; - } - } - return updated; - }; + for( const auto& i : in ) { + auto& pidx = my->index.get(); + auto pitr = pidx.lower_bound( i ); + auto epitr = pidx.upper_bound( i ); + while( pitr != epitr ) { + pidx.modify( pitr, [&]( auto& bsp ) { + if( bsp->bft_irreversible_blocknum < block_num ) { + bsp->bft_irreversible_blocknum = block_num; + updated.push_back( bsp->id ); + } + }); + ++pitr; + } + } + return updated; + }; + + vector queue{id}; + while( queue.size() ) { + queue = update( queue ); + } + } - vector queue{id}; - while( queue.size() ) { - queue = update( queue ); - } + void fork_database::set_latest_checkpoint( block_id_type id) { + auto b = get_block( id ); + EOS_ASSERT( b, fork_db_block_not_found, "unable to find block id ${id}", ("id",id)); + + auto& idx = my->index.get(); + auto itr = idx.find(id); + uint32_t block_num = (*itr)->block_num; + idx.modify( itr, [&]( auto& bsp ) { + bsp->pbft_stable_checkpoint_blocknum = bsp->block_num; + }); + + auto update = [&]( const vector& in ) { + vector updated; + + for( const auto& i : in ) { + auto& pidx = my->index.get(); + auto pitr = pidx.lower_bound( i ); + auto epitr = pidx.upper_bound( i ); + while( pitr != epitr ) { + pidx.modify( pitr, [&]( auto& bsp ) { + if( bsp->pbft_stable_checkpoint_blocknum < block_num ) { + bsp->pbft_stable_checkpoint_blocknum = block_num; + updated.push_back( bsp->id ); + } + }); + ++pitr; + } + } + return updated; + }; + + vector queue{id}; + while( queue.size() ) { + queue = update( queue ); + } } -} } /// eosio::chain + + } } /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/block_header.hpp b/libraries/chain/include/eosio/chain/block_header.hpp index bf9cf0bedb8..bc6f196d089 100644 --- a/libraries/chain/include/eosio/chain/block_header.hpp +++ b/libraries/chain/include/eosio/chain/block_header.hpp @@ -18,7 +18,7 @@ namespace eosio { namespace chain { * behavior. When producing a block a producer is always confirming at least the block he * is building off of. A producer cannot confirm "this" block, only prior blocks. */ - uint16_t confirmed = 1; + uint16_t confirmed = 0; block_id_type previous; diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index c318843d5df..3f38d329885 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -16,6 +16,7 @@ struct block_header_state { uint32_t dpos_proposed_irreversible_blocknum = 0; uint32_t dpos_irreversible_blocknum = 0; uint32_t bft_irreversible_blocknum = 0; + uint32_t pbft_stable_checkpoint_blocknum = 0; uint32_t pending_schedule_lib_num = 0; /// last irr block num digest_type pending_schedule_hash; producer_schedule_type pending_schedule; @@ -61,6 +62,7 @@ struct block_header_state { FC_REFLECT( eosio::chain::block_header_state, (id)(block_num)(header)(dpos_proposed_irreversible_blocknum)(dpos_irreversible_blocknum)(bft_irreversible_blocknum) + (pbft_stable_checkpoint_blocknum) (pending_schedule_lib_num)(pending_schedule_hash) (pending_schedule)(active_schedule)(blockroot_merkle) (producer_to_last_produced)(producer_to_last_implied_irb)(block_signing_key) diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 2292392ade4..170cdd37abc 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -21,6 +21,8 @@ namespace eosio { namespace chain { signed_block_ptr block; bool validated = false; bool in_current_chain = false; + bool pbft_prepared = false; + bool pbft_my_prepare = false; /// this data is redundant with the data stored in block, but facilitates /// recapturing transactions when we pop a block @@ -31,4 +33,4 @@ namespace eosio { namespace chain { } } /// namespace eosio::chain -FC_REFLECT_DERIVED( eosio::chain::block_state, (eosio::chain::block_header_state), (block)(validated)(in_current_chain) ) +FC_REFLECT_DERIVED( eosio::chain::block_state, (eosio::chain::block_header_state), (block)(validated)(in_current_chain)(pbft_prepared)(pbft_my_prepare) ) diff --git a/libraries/chain/include/eosio/chain/config.hpp b/libraries/chain/include/eosio/chain/config.hpp index 0d5ff9e9469..85599283cd7 100644 --- a/libraries/chain/include/eosio/chain/config.hpp +++ b/libraries/chain/include/eosio/chain/config.hpp @@ -19,9 +19,12 @@ const static auto default_reversible_guard_size = 2*1024*1024ll;/// 1MB * 340 bl const static auto default_state_dir_name = "state"; const static auto forkdb_filename = "forkdb.dat"; +const static auto pbftdb_filename = "pbftdb.dat"; const static auto default_state_size = 1*1024*1024*1024ll; const static auto default_state_guard_size = 128*1024*1024ll; +const static auto checkpoints_filename = "checkpoints.dat"; + const static uint64_t system_account_name = N(eosio); const static uint64_t null_account_name = N(eosio.null); @@ -104,8 +107,8 @@ const static uint32_t default_abi_serializer_max_time_ms = 15*1000; ///< defau const static int producer_repetitions = 12; const static int max_producers = 125; -const static size_t maximum_tracked_dpos_confirmations = 1024; ///< -static_assert(maximum_tracked_dpos_confirmations >= ((max_producers * 2 / 3) + 1) * producer_repetitions, "Settings never allow for DPOS irreversibility" ); +//const static size_t maximum_tracked_dpos_confirmations = 1024; ///< +//static_assert(maximum_tracked_dpos_confirmations >= ((max_producers * 2 / 3) + 1) * producer_repetitions, "Settings never allow for DPOS irreversibility" ); /** diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 9e6947fdff9..e4d38785873 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -49,6 +49,8 @@ namespace eosio { namespace chain { LIGHT }; + using signature_provider_type = std::function; + class controller { public: @@ -66,6 +68,8 @@ namespace eosio { namespace chain { uint64_t state_guard_size = chain::config::default_state_guard_size; uint64_t reversible_cache_size = chain::config::default_reversible_cache_size; uint64_t reversible_guard_size = chain::config::default_reversible_guard_size; + path checkpoints_dir = blocks_dir; + uint32_t sig_cpu_bill_pct = chain::config::default_sig_cpu_bill_pct; uint16_t thread_pool_size = chain::config::default_controller_thread_pool_size; bool read_only = false; @@ -82,6 +86,10 @@ namespace eosio { namespace chain { flat_set resource_greylist; flat_set trusted_producers; + + + std::map my_signature_providers; + std::set my_producers; }; enum class block_status { @@ -139,8 +147,24 @@ namespace eosio { namespace chain { const chainbase::database& db()const; + void pbft_commit_local( const block_id_type& id ); + + bool pending_pbft_lib(); + + uint32_t last_proposed_schedule_block_num()const; + uint32_t last_promoted_proposed_schedule_block_num()const; + + void set_pbft_latest_checkpoint( const block_id_type& id ); + uint32_t last_stable_checkpoint_block_num()const; + block_id_type last_stable_checkpoint_block_id()const; + + const fork_database& fork_db()const; + std::map my_signature_providers()const; + void set_my_signature_providers(std::map msp); + + const account_object& get_account( account_name n )const; const global_property_object& get_global_properties()const; const dynamic_global_property_object& get_dynamic_global_properties()const; @@ -235,6 +259,16 @@ namespace eosio { namespace chain { void set_subjective_cpu_leeway(fc::microseconds leeway); + path state_dir()const; + path blocks_dir()const; + producer_schedule_type initial_schedule()const; + bool is_replaying()const; + + void set_pbft_prepared(const block_id_type& id)const; + void set_pbft_my_prepare(const block_id_type& id)const; + block_id_type get_pbft_my_prepare()const; + void reset_pbft_my_prepare()const; + signal pre_accepted_block; signal accepted_block_header; signal accepted_block; @@ -244,6 +278,8 @@ namespace eosio { namespace chain { signal accepted_confirmation; signal bad_alloc; + void set_lib()const; + /* signal pre_apply_block; signal post_apply_block; @@ -300,6 +336,7 @@ FC_REFLECT( eosio::chain::controller::config, (state_dir) (state_size) (reversible_cache_size) + (checkpoints_dir) (read_only) (force_all_checks) (disable_replay_opts) diff --git a/libraries/chain/include/eosio/chain/exceptions.hpp b/libraries/chain/include/eosio/chain/exceptions.hpp index 6c3e504d349..65d9e8c0ce6 100644 --- a/libraries/chain/include/eosio/chain/exceptions.hpp +++ b/libraries/chain/include/eosio/chain/exceptions.hpp @@ -136,6 +136,8 @@ namespace eosio { namespace chain { FC_DECLARE_DERIVED_EXCEPTION( fork_db_block_not_found, fork_database_exception, 3020001, "Block can not be found" ) + FC_DECLARE_DERIVED_EXCEPTION( pbft_exception, chain_exception, + 4010000, "PBFT exception" ) FC_DECLARE_DERIVED_EXCEPTION( block_validate_exception, chain_exception, 3030000, "Block exception" ) diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 998157ab41a..5cac3e3c024 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -69,9 +69,18 @@ namespace eosio { namespace chain { * it is removed unless it is the head block. */ signal irreversible; - - private: + void set_bft_irreversible( block_id_type id ); + + void set_latest_checkpoint( block_id_type id); + + void mark_pbft_prepared_fork(const block_id_type &id) const; + + void mark_pbft_my_prepare_fork(const block_id_type &id) const; + + void remove_pbft_my_prepare_fork(const block_id_type &id) const; + + private: unique_ptr my; }; diff --git a/libraries/chain/include/eosio/chain/pbft.hpp b/libraries/chain/include/eosio/chain/pbft.hpp new file mode 100644 index 00000000000..2568d26bb63 --- /dev/null +++ b/libraries/chain/include/eosio/chain/pbft.hpp @@ -0,0 +1,232 @@ +#pragma once + +#include +#include +#include + +namespace eosio { + namespace chain { + using namespace std; + using namespace fc; + + struct psm_cache { + vector prepares_cache; + vector commits_cache; + vector view_changes_cache; + vector prepared_certificate; + vector view_changed_certificate; + }; + + class psm_machine { + class psm_state *current; + + public: + explicit psm_machine(pbft_database& pbft_db); + ~psm_machine(); + + void set_current(psm_state *s) { + current = s; + } + + void on_prepare(pbft_prepare &e); + void send_prepare(); + + void on_commit(pbft_commit &e); + void send_commit(); + + void on_view_change(pbft_view_change &e); + void send_view_change(); + + void on_new_view(pbft_new_view &e); + + template + void transit_to_committed_state(T const & s); + + template + void transit_to_prepared_state(T const & s); + + void send_pbft_view_change(); + + template + void transit_to_view_change_state(T const & s); + + template + void transit_to_new_view(const pbft_new_view &new_view, T const &s); + + const vector &get_prepares_cache() const; + + void set_prepares_cache(const vector &prepares_cache); + + const vector &get_commits_cache() const; + + void set_commits_cache(const vector &commits_cache); + + const vector &get_view_changes_cache() const; + + void set_view_changes_cache(const vector &view_changes_cache); + + const uint32_t &get_current_view() const; + + void set_current_view(const uint32_t ¤t_view); + + const vector &get_prepared_certificate() const; + + void set_prepared_certificate(const vector &prepared_certificate); + + const vector &get_view_changed_certificate() const; + + void set_view_changed_certificate(const vector &view_changed_certificate); + + const uint32_t &get_target_view_retries() const; + + void set_target_view_retries(const uint32_t &target_view_reties); + + const uint32_t &get_target_view() const; + + void set_target_view(const uint32_t &target_view); + + const uint32_t &get_view_change_timer() const; + + void set_view_change_timer(const uint32_t &view_change_timer); + + void manually_set_current_view(const uint32_t ¤t_view); + + protected: + psm_cache cache; + uint32_t current_view; + uint32_t target_view_retries; + uint32_t target_view; + uint32_t view_change_timer; + + private: + pbft_database &pbft_db; + + }; + + class psm_state { + + public: + psm_state(); + ~psm_state(); + + virtual void on_prepare(psm_machine *m, pbft_prepare &e, pbft_database &pbft_db) = 0; + + virtual void send_prepare(psm_machine *m, pbft_database &pbft_db) = 0; + + virtual void on_commit(psm_machine *m, pbft_commit &e, pbft_database &pbft_db) = 0; + + virtual void send_commit(psm_machine *m, pbft_database &pbft_db) = 0; + + virtual void on_view_change(psm_machine *m, pbft_view_change &e, pbft_database &pbft_db) = 0; + + virtual void send_view_change(psm_machine *m, pbft_database &pbft_db) = 0; + + virtual void on_new_view(psm_machine *m, pbft_new_view &e, pbft_database &pbft_db) = 0; + + virtual void manually_set_view(psm_machine *m, const uint32_t &view) = 0; + + }; + + class psm_prepared_state final: public psm_state { + + public: + psm_prepared_state(); + ~psm_prepared_state(); + + void on_prepare(psm_machine *m, pbft_prepare &e, pbft_database &pbft_db) override; + + void send_prepare(psm_machine *m, pbft_database &pbft_db) override; + + void on_commit(psm_machine *m, pbft_commit &e, pbft_database &pbft_db) override; + + void send_commit(psm_machine *m, pbft_database &pbft_db) override; + + void on_view_change(psm_machine *m, pbft_view_change &e, pbft_database &pbft_db) override; + + void send_view_change(psm_machine *m, pbft_database &pbft_db) override; + + void on_new_view(psm_machine *m, pbft_new_view &e, pbft_database &pbft_db) override; + + void manually_set_view(psm_machine *m, const uint32_t &view) override; + + bool pending_commit_local; + + }; + + class psm_committed_state final: public psm_state { + public: + psm_committed_state(); + ~psm_committed_state(); + + void on_prepare(psm_machine *m, pbft_prepare &e, pbft_database &pbft_db) override; + + void send_prepare(psm_machine *m, pbft_database &pbft_db) override; + + void on_commit(psm_machine *m, pbft_commit &e, pbft_database &pbft_db) override; + + void send_commit(psm_machine *m, pbft_database &pbft_db) override; + + void on_view_change(psm_machine *m, pbft_view_change &e, pbft_database &pbft_db) override; + + void send_view_change(psm_machine *m, pbft_database &pbft_db) override; + + void on_new_view(psm_machine *m, pbft_new_view &e, pbft_database &pbft_db) override; + + void manually_set_view(psm_machine *m, const uint32_t &view) override; + + bool pending_commit_local; + }; + + class psm_view_change_state final: public psm_state { + public: + void on_prepare(psm_machine *m, pbft_prepare &e, pbft_database &pbft_db) override; + + void send_prepare(psm_machine *m, pbft_database &pbft_db) override; + + void on_commit(psm_machine *m, pbft_commit &e, pbft_database &pbft_db) override; + + void send_commit(psm_machine *m, pbft_database &pbft_db) override; + + void on_view_change(psm_machine *m, pbft_view_change &e, pbft_database &pbft_db) override; + + void send_view_change(psm_machine *m, pbft_database &pbft_db) override; + + void on_new_view(psm_machine *m, pbft_new_view &e, pbft_database &pbft_db) override; + + void manually_set_view(psm_machine *m, const uint32_t &view) override; + }; + + struct pbft_config { + uint32_t view_change_timeout = 6; + bool bp_candidate = false; + }; + + class pbft_controller { + public: + pbft_controller(controller& ctrl); + ~pbft_controller(); + + pbft_database pbft_db; + psm_machine state_machine; + pbft_config config; + + void maybe_pbft_prepare(); + void maybe_pbft_commit(); + void maybe_pbft_view_change(); + void send_pbft_checkpoint(); + + void on_pbft_prepare(pbft_prepare &p); + void on_pbft_commit(pbft_commit &c); + void on_pbft_view_change(pbft_view_change &vc); + void on_pbft_new_view(pbft_new_view &nv); + void on_pbft_checkpoint(pbft_checkpoint &cp); + + private: + fc::path datadir; + + + }; + } +} /// namespace eosio::chain + +FC_REFLECT(eosio::chain::pbft_controller, (pbft_db)(state_machine)(config)) \ No newline at end of file diff --git a/libraries/chain/include/eosio/chain/pbft_database.hpp b/libraries/chain/include/eosio/chain/pbft_database.hpp new file mode 100644 index 00000000000..4a9abf048ba --- /dev/null +++ b/libraries/chain/include/eosio/chain/pbft_database.hpp @@ -0,0 +1,616 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace eosio { + namespace chain { + using boost::multi_index_container; + using namespace boost::multi_index; + using namespace std; + using boost::uuids::uuid; + + + struct block_info { + block_id_type block_id; + block_num_type block_num = 0; + }; + + struct pbft_prepare { + string uuid; + uint32_t view; + block_num_type block_num = 0; + block_id_type block_id; + public_key_type public_key; + chain_id_type chain_id = chain_id_type(""); + signature_type producer_signature; + time_point timestamp = time_point::now(); + + + bool operator==(const pbft_prepare &rhs) const { + return view == rhs.view + && block_num == rhs.block_num + && block_id == rhs.block_id + && public_key == rhs.public_key + && chain_id == rhs.chain_id + && timestamp == rhs.timestamp; + } + + bool operator<(const pbft_prepare &rhs) const { + if (block_num < rhs.block_num) { + return true; + } else return block_num == rhs.block_num && view < rhs.view; + } + + digest_type digest() const { + digest_type::encoder enc; + fc::raw::pack(enc, view); + fc::raw::pack(enc, block_num); + fc::raw::pack(enc, block_id); + fc::raw::pack(enc, public_key); + fc::raw::pack(enc, chain_id); + fc::raw::pack(enc, timestamp); + return enc.result(); + } + + bool is_signature_valid() const { + try { + auto pk = crypto::public_key(producer_signature, digest(), true); + return public_key == pk; + } catch (fc::exception & /*e*/) { + return false; + } + } + }; + + struct pbft_commit { + string uuid; + uint32_t view; + block_num_type block_num = 0; + block_id_type block_id; + public_key_type public_key; + chain_id_type chain_id = chain_id_type(""); + signature_type producer_signature; + time_point timestamp = time_point::now(); + + + bool operator==(const pbft_commit &rhs) const { + return view == rhs.view + && block_num == rhs.block_num + && block_id == rhs.block_id + && public_key == rhs.public_key + && chain_id == rhs.chain_id + && timestamp == rhs.timestamp; + } + + bool operator<(const pbft_commit &rhs) const { + if (block_num < rhs.block_num) { + return true; + } else return block_num == rhs.block_num && view < rhs.view; + } + + digest_type digest() const { + digest_type::encoder enc; + fc::raw::pack(enc, view); + fc::raw::pack(enc, block_num); + fc::raw::pack(enc, block_id); + fc::raw::pack(enc, public_key); + fc::raw::pack(enc, chain_id); + fc::raw::pack(enc, timestamp); + return enc.result(); + } + + bool is_signature_valid() const { + try { + auto pk = crypto::public_key(producer_signature, digest(), true); + return public_key == pk; + } catch (fc::exception & /*e*/) { + return false; + } + } + }; + + struct pbft_checkpoint { + string uuid; + block_num_type block_num = 0; + block_id_type block_id; + public_key_type public_key; + chain_id_type chain_id = chain_id_type(""); + signature_type producer_signature; + time_point timestamp = time_point::now(); + + bool operator==(const pbft_checkpoint &rhs) const { + return block_num == rhs.block_num + && block_id == rhs.block_id + && public_key == rhs.public_key + && chain_id == rhs.chain_id + && timestamp == rhs.timestamp; + + } + + bool operator<(const pbft_checkpoint &rhs) const { + return block_num < rhs.block_num; + } + + digest_type digest() const { + digest_type::encoder enc; + fc::raw::pack(enc, block_num); + fc::raw::pack(enc, block_id); + fc::raw::pack(enc, public_key); + fc::raw::pack(enc, chain_id); + fc::raw::pack(enc, timestamp); + return enc.result(); + } + + bool is_signature_valid() const { + try { + auto pk = crypto::public_key(producer_signature, digest(), true); + return public_key == pk; + } catch (fc::exception & /*e*/) { + return false; + } + } + }; + + struct pbft_stable_checkpoint { + block_num_type block_num = 0; + block_id_type block_id; + vector checkpoints; + chain_id_type chain_id = chain_id_type(""); + + bool operator==(const pbft_stable_checkpoint &rhs) const { + return block_id == rhs.block_id + && block_num == rhs.block_num + && checkpoints == rhs.checkpoints + && chain_id == rhs.chain_id; + } + + bool operator<(const pbft_stable_checkpoint &rhs) const { + return block_num < rhs.block_num; + } + + digest_type digest() const { + digest_type::encoder enc; + fc::raw::pack(enc, block_num); + fc::raw::pack(enc, block_id); + fc::raw::pack(enc, checkpoints); + fc::raw::pack(enc, chain_id); + return enc.result(); + } + }; + + struct pbft_prepared_certificate { + block_id_type block_id; + block_num_type block_num = 0; + vector prepares; + + public_key_type public_key; + signature_type producer_signature; + + bool operator==(const pbft_prepared_certificate &rhs) const { + return block_num == rhs.block_num + && block_id == rhs.block_id + && prepares == rhs.prepares + && public_key == rhs.public_key; + } + + digest_type digest() const { + digest_type::encoder enc; + fc::raw::pack(enc, block_id); + fc::raw::pack(enc, block_num); + fc::raw::pack(enc, prepares); + fc::raw::pack(enc, public_key); + return enc.result(); + } + + bool is_signature_valid() const { + try { + auto pk = crypto::public_key(producer_signature, digest(), true); + return public_key == pk; + } catch (fc::exception & /*e*/) { + return false; + } + } + }; + + struct pbft_view_change { + string uuid; + uint32_t current_view; + uint32_t target_view; + pbft_prepared_certificate prepared; + pbft_stable_checkpoint stable_checkpoint; + public_key_type public_key; + chain_id_type chain_id = chain_id_type(""); + signature_type producer_signature; + time_point timestamp = time_point::now(); + + bool operator==(const pbft_view_change &rhs) const { + return current_view == rhs.current_view + && target_view == rhs.target_view + && prepared == rhs.prepared + && stable_checkpoint == rhs.stable_checkpoint + && public_key == rhs.public_key + && chain_id == rhs.chain_id + && timestamp == rhs.timestamp; + + } + + bool operator<(const pbft_view_change &rhs) const { + return target_view < rhs.target_view; + } + + digest_type digest() const { + digest_type::encoder enc; + fc::raw::pack(enc, current_view); + fc::raw::pack(enc, target_view); + fc::raw::pack(enc, prepared); + fc::raw::pack(enc, stable_checkpoint); + fc::raw::pack(enc, public_key); + fc::raw::pack(enc, chain_id); + fc::raw::pack(enc, timestamp); + return enc.result(); + } + + bool is_signature_valid() const { + try { + auto pk = crypto::public_key(producer_signature, digest(), true); + return public_key == pk; + } catch (fc::exception & /*e*/) { + return false; + } + } + }; + + struct pbft_view_changed_certificate { + uint32_t view; + vector view_changes; + + public_key_type public_key; + signature_type producer_signature; + + bool operator==(const pbft_view_changed_certificate &rhs) const { + return view == rhs.view + && view_changes == rhs.view_changes + && public_key == rhs.public_key; + } + + digest_type digest() const { + digest_type::encoder enc; + fc::raw::pack(enc, view); + fc::raw::pack(enc, view_changes); + fc::raw::pack(enc, public_key); + return enc.result(); + } + + bool is_signature_valid() const { + try { + auto pk = crypto::public_key(producer_signature, digest(), true); + return public_key == pk; + } catch (fc::exception & /*e*/) { + return false; + } + } + }; + + struct pbft_new_view { + string uuid; + uint32_t view; + pbft_prepared_certificate prepared; + pbft_stable_checkpoint stable_checkpoint; + pbft_view_changed_certificate view_changed; + public_key_type public_key; + chain_id_type chain_id = chain_id_type(""); + signature_type producer_signature; + time_point timestamp = time_point::now(); + + bool operator==(const pbft_new_view &rhs) const { + return view == rhs.view + && prepared == rhs.prepared + && stable_checkpoint == rhs.stable_checkpoint + && view_changed == rhs.view_changed + && public_key == rhs.public_key + && chain_id == rhs.chain_id + && timestamp == rhs.timestamp; + } + + bool operator<(const pbft_new_view &rhs) const { + return view < rhs.view; + } + + digest_type digest() const { + digest_type::encoder enc; + fc::raw::pack(enc, view); + fc::raw::pack(enc, prepared); + fc::raw::pack(enc, stable_checkpoint); + fc::raw::pack(enc, view_changed); + fc::raw::pack(enc, public_key); + fc::raw::pack(enc, chain_id); + fc::raw::pack(enc, timestamp); + return enc.result(); + } + + bool is_signature_valid() const { + try { + auto pk = crypto::public_key(producer_signature, digest(), true); + return public_key == pk; + } catch (fc::exception & /*e*/) { + return false; + } + } + }; + + struct pbft_state { + block_id_type block_id; + block_num_type block_num = 0; + vector prepares; + bool should_prepared = false; + vector commits; + bool should_committed = false; + }; + + struct pbft_view_state { + uint32_t view; + vector view_changes; + bool should_view_changed = false; + }; + + struct pbft_checkpoint_state { + block_id_type block_id; + block_num_type block_num = 0; + vector checkpoints; + bool is_stable = false; + }; + + using pbft_state_ptr = std::shared_ptr; + using pbft_view_state_ptr = std::shared_ptr; + using pbft_checkpoint_state_ptr = std::shared_ptr; + + struct by_block_id; + struct by_num; + struct by_prepare_and_num; + struct by_commit_and_num; + typedef multi_index_container< + pbft_state_ptr, + indexed_by< + hashed_unique< + tag, + member, + std::hash + >, + ordered_non_unique< + tag, + composite_key< + pbft_state, + member + >, + composite_key_compare> + >, + ordered_non_unique< + tag, + composite_key< + pbft_state, + member, + member + >, + composite_key_compare, greater<>> + >, + ordered_non_unique< + tag, + composite_key< + pbft_state, + member, + member + >, + composite_key_compare, greater<>> + > + > + > + pbft_state_multi_index_type; + + struct by_view; + struct by_count_and_view; + typedef multi_index_container< + pbft_view_state_ptr, + indexed_by< + hashed_unique< + tag, + member, + std::hash + >, + ordered_non_unique< + tag, + composite_key< + pbft_view_state, + member, + member + >, + composite_key_compare, greater<>> + > + > + > + pbft_view_state_multi_index_type; + + struct by_block_id; + struct by_num; + typedef multi_index_container< + pbft_checkpoint_state_ptr, + indexed_by< + hashed_unique< + tag, + member, + std::hash + >, + ordered_non_unique< + tag, + composite_key< + pbft_checkpoint_state, +// member, + member + >, + composite_key_compare> + > + > + > + pbft_checkpoint_state_multi_index_type; + + class pbft_database { + public: + explicit pbft_database(controller &ctrl); + + ~pbft_database(); + + void close(); + + bool should_prepared(); + + bool should_committed(); + + uint32_t should_view_change(); + + bool should_new_view(uint32_t target_view); + + bool is_new_primary(uint32_t target_view); + + uint32_t get_proposed_new_view_num(); + + void add_pbft_prepare(pbft_prepare &p); + + void add_pbft_commit(pbft_commit &c); + + void add_pbft_view_change(pbft_view_change &vc); + + void add_pbft_checkpoint(pbft_checkpoint &cp); + + vector send_and_add_pbft_prepare( + const vector &pv = vector{}, + uint32_t current_view = 0); + + vector send_and_add_pbft_commit( + const vector &cv = vector{}, + uint32_t current_view = 0); + + vector send_and_add_pbft_view_change( + const vector &vcv = vector{}, + const vector &ppc = vector{}, + uint32_t current_view = 0, + uint32_t new_view = 1); + + pbft_new_view send_pbft_new_view( + const vector &vcc = vector{}, + uint32_t current_view = 1); + + vector generate_and_add_pbft_checkpoint(); + + bool is_valid_prepare(const pbft_prepare &p); + + bool is_valid_commit(const pbft_commit &c); + + void commit_local(); + + bool pending_pbft_lib(); + + void prune_pbft_index(); + + uint32_t get_committed_view(); + + chain_id_type chain_id(); + + vector generate_prepared_certificate(); + + vector generate_view_changed_certificate(uint32_t target_view); + + pbft_stable_checkpoint get_stable_checkpoint_by_id(const block_id_type &block_id); + + block_info cal_pending_stable_checkpoint() const; + + bool should_send_pbft_msg(); + + bool should_recv_pbft_msg(const public_key_type &pub_key); + + void send_pbft_checkpoint(); + + bool is_valid_checkpoint(const pbft_checkpoint &cp); + + bool is_valid_stable_checkpoint(const pbft_stable_checkpoint &scp); + + signal pbft_outgoing_prepare; + signal pbft_incoming_prepare; + + signal pbft_outgoing_commit; + signal pbft_incoming_commit; + + signal pbft_outgoing_view_change; + signal pbft_incoming_view_change; + + signal pbft_outgoing_new_view; + signal pbft_incoming_new_view; + + signal pbft_outgoing_checkpoint; + signal pbft_incoming_checkpoint; + + bool is_valid_view_change(const pbft_view_change &vc); + + bool is_valid_new_view(const pbft_new_view &nv); + + bool should_stop_view_change(const pbft_view_change &vc); + + private: + controller &ctrl; + pbft_state_multi_index_type pbft_state_index; + pbft_view_state_multi_index_type view_state_index; + pbft_checkpoint_state_multi_index_type checkpoint_index; + fc::path pbft_db_dir; + fc::path checkpoints_dir; + boost::uuids::random_generator uuid_generator; + + bool is_valid_prepared_certificate(const pbft_prepared_certificate &certificate); + + public_key_type get_new_view_primary_key(uint32_t target_view); + + vector> fetch_fork_from(vector block_infos); + + vector fetch_first_fork_from(vector &bi); + + producer_schedule_type lib_active_producers() const; + + template + void emit(const Signal &s, Arg &&a); + + void set(pbft_state_ptr s); + + void set(pbft_checkpoint_state_ptr s); + + void prune(const pbft_state_ptr &h); + + }; + + } +} /// namespace eosio::chain + +FC_REFLECT(eosio::chain::block_info, (block_id)(block_num)) +FC_REFLECT(eosio::chain::pbft_prepare, + (uuid)(view)(block_num)(block_id)(public_key)(chain_id)(producer_signature)(timestamp)) +FC_REFLECT(eosio::chain::pbft_commit, + (uuid)(view)(block_num)(block_id)(public_key)(chain_id)(producer_signature)(timestamp)) +FC_REFLECT(eosio::chain::pbft_view_change, + (uuid)(current_view)(target_view)(prepared)(stable_checkpoint)(public_key)(chain_id)(producer_signature)( + timestamp)) +FC_REFLECT(eosio::chain::pbft_new_view, + (uuid)(view)(prepared)(stable_checkpoint)(view_changed)(public_key)(chain_id)(producer_signature)(timestamp)) +FC_REFLECT(eosio::chain::pbft_state, (block_id)(block_num)(prepares)(should_prepared)(commits)(should_committed)) +FC_REFLECT(eosio::chain::pbft_prepared_certificate, (block_id)(block_num)(prepares)(public_key)(producer_signature)) +FC_REFLECT(eosio::chain::pbft_view_changed_certificate, (view)(view_changes)(public_key)(producer_signature)) +FC_REFLECT(eosio::chain::pbft_checkpoint, + (uuid)(block_num)(block_id)(public_key)(chain_id)(producer_signature)(timestamp)) +FC_REFLECT(eosio::chain::pbft_stable_checkpoint, (block_num)(block_id)(checkpoints)(chain_id)) +FC_REFLECT(eosio::chain::pbft_checkpoint_state, (block_id)(block_num)(checkpoints)(is_stable)) \ No newline at end of file diff --git a/libraries/chain/pbft.cpp b/libraries/chain/pbft.cpp new file mode 100644 index 00000000000..c63a76bf03c --- /dev/null +++ b/libraries/chain/pbft.cpp @@ -0,0 +1,601 @@ +#include +#include +#include + +namespace eosio { + namespace chain { + + pbft_controller::pbft_controller(controller &ctrl) : pbft_db(ctrl), state_machine(pbft_db) { + config.view_change_timeout = 6; + config.bp_candidate = true; + datadir = ctrl.state_dir(); + + if (!fc::is_directory(datadir)) + fc::create_directories(datadir); + + auto pbft_db_dat = datadir / config::pbftdb_filename; + if (fc::exists(pbft_db_dat)) { + string content; + fc::read_file_contents(pbft_db_dat, content); + + fc::datastream ds(content.data(), content.size()); + uint32_t current_view; + fc::raw::unpack(ds, current_view); + state_machine.set_current_view(current_view); + + state_machine.set_target_view(state_machine.get_current_view() + 1); + ilog("current view: ${cv}", ("cv", current_view)); + } + + fc::remove(pbft_db_dat); + } + + pbft_controller::~pbft_controller() { + fc::path pbft_db_dat = datadir / config::pbftdb_filename; + std::ofstream out(pbft_db_dat.generic_string().c_str(), + std::ios::out | std::ios::binary | std::ofstream::trunc); + + uint32_t current_view = state_machine.get_current_view(); + fc::raw::pack(out, current_view); + } + + void pbft_controller::maybe_pbft_prepare() { + if (!pbft_db.should_send_pbft_msg()) return; + state_machine.send_prepare(); + } + + void pbft_controller::maybe_pbft_commit() { + if (!pbft_db.should_send_pbft_msg()) return; + state_machine.send_commit(); + } + + void pbft_controller::maybe_pbft_view_change() { + if (!pbft_db.should_send_pbft_msg()) return; + if (state_machine.get_view_change_timer() <= config.view_change_timeout) { + if (!state_machine.get_view_changes_cache().empty()) { + pbft_db.send_and_add_pbft_view_change(state_machine.get_view_changes_cache()); + } + state_machine.set_view_change_timer(state_machine.get_view_change_timer() + 1); + } else { + state_machine.set_view_change_timer(0); + state_machine.send_view_change(); + } + } + + void pbft_controller::on_pbft_prepare(pbft_prepare &p) { + if (!config.bp_candidate) return; + state_machine.on_prepare(p); + } + + void pbft_controller::on_pbft_commit(pbft_commit &c) { + if (!config.bp_candidate) return; + state_machine.on_commit(c); + } + + void pbft_controller::on_pbft_view_change(pbft_view_change &vc) { + if (!config.bp_candidate) return; + state_machine.on_view_change(vc); + } + + void pbft_controller::on_pbft_new_view(pbft_new_view &nv) { + if (!config.bp_candidate) return; + state_machine.on_new_view(nv); + } + + void pbft_controller::send_pbft_checkpoint() { + if (!pbft_db.should_send_pbft_msg()) return; + pbft_db.send_pbft_checkpoint(); + } + + void pbft_controller::on_pbft_checkpoint(pbft_checkpoint &cp) { + pbft_db.add_pbft_checkpoint(cp); + } + + psm_state::psm_state() = default; + + psm_state::~psm_state() = default; + + + psm_machine::psm_machine(pbft_database &pbft_db) : pbft_db(pbft_db) { + this->set_current(new psm_committed_state); + + this->set_prepares_cache(vector{}); + this->set_commits_cache(vector{}); + this->set_view_changes_cache(vector{}); + + this->set_prepared_certificate(vector{}); + this->set_view_changed_certificate(vector{}); + + this->view_change_timer = 0; + this->target_view_retries = 0; + this->current_view = 0; + this->target_view = this->current_view + 1; + } + + psm_machine::~psm_machine() = default; + + void psm_machine::on_prepare(pbft_prepare &e) { + current->on_prepare(this, e, pbft_db); + } + + void psm_machine::send_prepare() { + current->send_prepare(this, pbft_db); + } + + void psm_machine::on_commit(pbft_commit &e) { + current->on_commit(this, e, pbft_db); + } + + void psm_machine::send_commit() { + current->send_commit(this, pbft_db); + } + + void psm_machine::on_view_change(pbft_view_change &e) { + current->on_view_change(this, e, pbft_db); + } + + void psm_machine::send_view_change() { + current->send_view_change(this, pbft_db); + } + + void psm_machine::on_new_view(pbft_new_view &e) { + current->on_new_view(this, e, pbft_db); + } + + void psm_machine::manually_set_current_view(const uint32_t ¤t_view) { + current->manually_set_view(this, current_view); + } + + /** + * psm_prepared_state + */ + + psm_prepared_state::psm_prepared_state() { + pending_commit_local = false; + } + + psm_prepared_state::~psm_prepared_state() = default; + + void psm_prepared_state::on_prepare(psm_machine *m, pbft_prepare &e, pbft_database &pbft_db) { + //ignore + } + + void psm_prepared_state::send_prepare(psm_machine *m, pbft_database &pbft_db) { + //retry + if (m->get_prepares_cache().empty()) return; + + pbft_db.send_and_add_pbft_prepare(m->get_prepares_cache(), m->get_current_view()); + } + + void psm_prepared_state::on_commit(psm_machine *m, pbft_commit &e, pbft_database &pbft_db) { + + if (e.view < m->get_current_view()) return; + + pbft_db.add_pbft_commit(e); + + //`pending_commit_local` is used to mark committed local status in psm machine; + //`pbft_db.pending_pbft_lib()` is used to mark commit local status in controller; + // following logic is implemented to resolve async problem during lib committing; + + if (pbft_db.should_committed() && !pending_commit_local) { + pbft_db.commit_local(); + pending_commit_local = true; + } + + if (pending_commit_local && !pbft_db.pending_pbft_lib()) { + pbft_db.send_pbft_checkpoint(); + m->transit_to_committed_state(this); + } + } + + + void psm_prepared_state::send_commit(psm_machine *m, pbft_database &pbft_db) { + auto commits = pbft_db.send_and_add_pbft_commit(m->get_commits_cache(), m->get_current_view()); + + if (!commits.empty()) { + m->set_commits_cache(commits); + } + + if (pbft_db.should_committed() && !pending_commit_local) { + pbft_db.commit_local(); + pending_commit_local = true; + } + + if (pending_commit_local && !pbft_db.pending_pbft_lib()) { + pbft_db.send_pbft_checkpoint(); + m->transit_to_committed_state(this); + } + } + + void psm_prepared_state::on_view_change(psm_machine *m, pbft_view_change &e, pbft_database &pbft_db) { + + if (e.target_view <= m->get_current_view()) return; + + pbft_db.add_pbft_view_change(e); + + //if received >= f+1 view_change on some view, transit to view_change and send view change + auto target_view = pbft_db.should_view_change(); + if (target_view > 0 && target_view > m->get_current_view()) { + m->set_target_view(target_view); + m->transit_to_view_change_state(this); + } + } + + void psm_prepared_state::send_view_change(psm_machine *m, pbft_database &pbft_db) { + m->transit_to_view_change_state(this); + } + + void psm_prepared_state::on_new_view(psm_machine *m, pbft_new_view &e, pbft_database &pbft_db) { + + if (e.view <= m->get_current_view()) return; + + if (pbft_db.is_valid_new_view(e)) m->transit_to_new_view(e, this); + } + + void psm_prepared_state::manually_set_view(psm_machine *m, const uint32_t ¤t_view) { + m->set_current_view(current_view); + m->set_target_view(current_view+1); + m->transit_to_view_change_state(this); + } + + psm_committed_state::psm_committed_state() { + pending_commit_local = false; + } + + psm_committed_state::~psm_committed_state() = default; + + /** + * psm_committed_state + */ + void psm_committed_state::on_prepare(psm_machine *m, pbft_prepare &e, pbft_database &pbft_db) { + //validate + if (e.view < m->get_current_view()) return; + + //do action add prepare + pbft_db.add_pbft_prepare(e); + + //if prepare >= 2f+1, transit to prepared + if (pbft_db.should_prepared()) m->transit_to_prepared_state(this); + } + + void psm_committed_state::send_prepare(psm_machine *m, pbft_database &pbft_db) { + auto prepares = pbft_db.send_and_add_pbft_prepare(m->get_prepares_cache(), m->get_current_view()); + + if (!prepares.empty()) { + m->set_prepares_cache(prepares); + } + + //if prepare >= 2f+1, transit to prepared + if (pbft_db.should_prepared()) m->transit_to_prepared_state(this); + } + + void psm_committed_state::on_commit(psm_machine *m, pbft_commit &e, pbft_database &pbft_db) { + + if (e.view < m->get_current_view()) return; + + pbft_db.add_pbft_commit(e); + } + + void psm_committed_state::send_commit(psm_machine *m, pbft_database &pbft_db) { + + if (m->get_commits_cache().empty()) return; + pbft_db.send_and_add_pbft_commit(m->get_commits_cache(), m->get_current_view()); + + } + + void psm_committed_state::on_view_change(psm_machine *m, pbft_view_change &e, pbft_database &pbft_db) { + + if (e.target_view <= m->get_current_view()) return; + + pbft_db.add_pbft_view_change(e); + + //if received >= f+1 view_change on some view, transit to view_change and send view change + auto new_view = pbft_db.should_view_change(); + if (new_view > 0 && new_view > m->get_current_view()) { + m->set_target_view(new_view); + m->transit_to_view_change_state(this); + } + } + + void psm_committed_state::send_view_change(psm_machine *m, pbft_database &pbft_db) { + m->transit_to_view_change_state(this); + } + + void psm_committed_state::on_new_view(psm_machine *m, pbft_new_view &e, pbft_database &pbft_db) { + + if (e.view <= m->get_current_view()) return; + + if (pbft_db.is_valid_new_view(e)) m->transit_to_new_view(e, this); + } + + void psm_committed_state::manually_set_view(psm_machine *m, const uint32_t ¤t_view) { + m->set_current_view(current_view); + m->set_target_view(current_view+1); + m->transit_to_view_change_state(this); + } + + /** + * psm_view_change_state + */ + void psm_view_change_state::on_prepare(psm_machine *m, pbft_prepare &e, pbft_database &pbft_db) { + //ignore; + } + + void psm_view_change_state::send_prepare(psm_machine *m, pbft_database &pbft_db) { + //ignore; + } + + void psm_view_change_state::on_commit(psm_machine *m, pbft_commit &e, pbft_database &pbft_db) { + //ignore; + } + + void psm_view_change_state::send_commit(psm_machine *m, pbft_database &pbft_db) { + //ignore; + } + + void psm_view_change_state::on_view_change(psm_machine *m, pbft_view_change &e, pbft_database &pbft_db) { + + //skip from view change state if my lib is higher than my view change state height. + auto vc = m->get_view_changes_cache(); + if (!vc.empty() && pbft_db.should_stop_view_change(vc.front())) { + m->transit_to_committed_state(this); + return; + } + + if (e.target_view <= m->get_current_view()) return; + + pbft_db.add_pbft_view_change(e); + + //if view_change >= 2f+1, calculate next primary, send new view if is primary + auto nv = m->get_target_view(); + if (pbft_db.should_new_view(nv) && pbft_db.is_new_primary(nv)) { + + m->set_view_changed_certificate(pbft_db.generate_view_changed_certificate(nv)); + + auto new_view = pbft_db.get_proposed_new_view_num(); + if (new_view != nv) return; + + auto nv_msg = pbft_db.send_pbft_new_view( + m->get_view_changed_certificate(), + new_view); + + if (nv_msg == pbft_new_view{} || !pbft_db.is_valid_new_view(nv_msg)) return; + + m->transit_to_new_view(nv_msg, this); + return; + } + } + + void psm_view_change_state::send_view_change(psm_machine *m, pbft_database &pbft_db) { + + //skip from view change state if my lib is higher than my view change state height. + auto vc = m->get_view_changes_cache(); + if (!vc.empty() && pbft_db.should_stop_view_change(vc.front())) { + m->transit_to_committed_state(this); + return; + } + + m->send_pbft_view_change(); + + //if view_change >= 2f+1, calculate next primary, send new view if is primary + auto nv = m->get_target_view(); + if (pbft_db.should_new_view(nv) && pbft_db.is_new_primary(nv)) { + + m->set_view_changed_certificate(pbft_db.generate_view_changed_certificate(nv)); + + auto new_view = pbft_db.get_proposed_new_view_num(); + if (new_view != nv) return; + + auto nv_msg = pbft_db.send_pbft_new_view( + m->get_view_changed_certificate(), + new_view); + + if (nv_msg == pbft_new_view{} || !pbft_db.is_valid_new_view(nv_msg)) return; + + m->transit_to_new_view(nv_msg, this); + return; + } + } + + + void psm_view_change_state::on_new_view(psm_machine *m, pbft_new_view &e, pbft_database &pbft_db) { + + if (e.view <= m->get_current_view()) return; + + if (pbft_db.is_valid_new_view(e)) m->transit_to_new_view(e, this); + } + + void psm_view_change_state::manually_set_view(psm_machine *m, const uint32_t ¤t_view) { + m->set_current_view(current_view); + m->set_target_view(current_view+1); + m->transit_to_view_change_state(this); + } + + template + void psm_machine::transit_to_committed_state(T const & s) { + + auto nv = pbft_db.get_committed_view(); + if (nv > this->get_current_view()) this->set_current_view(nv); + this->set_target_view(this->get_current_view() + 1); + + auto prepares = this->pbft_db.send_and_add_pbft_prepare(vector{}, this->get_current_view()); + set_prepares_cache(prepares); + + this->set_view_changes_cache(vector{}); + this->set_view_change_timer(0); + + this->set_current(new psm_committed_state); + delete s; + } + + template + void psm_machine::transit_to_prepared_state(T const & s) { + + auto commits = this->pbft_db.send_and_add_pbft_commit(vector{}, this->get_current_view()); + set_commits_cache(commits); + + this->set_view_changes_cache(vector{}); + + this->set_current(new psm_prepared_state); + delete s; + } + + template + void psm_machine::transit_to_view_change_state(T const &s) { + + this->set_commits_cache(vector{}); + this->set_prepares_cache(vector{}); + + this->set_view_change_timer(0); + this->set_target_view_retries(0); + + this->set_current(new psm_view_change_state); + if (pbft_db.should_send_pbft_msg()) this->send_pbft_view_change(); + + delete s; + } + + template + void psm_machine::transit_to_new_view(const pbft_new_view &new_view, T const &s) { + + this->set_current_view(new_view.view); + this->set_target_view(new_view.view + 1); + + this->set_prepares_cache(vector{}); + + this->set_view_change_timer(0); + this->set_target_view_retries(0); + + this->pbft_db.prune_pbft_index(); + + if (!(new_view.stable_checkpoint == pbft_stable_checkpoint{})) { + for (auto cp :new_view.stable_checkpoint.checkpoints) { + try { + pbft_db.add_pbft_checkpoint(cp); + } catch (...) { + wlog("insert checkpoint failed"); + } + } + } + + if (!new_view.prepared.prepares.empty()) { + for (auto p: new_view.prepared.prepares) { + try { + pbft_db.add_pbft_prepare(p); + } catch (...) { + wlog("insert prepare failed"); + } + } + if (pbft_db.should_prepared()) { + transit_to_prepared_state(s); + return; + } + } + + this->set_current(new psm_committed_state); + delete s; + } + + void psm_machine::send_pbft_view_change() { + + if (this->get_target_view_retries() == 0) { + this->set_view_changes_cache(vector{}); + this->set_prepared_certificate(pbft_db.generate_prepared_certificate()); + } + + EOS_ASSERT((this->get_target_view() > this->get_current_view()), pbft_exception, + "target view should be always greater than current view"); + + if (this->get_target_view_retries() < pow(2, this->get_target_view() - this->get_current_view() - 1)) { + this->set_target_view_retries(this->get_target_view_retries() + 1); + } else { + this->set_target_view_retries(0); + this->set_target_view(this->get_target_view() + 1); + this->set_view_changes_cache(vector{}); + } + + auto view_changes = pbft_db.send_and_add_pbft_view_change( + this->get_view_changes_cache(), + this->get_prepared_certificate(), + this->get_current_view(), + this->get_target_view()); + + if (!view_changes.empty()) { + this->set_view_changes_cache(view_changes); + } + } + + const vector &psm_machine::get_prepares_cache() const { + return this->cache.prepares_cache; + } + + void psm_machine::set_prepares_cache(const vector &prepares_cache) { + this->cache.prepares_cache = prepares_cache; + } + + const vector &psm_machine::get_commits_cache() const { + return this->cache.commits_cache; + } + + void psm_machine::set_commits_cache(const vector &commits_cache) { + this->cache.commits_cache = commits_cache; + } + + const vector &psm_machine::get_view_changes_cache() const { + return this->cache.view_changes_cache; + } + + void psm_machine::set_view_changes_cache(const vector &view_changes_cache) { + this->cache.view_changes_cache = view_changes_cache; + } + + const uint32_t &psm_machine::get_current_view() const { + return this->current_view; + } + + void psm_machine::set_current_view(const uint32_t ¤t_view) { + this->current_view = current_view; + } + + const vector &psm_machine::get_prepared_certificate() const { + return this->cache.prepared_certificate; + } + + void psm_machine::set_prepared_certificate(const vector &prepared_certificate) { + this->cache.prepared_certificate = prepared_certificate; + } + + const vector &psm_machine::get_view_changed_certificate() const { + return this->cache.view_changed_certificate; + } + + void psm_machine::set_view_changed_certificate( + const vector &view_changed_certificate) { + this->cache.view_changed_certificate = view_changed_certificate; + } + + const uint32_t &psm_machine::get_target_view_retries() const { + return this->target_view_retries; + } + + void psm_machine::set_target_view_retries(const uint32_t &target_view_reties) { + this->target_view_retries = target_view_reties; + } + + const uint32_t &psm_machine::get_target_view() const { + return this->target_view; + } + + void psm_machine::set_target_view(const uint32_t &target_view) { + this->target_view = target_view; + } + + const uint32_t &psm_machine::get_view_change_timer() const { + return this->view_change_timer; + } + + void psm_machine::set_view_change_timer(const uint32_t &view_change_timer) { + this->view_change_timer = view_change_timer; + } + } +} \ No newline at end of file diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp new file mode 100644 index 00000000000..85cfe28f8b6 --- /dev/null +++ b/libraries/chain/pbft_database.cpp @@ -0,0 +1,1223 @@ +#include +#include +#include +#include + +namespace eosio { + namespace chain { + + pbft_database::pbft_database(controller &ctrl) : + ctrl(ctrl), + view_state_index(pbft_view_state_multi_index_type{}) { + checkpoint_index = pbft_checkpoint_state_multi_index_type{}; + pbft_db_dir = ctrl.state_dir(); + checkpoints_dir = ctrl.blocks_dir(); + + if (!fc::is_directory(pbft_db_dir)) fc::create_directories(pbft_db_dir); + + auto pbft_db_dat = pbft_db_dir / config::pbftdb_filename; + if (fc::exists(pbft_db_dat)) { + string content; + fc::read_file_contents(pbft_db_dat, content); + + fc::datastream ds(content.data(), content.size()); + + // keep these unused variables. + uint32_t current_view; + fc::raw::unpack(ds, current_view); + + unsigned_int size; + fc::raw::unpack(ds, size); + for (uint32_t i = 0, n = size.value; i < n; ++i) { + pbft_state s; + fc::raw::unpack(ds, s); + set(std::make_shared(move(s))); + } + ilog("pbft index size: ${s}", ("s", pbft_state_index.size())); + } else { + pbft_state_index = pbft_state_multi_index_type{}; + } + + if (!fc::is_directory(checkpoints_dir)) fc::create_directories(checkpoints_dir); + + auto checkpoints_db = checkpoints_dir / config::checkpoints_filename; + if (fc::exists(checkpoints_db)) { + string content; + fc::read_file_contents(checkpoints_db, content); + + fc::datastream ds(content.data(), content.size()); + + unsigned_int checkpoint_size; + fc::raw::unpack(ds, checkpoint_size); + for (uint32_t j = 0, m = checkpoint_size.value; j < m; ++j) { + pbft_checkpoint_state cs; + fc::raw::unpack(ds, cs); + set(std::make_shared(move(cs))); + } + ilog("checkpoint index size: ${cs}", ("cs", checkpoint_index.size())); + } else { + checkpoint_index = pbft_checkpoint_state_multi_index_type{}; + } + } + + void pbft_database::close() { + + + fc::path checkpoints_db = checkpoints_dir / config::checkpoints_filename; + std::ofstream c_out(checkpoints_db.generic_string().c_str(), + std::ios::out | std::ios::binary | std::ofstream::trunc); + + uint32_t num_records_in_checkpoint_db = checkpoint_index.size(); + fc::raw::pack(c_out, unsigned_int{num_records_in_checkpoint_db}); + + if (!checkpoint_index.empty()) { + for (const auto &s: checkpoint_index) { + fc::raw::pack(c_out, *s); + } + } + + fc::path pbft_db_dat = pbft_db_dir / config::pbftdb_filename; + std::ofstream out(pbft_db_dat.generic_string().c_str(), + std::ios::out | std::ios::binary | std::ofstream::app); + uint32_t num_records_in_db = pbft_state_index.size(); + fc::raw::pack(out, unsigned_int{num_records_in_db}); + + if (!pbft_state_index.empty()) { + for (const auto &s : pbft_state_index) { + fc::raw::pack(out, *s); + } + } + pbft_state_index.clear(); + checkpoint_index.clear(); + } + + pbft_database::~pbft_database() { + close(); + } + + + void pbft_database::add_pbft_prepare(pbft_prepare &p) { + + if (!is_valid_prepare(p)) return; + + auto &by_block_id_index = pbft_state_index.get(); + + auto current = ctrl.fetch_block_state_by_id(p.block_id); + + while ((current) && (current->block_num > ctrl.last_irreversible_block_num())) { + auto curr_itr = by_block_id_index.find(current->id); + + if (curr_itr == by_block_id_index.end()) { + try { + auto curr_ps = pbft_state{current->id, current->block_num, {p}}; + auto curr_psp = make_shared(curr_ps); + pbft_state_index.insert(curr_psp); + } catch (...) { + EOS_ASSERT(false, pbft_exception, "prepare insert failure: ${p}", ("p", p)); + } + } else { + auto prepares = (*curr_itr)->prepares; + auto p_itr = find_if(prepares.begin(), prepares.end(), + [&](const pbft_prepare &prep) { + return prep.public_key == p.public_key && prep.view == p.view; + }); + if (p_itr == prepares.end()) { + by_block_id_index.modify(curr_itr, [&](const pbft_state_ptr &psp) { + psp->prepares.emplace_back(p); + std::sort(psp->prepares.begin(), psp->prepares.end(), less<>()); + }); + } + } + curr_itr = by_block_id_index.find(current->id); + if (curr_itr == by_block_id_index.end()) return; + + auto prepares = (*curr_itr)->prepares; + auto as = current->active_schedule.producers; + flat_map prepare_count; + for (const auto &pre: prepares) { + if (prepare_count.find(pre.view) == prepare_count.end()) prepare_count[pre.view] = 0; + } + + if (!(*curr_itr)->should_prepared) { + for (auto const &sp: as) { + for (auto const &pp: prepares) { + if (sp.block_signing_key == pp.public_key) prepare_count[pp.view] += 1; + } + } + for (auto const &e: prepare_count) { + if (e.second >= as.size() * 2 / 3 + 1) { + by_block_id_index.modify(curr_itr, + [&](const pbft_state_ptr &psp) { psp->should_prepared = true; }); + } + } + } + current = ctrl.fetch_block_state_by_id(current->prev()); + } + } + + + vector pbft_database::send_and_add_pbft_prepare(const vector &pv, uint32_t current_view) { + + auto head_block_num = ctrl.head_block_num(); + if (head_block_num <= 1) return vector{}; + auto my_prepare = ctrl.get_pbft_my_prepare(); + + auto reserve_prepare = [&](const block_id_type &in) { + if (in == block_id_type{} || !ctrl.fetch_block_state_by_id(in)) return false; + auto lib = ctrl.last_irreversible_block_id(); + if (lib == block_id_type{}) return true; + auto forks = ctrl.fork_db().fetch_branch_from(in, lib); + return !forks.first.empty() && forks.second.empty(); + }; + + vector new_pv; + if (!pv.empty()) { + for (auto p : pv) { + //change uuid, sign again, update cache, then emit + auto uuid = boost::uuids::to_string(uuid_generator()); + p.uuid = uuid; + p.timestamp = time_point::now(); + p.producer_signature = ctrl.my_signature_providers()[p.public_key](p.digest()); + emit(pbft_outgoing_prepare, p); + } + return vector{}; + } else if (reserve_prepare(my_prepare)) { + for (auto const &sp : ctrl.my_signature_providers()) { + auto uuid = boost::uuids::to_string(uuid_generator()); + auto my_prepare_num = ctrl.fetch_block_state_by_id(my_prepare)->block_num; + auto p = pbft_prepare{uuid, current_view, my_prepare_num, my_prepare, sp.first, chain_id()}; + p.producer_signature = sp.second(p.digest()); + emit(pbft_outgoing_prepare, p); + new_pv.emplace_back(p); + } + return new_pv; + } else { + uint32_t high_water_mark_block_num = head_block_num; + auto next_proposed_schedule_block_num = ctrl.get_global_properties().proposed_schedule_block_num; + auto promoted_proposed_schedule_block_num = ctrl.last_promoted_proposed_schedule_block_num(); + auto lib = ctrl.last_irreversible_block_num(); + + if (next_proposed_schedule_block_num && *next_proposed_schedule_block_num > lib) { + high_water_mark_block_num = std::min(head_block_num, *next_proposed_schedule_block_num); + } + + if (promoted_proposed_schedule_block_num && promoted_proposed_schedule_block_num > lib) { + high_water_mark_block_num = std::min(high_water_mark_block_num, + promoted_proposed_schedule_block_num); + } + + if (high_water_mark_block_num <= lib) return vector{}; + block_id_type high_water_mark_block_id = ctrl.get_block_id_for_num(high_water_mark_block_num); + for (auto const &sp : ctrl.my_signature_providers()) { + auto uuid = boost::uuids::to_string(uuid_generator()); + auto p = pbft_prepare{uuid, current_view, high_water_mark_block_num, high_water_mark_block_id, + sp.first, chain_id()}; + p.producer_signature = sp.second(p.digest()); + add_pbft_prepare(p); + emit(pbft_outgoing_prepare, p); + new_pv.emplace_back(p); + ctrl.set_pbft_my_prepare(high_water_mark_block_id); + } + return new_pv; + } + } + + bool pbft_database::should_prepared() { + + const auto &by_prepare_and_num_index = pbft_state_index.get(); + auto itr = by_prepare_and_num_index.begin(); + if (itr == by_prepare_and_num_index.end()) return false; + + pbft_state_ptr psp = *itr; + + if (psp->should_prepared && (psp->block_num > ctrl.last_irreversible_block_num())) { + ctrl.set_pbft_prepared((*itr)->block_id); + return true; + } + return false; + } + + bool pbft_database::is_valid_prepare(const pbft_prepare &p) { + if (p.chain_id != chain_id()) return false; + // a prepare msg under lscb (which is no longer in fork_db), can be treated as null, thus true. + if (p.block_num <= ctrl.last_stable_checkpoint_block_num()) return true; + if (!p.is_signature_valid()) return false; + return should_recv_pbft_msg(p.public_key); + } + + void pbft_database::add_pbft_commit(pbft_commit &c) { + + if (!is_valid_commit(c)) return; + auto &by_block_id_index = pbft_state_index.get(); + + auto current = ctrl.fetch_block_state_by_id(c.block_id); + + while ((current) && (current->block_num > ctrl.last_irreversible_block_num())) { + + auto curr_itr = by_block_id_index.find(current->id); + + if (curr_itr == by_block_id_index.end()) { + try { + auto curr_ps = pbft_state{current->id, current->block_num, .commits={c}}; + auto curr_psp = make_shared(curr_ps); + pbft_state_index.insert(curr_psp); + } catch (...) { + EOS_ASSERT(false, pbft_exception, "commit insert failure: ${c}", ("c", c)); + } + } else { + auto commits = (*curr_itr)->commits; + auto p_itr = find_if(commits.begin(), commits.end(), + [&](const pbft_commit &comm) { + return comm.public_key == c.public_key && comm.view == c.view; + }); + if (p_itr == commits.end()) { + by_block_id_index.modify(curr_itr, [&](const pbft_state_ptr &psp) { + psp->commits.emplace_back(c); + std::sort(psp->commits.begin(), psp->commits.end(), less<>()); + }); + } + } + + curr_itr = by_block_id_index.find(current->id); + if (curr_itr == by_block_id_index.end()) return; + + auto commits = (*curr_itr)->commits; + + auto as = current->active_schedule; + flat_map commit_count; + for (const auto &com: commits) { + if (commit_count.find(com.view) == commit_count.end()) commit_count[com.view] = 0; + } + + if (!(*curr_itr)->should_committed) { + + for (auto const &sp: as.producers) { + for (auto const &pc: commits) { + if (sp.block_signing_key == pc.public_key) commit_count[pc.view] += 1; + } + } + + for (auto const &e: commit_count) { + if (e.second >= current->active_schedule.producers.size() * 2 / 3 + 1) { + by_block_id_index.modify(curr_itr, + [&](const pbft_state_ptr &psp) { psp->should_committed = true; }); + } + } + } + current = ctrl.fetch_block_state_by_id(current->prev()); + } + } + + vector pbft_database::send_and_add_pbft_commit(const vector &cv, uint32_t current_view) { + if (!cv.empty()) { + for (auto c : cv) { + //change uuid, sign again, update cache, then emit + auto uuid = boost::uuids::to_string(uuid_generator()); + c.uuid = uuid; + c.timestamp = time_point::now(); + c.producer_signature = ctrl.my_signature_providers()[c.public_key](c.digest()); + emit(pbft_outgoing_commit, c); + } + return vector{}; + } else { + const auto &by_prepare_and_num_index = pbft_state_index.get(); + auto itr = by_prepare_and_num_index.begin(); + if (itr == by_prepare_and_num_index.end()) { + return vector{}; + } + vector new_cv; + pbft_state_ptr psp = *itr; + auto bs = ctrl.fork_db().get_block(psp->block_id); + + if (psp->should_prepared && (psp->block_num > ctrl.last_irreversible_block_num())) { + + for (auto const &sp : ctrl.my_signature_providers()) { + auto uuid = boost::uuids::to_string(uuid_generator()); + auto c = pbft_commit{uuid, current_view, psp->block_num, psp->block_id, sp.first, chain_id()}; + c.producer_signature = sp.second(c.digest()); + add_pbft_commit(c); + emit(pbft_outgoing_commit, c); + new_cv.emplace_back(c); + } + } + return new_cv; + } + } + + bool pbft_database::should_committed() { + const auto &by_commit_and_num_index = pbft_state_index.get(); + auto itr = by_commit_and_num_index.begin(); + if (itr == by_commit_and_num_index.end()) return false; + pbft_state_ptr psp = *itr; + + return (psp->should_committed && (psp->block_num > ctrl.last_irreversible_block_num())); + } + + uint32_t pbft_database::get_committed_view() { + uint32_t new_view = 0; + if (!should_committed()) return new_view; + + const auto &by_commit_and_num_index = pbft_state_index.get(); + auto itr = by_commit_and_num_index.begin(); + pbft_state_ptr psp = *itr; + + auto blk_state = ctrl.fetch_block_state_by_id((*itr)->block_id); + if (!blk_state) return new_view; + auto as = blk_state->active_schedule.producers; + + auto commits = (*itr)->commits; + + flat_map commit_count; + for (const auto &com: commits) { + if (commit_count.find(com.view) == commit_count.end()) { + commit_count[com.view] = 1; + } else { + commit_count[com.view] += 1; + } + } + + for (auto const &e: commit_count) { + if (e.second >= as.size() * 2 / 3 + 1 && e.first > new_view) { + new_view = e.first; + } + } + return new_view; + } + + bool pbft_database::is_valid_commit(const pbft_commit &c) { + if (c.chain_id != chain_id()) return false; + if (c.block_num <= ctrl.last_stable_checkpoint_block_num()) return true; + if (!c.is_signature_valid()) return false; + return should_recv_pbft_msg(c.public_key); + } + + void pbft_database::commit_local() { + const auto &by_commit_and_num_index = pbft_state_index.get(); + auto itr = by_commit_and_num_index.begin(); + if (itr == by_commit_and_num_index.end()) return; + + pbft_state_ptr psp = *itr; + + ctrl.pbft_commit_local(psp->block_id); + } + + bool pbft_database::pending_pbft_lib() { + return ctrl.pending_pbft_lib(); + } + + void pbft_database::add_pbft_view_change(pbft_view_change &vc) { + if (!is_valid_view_change(vc)) return; + auto active_bps = lib_active_producers().producers; + + auto &by_view_index = view_state_index.get(); + auto itr = by_view_index.find(vc.target_view); + if (itr == by_view_index.end()) { + auto vs = pbft_view_state{vc.target_view, .view_changes={vc}}; + auto vsp = make_shared(vs); + view_state_index.insert(vsp); + } else { + auto pvs = (*itr); + auto view_changes = pvs->view_changes; + auto p_itr = find_if(view_changes.begin(), view_changes.end(), + [&](const pbft_view_change &existed) { + return existed.public_key == vc.public_key; + }); + if (p_itr == view_changes.end()) { + by_view_index.modify(itr, [&](const pbft_view_state_ptr &pvsp) { + pvsp->view_changes.emplace_back(vc); + }); + } + } + + itr = by_view_index.find(vc.target_view); + if (itr == by_view_index.end()) return; + + auto vc_count = 0; + if (!(*itr)->should_view_changed) { + for (auto const &sp: active_bps) { + for (auto const &v: (*itr)->view_changes) { + if (sp.block_signing_key == v.public_key) vc_count += 1; + } + } + if (vc_count >= active_bps.size() * 2 / 3 + 1) { + by_view_index.modify(itr, [&](const pbft_view_state_ptr &pvsp) { pvsp->should_view_changed = true; }); + } + } + } + + uint32_t pbft_database::should_view_change() { + uint32_t nv = 0; + auto &by_view_index = view_state_index.get(); + auto itr = by_view_index.begin(); + if (itr == by_view_index.end()) return nv; + + while (itr != by_view_index.end()) { + auto active_bps = lib_active_producers().producers; + auto vc_count = 0; + auto pvs = (*itr); + + for (auto const &bp: active_bps) { + for (auto const &pp: pvs->view_changes) { + if (bp.block_signing_key == pp.public_key) vc_count += 1; + } + } + //if contains self or view_change >= f+1, transit to view_change and send view change + if (vc_count >= active_bps.size() / 3 + 1) { + nv = pvs->view; + break; + } + ++itr; + } + return nv; + } + + vector pbft_database::send_and_add_pbft_view_change( + const vector &vcv, + const vector &ppc, + uint32_t current_view, + uint32_t new_view) { + if (!vcv.empty()) { + for (auto vc : vcv) { + //change uuid, sign again, update cache, then emit + auto uuid = boost::uuids::to_string(uuid_generator()); + vc.uuid = uuid; + vc.timestamp = time_point::now(); + vc.producer_signature = ctrl.my_signature_providers()[vc.public_key](vc.digest()); + emit(pbft_outgoing_view_change, vc); + } + return vector{}; + } else { + vector new_vcv; + + for (auto const &my_sp : ctrl.my_signature_providers()) { + auto ppc_ptr = find_if(ppc.begin(), ppc.end(), + [&](const pbft_prepared_certificate &v) { + return v.public_key == my_sp.first; + }); + + auto my_ppc = pbft_prepared_certificate{}; + if (ppc_ptr != ppc.end()) my_ppc = *ppc_ptr; + auto my_lsc = get_stable_checkpoint_by_id(ctrl.last_stable_checkpoint_block_id()); + auto uuid = boost::uuids::to_string(uuid_generator()); + auto vc = pbft_view_change{uuid, current_view, new_view, my_ppc, my_lsc, my_sp.first, chain_id()}; + vc.producer_signature = my_sp.second(vc.digest()); + emit(pbft_outgoing_view_change, vc); + add_pbft_view_change(vc); + new_vcv.emplace_back(vc); + } + return new_vcv; + } + } + + bool pbft_database::should_new_view(const uint32_t target_view) { + auto &by_view_index = view_state_index.get(); + auto itr = by_view_index.find(target_view); + if (itr == by_view_index.end()) return false; + return (*itr)->should_view_changed; + } + + uint32_t pbft_database::get_proposed_new_view_num() { + auto &by_count_and_view_index = view_state_index.get(); + auto itr = by_count_and_view_index.begin(); + if (itr == by_count_and_view_index.end() || !(*itr)->should_view_changed) return 0; + return (*itr)->view; + } + + bool pbft_database::is_new_primary(const uint32_t target_view) { + + auto primary_key = get_new_view_primary_key(target_view); + if (primary_key == public_key_type{}) return false; + auto sps = ctrl.my_signature_providers(); + auto sp_itr = sps.find(primary_key); + return sp_itr != sps.end(); + } + + void pbft_database::prune_pbft_index() { + pbft_state_index.clear(); + view_state_index.clear(); + ctrl.reset_pbft_my_prepare(); + } + + pbft_new_view pbft_database::send_pbft_new_view( + const vector &vcc, + uint32_t current_view) { + + auto primary_key = get_new_view_primary_key(current_view); + if (!is_new_primary(current_view)) return pbft_new_view{}; + + //`sp_itr` is not possible to be the end iterator, since it's already been checked in `is_new_primary`. + auto my_sps = ctrl.my_signature_providers(); + auto sp_itr = my_sps.find(primary_key); + + auto vcc_ptr = find_if(vcc.begin(), vcc.end(), + [&](const pbft_view_changed_certificate &v) { return v.public_key == primary_key; }); + + if (vcc_ptr == vcc.end()) return pbft_new_view{}; + + auto highest_ppc = pbft_prepared_certificate{}; + auto highest_sc = pbft_stable_checkpoint{}; + + for (const auto &vc: vcc_ptr->view_changes) { + if (vc.prepared.block_num > highest_ppc.block_num && is_valid_prepared_certificate(vc.prepared)) { + highest_ppc = vc.prepared; + } + + if (vc.stable_checkpoint.block_num > highest_sc.block_num && + is_valid_stable_checkpoint(vc.stable_checkpoint)) { + highest_sc = vc.stable_checkpoint; + } + } + + auto uuid = boost::uuids::to_string(uuid_generator()); + auto nv = pbft_new_view{uuid, current_view, highest_ppc, highest_sc, *vcc_ptr, sp_itr->first, chain_id()}; + nv.producer_signature = sp_itr->second(nv.digest()); + emit(pbft_outgoing_new_view, nv); + return nv; + } + + vector pbft_database::generate_prepared_certificate() { + auto ppc = vector{}; + const auto &by_prepare_and_num_index = pbft_state_index.get(); + auto itr = by_prepare_and_num_index.begin(); + if (itr == by_prepare_and_num_index.end()) return vector{}; + pbft_state_ptr psp = *itr; + + auto prepared_block_state = ctrl.fetch_block_state_by_id(psp->block_id); + if (!prepared_block_state) return vector{}; + + auto as = prepared_block_state->active_schedule.producers; + if (psp->should_prepared && (psp->block_num > (ctrl.last_irreversible_block_num()))) { + for (auto const &my_sp : ctrl.my_signature_providers()) { + auto prepares = psp->prepares; + auto valid_prepares = vector{}; + + flat_map prepare_count; + flat_map> prepare_msg; + + for (const auto &pre: prepares) { + if (prepare_count.find(pre.view) == prepare_count.end()) prepare_count[pre.view] = 0; + prepare_msg[pre.view].push_back(pre); + } + + for (auto const &sp: as) { + for (auto const &pp: prepares) { + if (sp.block_signing_key == pp.public_key) prepare_count[pp.view] += 1; + } + } + + for (auto const &e: prepare_count) { + if (e.second >= as.size() * 2 / 3 + 1) { + valid_prepares = prepare_msg[e.first]; + } + } + + if (valid_prepares.empty()) return vector{}; + + auto pc = pbft_prepared_certificate{psp->block_id, psp->block_num, valid_prepares, my_sp.first}; + pc.producer_signature = my_sp.second(pc.digest()); + ppc.emplace_back(pc); + } + return ppc; + } else return vector{}; + } + + vector pbft_database::generate_view_changed_certificate(uint32_t target_view) { + auto vcc = vector{}; + + auto &by_view_index = view_state_index.get(); + auto itr = by_view_index.find(target_view); + if (itr == by_view_index.end()) return vcc; + + auto pvs = *itr; + + if (pvs->should_view_changed) { + for (auto const &my_sp : ctrl.my_signature_providers()) { + auto pc = pbft_view_changed_certificate{pvs->view, pvs->view_changes, my_sp.first}; + pc.producer_signature = my_sp.second(pc.digest()); + vcc.emplace_back(pc); + } + return vcc; + } else return vector{}; + } + + bool pbft_database::is_valid_prepared_certificate(const eosio::chain::pbft_prepared_certificate &certificate) { + // an empty certificate is valid since it acts as a null digest in pbft. + if (certificate == pbft_prepared_certificate{}) return true; + // a certificate under lscb (no longer in fork_db) is also treated as null. + if (certificate.block_num <= ctrl.last_stable_checkpoint_block_num()) return true; + + auto valid = true; + valid &= certificate.is_signature_valid(); + for (auto const &p : certificate.prepares) { + valid &= is_valid_prepare(p); + if (!valid) return false; + } + + auto cert_id = certificate.block_id; + auto cert_bs = ctrl.fetch_block_state_by_id(cert_id); + auto producer_schedule = lib_active_producers(); + if (certificate.block_num > 0 && cert_bs) { + producer_schedule = cert_bs->active_schedule; + } + auto bp_threshold = producer_schedule.producers.size() * 2 / 3 + 1; + + auto prepares = certificate.prepares; + flat_map prepare_count; + + for (const auto &pre: prepares) { + if (prepare_count.find(pre.view) == prepare_count.end()) prepare_count[pre.view] = 0; + } + + for (auto const &sp: producer_schedule.producers) { + for (auto const &pp: prepares) { + if (sp.block_signing_key == pp.public_key) prepare_count[pp.view] += 1; + } + } + + auto should_prepared = false; + + for (auto const &e: prepare_count) { + if (e.second >= bp_threshold) { + should_prepared = true; + } + } + + if (!should_prepared) return false; + + { + //validate prepare + auto lscb = ctrl.last_stable_checkpoint_block_num(); + auto non_fork_bp_count = 0; + vector prepare_infos(certificate.prepares.size()); + for (auto const &p : certificate.prepares) { + //only search in fork db + if (p.block_num <= lscb) { + ++non_fork_bp_count; + } else { + prepare_infos.push_back(block_info{p.block_id, p.block_num}); + } + } + + auto prepare_forks = fetch_fork_from(prepare_infos); + vector longest_fork; + for (auto const &f : prepare_forks) { + if (f.size() > longest_fork.size()) { + longest_fork = f; + } + } + if (longest_fork.size() + non_fork_bp_count < bp_threshold) return false; + + if (longest_fork.empty()) return true; + + auto calculated_block_info = longest_fork.back(); + + auto current_bs = ctrl.fetch_block_state_by_id(calculated_block_info.block_id); + while (current_bs) { + if (certificate.block_id == current_bs->id && certificate.block_num == current_bs->block_num) { + return true; + } + current_bs = ctrl.fetch_block_state_by_id(current_bs->prev()); + } + return false; + } + } + + bool pbft_database::is_valid_view_change(const pbft_view_change &vc) { + if (vc.chain_id != chain_id()) return false; + + return vc.is_signature_valid() + && should_recv_pbft_msg(vc.public_key); + // No need to check prepared cert and stable checkpoint, until generate or validate a new view msg + } + + + bool pbft_database::is_valid_new_view(const pbft_new_view &nv) { + //all signatures should be valid + if (nv.chain_id != chain_id()) return false; + + auto valid = is_valid_prepared_certificate(nv.prepared) + && is_valid_stable_checkpoint(nv.stable_checkpoint) + && nv.view_changed.is_signature_valid() + && nv.is_signature_valid(); + if (!valid) return false; + if (nv.view_changed.view != nv.view) return false; + auto schedule_threshold = lib_active_producers().producers.size() * 2 / 3 + 1; + + if (nv.view_changed.view_changes.size() < schedule_threshold) return false; + for (auto vc: nv.view_changed.view_changes) { + if (!is_valid_view_change(vc)) return false; + add_pbft_view_change(vc); + } + + if (!should_new_view(nv.view)) return false; + + auto highest_ppc = pbft_prepared_certificate{}; + auto highest_scp = pbft_stable_checkpoint{}; + + for (const auto &vc: nv.view_changed.view_changes) { + if (vc.prepared.block_num > highest_ppc.block_num + && is_valid_prepared_certificate(vc.prepared)) { + highest_ppc = vc.prepared; + } + + if (vc.stable_checkpoint.block_num > highest_scp.block_num + && is_valid_stable_checkpoint(vc.stable_checkpoint)) { + highest_scp = vc.stable_checkpoint; + } + } + + return highest_ppc == nv.prepared + && highest_scp == nv.stable_checkpoint; + } + + bool pbft_database::should_stop_view_change(const pbft_view_change &vc) { + auto lscb_num = ctrl.last_stable_checkpoint_block_num(); + return lscb_num > vc.prepared.block_num + && lscb_num > vc.stable_checkpoint.block_num; + } + + vector> pbft_database::fetch_fork_from(const vector block_infos) { + auto bi = block_infos; + + vector> result; + if (bi.empty()) { + return result; + } + if (bi.size() == 1) { + result.emplace_back(initializer_list{bi.front()}); + return result; + } + + sort(bi.begin(), bi.end(), + [](const block_info &a, const block_info &b) -> bool { return a.block_num > b.block_num; }); + + while (!bi.empty()) { + auto fork = fetch_first_fork_from(bi); + if (!fork.empty()) { + result.emplace_back(fork); + } + } + return result; + } + + vector pbft_database::fetch_first_fork_from(vector &bi) { + vector result; + if (bi.empty()) { + return result; + } + if (bi.size() == 1) { + result.emplace_back(bi.front()); + bi.clear(); + return result; + } + //bi should be sorted desc + auto high = bi.front().block_num; + auto low = bi.back().block_num; + + auto id = bi.front().block_id; + auto num = bi.front().block_num; + while (num <= high && num >= low && !bi.empty()) { + auto bs = ctrl.fetch_block_state_by_id(id); + + for (auto it = bi.begin(); it != bi.end();) { + if (it->block_id == id) { + if (bs) { + //add to result only if b exist + result.emplace_back((*it)); + } + it = bi.erase(it); + } else { + it++; + } + } + if (bs) { + id = bs->prev(); + num--; + } else { + break; + } + } + + return result; + } + + pbft_stable_checkpoint pbft_database::get_stable_checkpoint_by_id(const block_id_type &block_id) { + const auto &by_block = checkpoint_index.get(); + auto itr = by_block.find(block_id); + if (itr == by_block.end()) { + try { + auto blk = ctrl.fetch_block_by_id(block_id); + auto ext = blk->block_extensions; + if (!ext.empty() && ext.back().first == 0) { + auto scp_v = ext.back().second; + fc::datastream ds_decode( scp_v.data(), scp_v.size()); + + pbft_stable_checkpoint scp_decode; + fc::raw::unpack(ds_decode, scp_decode); + return scp_decode; + } + + } catch(...) { + wlog("no stable checkpoints found in the block extension"); + } + return pbft_stable_checkpoint{}; + } + auto cpp = *itr; + + if (cpp->is_stable) { + if (ctrl.my_signature_providers().empty()) return pbft_stable_checkpoint{}; + auto psc = pbft_stable_checkpoint{cpp->block_num, cpp->block_id, cpp->checkpoints, chain_id()}; + return psc; + } else return pbft_stable_checkpoint{}; + } + + block_info pbft_database::cal_pending_stable_checkpoint() const { + + auto lscb_num = ctrl.last_stable_checkpoint_block_num(); + auto lscb_id = ctrl.last_stable_checkpoint_block_id(); + auto lscb_info = block_info{lscb_id, lscb_num}; + + const auto &by_blk_num = checkpoint_index.get(); + auto itr = by_blk_num.lower_bound(lscb_num); + if (itr == by_blk_num.end()) return lscb_info; + + while (itr != by_blk_num.end()) { + if ((*itr)->is_stable && ctrl.fetch_block_state_by_id((*itr)->block_id)) { + auto lib = ctrl.fetch_block_state_by_number(ctrl.last_irreversible_block_num()); + + auto head_checkpoint_schedule = ctrl.fetch_block_state_by_id( + (*itr)->block_id)->active_schedule; + + auto current_schedule = lib_active_producers(); + auto new_schedule = lib_active_producers(); + + if (lib) { + current_schedule = lib->active_schedule; + new_schedule = lib->pending_schedule; + } + + if ((*itr)->is_stable + && (head_checkpoint_schedule == current_schedule || head_checkpoint_schedule == new_schedule)) { + lscb_info.block_id = (*itr)->block_id; + lscb_info.block_num = (*itr)->block_num; + } + } + ++itr; + } + return lscb_info; + } + + vector pbft_database::generate_and_add_pbft_checkpoint() { + auto new_pc = vector{}; + + const auto &by_commit_and_num_index = pbft_state_index.get(); + auto itr = by_commit_and_num_index.begin(); + if (itr == by_commit_and_num_index.end() || !(*itr)->should_committed) return new_pc; + + pbft_state_ptr psp = (*itr); + + vector pending_checkpoint_block_num; + + block_num_type my_latest_checkpoint = 0; + + auto checkpoint = [&](const block_num_type &in) { + return in % 100 == 1 + || in == ctrl.last_proposed_schedule_block_num() + || in == ctrl.last_promoted_proposed_schedule_block_num(); + }; + + for (auto i = psp->block_num; + i > std::max(ctrl.last_stable_checkpoint_block_num(), static_cast(1)); --i) { + if (checkpoint(i)) { + my_latest_checkpoint = max(i, my_latest_checkpoint); + auto &by_block = checkpoint_index.get(); + auto c_itr = by_block.find(ctrl.get_block_id_for_num(i)); + if (c_itr == by_block.end()) { + pending_checkpoint_block_num.emplace_back(i); + } else { + auto checkpoints = (*c_itr)->checkpoints; + bool contains_mine = false; + for (auto const &my_sp : ctrl.my_signature_providers()) { + auto p_itr = find_if(checkpoints.begin(), checkpoints.end(), + [&](const pbft_checkpoint &ext) { + return ext.public_key == my_sp.first; + }); + if (p_itr != checkpoints.end()) contains_mine = true; + } + if (!contains_mine) { + pending_checkpoint_block_num.emplace_back(i); + } + } + } + } + + if (!pending_checkpoint_block_num.empty()) { + for (auto h: pending_checkpoint_block_num) { + for (auto const &my_sp : ctrl.my_signature_providers()) { + auto uuid = boost::uuids::to_string(uuid_generator()); + auto cp = pbft_checkpoint{uuid, h, ctrl.get_block_id_for_num(h), + my_sp.first, .chain_id=chain_id()}; + cp.producer_signature = my_sp.second(cp.digest()); + add_pbft_checkpoint(cp); + new_pc.emplace_back(cp); + } + } + } else if (my_latest_checkpoint > 1) { + auto lscb_id = ctrl.get_block_id_for_num(my_latest_checkpoint); + auto &by_block = checkpoint_index.get(); + auto h_itr = by_block.find(lscb_id); + if (h_itr != by_block.end()) { + auto checkpoints = (*h_itr)->checkpoints; + for (auto const &my_sp : ctrl.my_signature_providers()) { + for (auto const &cp: checkpoints) { + if (my_sp.first == cp.public_key) { + auto retry_cp = cp; + auto uuid = boost::uuids::to_string(uuid_generator()); + retry_cp.uuid = uuid; + retry_cp.timestamp = time_point::now(); + retry_cp.producer_signature = my_sp.second(retry_cp.digest()); + new_pc.emplace_back(retry_cp); + } + } + } + } + } + + return new_pc; + } + + void pbft_database::add_pbft_checkpoint(pbft_checkpoint &cp) { + + if (!is_valid_checkpoint(cp)) return; + + auto lscb_num = ctrl.last_stable_checkpoint_block_num(); + + auto cp_block_state = ctrl.fetch_block_state_by_id(cp.block_id); + if (!cp_block_state) return; + auto active_bps = cp_block_state->active_schedule.producers; + auto checkpoint_count = count_if(active_bps.begin(), active_bps.end(), [&](const producer_key &p) { + return p.block_signing_key == cp.public_key; + }); + if (checkpoint_count == 0) return; + + auto &by_block = checkpoint_index.get(); + auto itr = by_block.find(cp.block_id); + if (itr == by_block.end()) { + auto cs = pbft_checkpoint_state{cp.block_id, cp.block_num, .checkpoints={cp}}; + auto csp = make_shared(cs); + checkpoint_index.insert(csp); + itr = by_block.find(cp.block_id); + } else { + auto csp = (*itr); + auto checkpoints = csp->checkpoints; + auto p_itr = find_if(checkpoints.begin(), checkpoints.end(), + [&](const pbft_checkpoint &existed) { + return existed.public_key == cp.public_key; + }); + if (p_itr == checkpoints.end()) { + by_block.modify(itr, [&](const pbft_checkpoint_state_ptr &pcp) { + csp->checkpoints.emplace_back(cp); + }); + } + } + + auto csp = (*itr); + auto cp_count = 0; + if (!csp->is_stable) { + for (auto const &sp: active_bps) { + for (auto const &pp: csp->checkpoints) { + if (sp.block_signing_key == pp.public_key) cp_count += 1; + } + } + if (cp_count >= active_bps.size() * 2 / 3 + 1) { + by_block.modify(itr, [&](const pbft_checkpoint_state_ptr &pcp) { csp->is_stable = true; }); + auto id = csp->block_id; + auto blk = ctrl.fetch_block_by_id(id); + + if (blk && (blk->block_extensions.empty() || blk->block_extensions.back().first != 0 )) { + auto scp = get_stable_checkpoint_by_id(id); + auto scp_size = fc::raw::pack_size(scp); + + auto buffer = std::make_shared>(scp_size); + fc::datastream ds( buffer->data(), scp_size); + fc::raw::pack( ds, scp ); + + blk->block_extensions.emplace_back(); + auto &extension = blk->block_extensions.back(); + extension.first = static_cast(0); + extension.second.resize(scp_size); + std::copy(buffer->begin(),buffer->end(), extension.second.data()); + } + +// wlog("block extensions: ${ext}", ("ext", blk->block_extensions)); +// auto decode = blk->block_extensions.back().second; +// fc::datastream ds_decode( decode.data(), decode.size()); +// +// pbft_stable_checkpoint scp_decode; +// fc::raw::unpack(ds_decode, scp_decode); +// +// +// wlog("decode scp: ${scp}", ("scp", scp_decode)); + + } + } + + auto lscb_info = cal_pending_stable_checkpoint(); + auto pending_num = lscb_info.block_num; + auto pending_id = lscb_info.block_id; + if (pending_num > lscb_num) { + ctrl.set_pbft_latest_checkpoint(pending_id); + if (ctrl.last_irreversible_block_num() < pending_num) ctrl.pbft_commit_local(pending_id); + const auto &by_block_id_index = pbft_state_index.get(); + auto pitr = by_block_id_index.find(pending_id); + if (pitr != by_block_id_index.end()) { + prune(*pitr); + } + } + + } + + void pbft_database::send_pbft_checkpoint() { + auto cps_to_send = generate_and_add_pbft_checkpoint(); + for (auto const &cp: cps_to_send) { + emit(pbft_outgoing_checkpoint, cp); + } + } + + bool pbft_database::is_valid_checkpoint(const pbft_checkpoint &cp) { + + if (cp.block_num > ctrl.head_block_num() + || cp.block_num <= ctrl.last_stable_checkpoint_block_num() + || !cp.is_signature_valid()) + return false; + auto bs = ctrl.fetch_block_state_by_id(cp.block_id); + if (bs) { + auto active_bps = bs->active_schedule.producers; + for (const auto &bp: active_bps) { + if (bp.block_signing_key == cp.public_key) return true; + } + } + return false; + } + + bool pbft_database::is_valid_stable_checkpoint(const pbft_stable_checkpoint &scp) { + if (scp.block_num <= ctrl.last_stable_checkpoint_block_num()) return true; + + auto valid = true; + for (const auto &c: scp.checkpoints) { + valid &= is_valid_checkpoint(c) + && c.block_id == scp.block_id + && c.block_num == scp.block_num; + if (!valid) return false; + } + //TODO: check if (2/3 + 1) met + return valid; + } + + bool pbft_database::should_send_pbft_msg() { + + //use last_stable_checkpoint producer schedule + auto lscb_num = ctrl.last_stable_checkpoint_block_num(); + + auto as = lib_active_producers(); + auto my_sp = ctrl.my_signature_providers(); + + for (auto i = lscb_num; i <= ctrl.head_block_num(); ++i) { + for (auto const &bp: as.producers) { + for (auto const &my: my_sp) { + if (bp.block_signing_key == my.first) return true; + } + } + auto bs = ctrl.fetch_block_state_by_number(i); + if (bs && bs->active_schedule != as) as = bs->active_schedule; + } + return false; + } + + bool pbft_database::should_recv_pbft_msg(const public_key_type &pub_key) { + auto lscb_num = ctrl.last_stable_checkpoint_block_num(); + + auto as = lib_active_producers(); + auto my_sp = ctrl.my_signature_providers(); + + for (auto i = lscb_num; i <= ctrl.head_block_num(); ++i) { + for (auto const &bp: as.producers) { + if (bp.block_signing_key == pub_key) return true; + } + auto bs = ctrl.fetch_block_state_by_number(i); + if (bs && bs->active_schedule != as) as = bs->active_schedule; + } + return false; + } + + public_key_type pbft_database::get_new_view_primary_key(const uint32_t target_view) { + + auto active_bps = lib_active_producers().producers; + if (active_bps.empty()) return public_key_type{}; + + return active_bps[target_view % active_bps.size()].block_signing_key; + } + + producer_schedule_type pbft_database::lib_active_producers() const { + auto lib_num = ctrl.last_irreversible_block_num(); + if (lib_num == 0) return ctrl.initial_schedule(); + + auto lib_state = ctrl.fetch_block_state_by_number(lib_num); + if (!lib_state) return ctrl.initial_schedule(); + + if (lib_state->pending_schedule.producers.empty()) return lib_state->active_schedule; + return lib_state->pending_schedule; + } + + chain_id_type pbft_database::chain_id() { + return ctrl.get_chain_id(); + } + + void pbft_database::set(pbft_state_ptr s) { + auto result = pbft_state_index.insert(s); + + EOS_ASSERT(result.second, pbft_exception, + "unable to insert pbft state, duplicate state detected"); + } + + void pbft_database::set(pbft_checkpoint_state_ptr s) { + auto result = checkpoint_index.insert(s); + + EOS_ASSERT(result.second, pbft_exception, + "unable to insert pbft checkpoint index, duplicate state detected"); + } + + void pbft_database::prune(const pbft_state_ptr &h) { + auto num = h->block_num; + + auto &by_bn = pbft_state_index.get(); + auto bni = by_bn.begin(); + while (bni != by_bn.end() && (*bni)->block_num < num) { + prune(*bni); + bni = by_bn.begin(); + } + + auto itr = pbft_state_index.find(h->block_id); + if (itr != pbft_state_index.end()) { + pbft_state_index.erase(itr); + } + } + + template + void pbft_database::emit(const Signal &s, Arg &&a) { + try { + s(std::forward(a)); + } catch (boost::interprocess::bad_alloc &e) { + wlog("bad alloc"); + throw e; + } catch (controller_emit_signal_exception &e) { + wlog("${details}", ("details", e.to_detail_string())); + throw e; + } catch (fc::exception &e) { + wlog("${details}", ("details", e.to_detail_string())); + } catch (...) { + wlog("signal handler threw exception"); + } + } + } +} \ No newline at end of file diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index c9a4591d6c9..f65536dda03 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -291,6 +292,7 @@ namespace eosio { namespace testing { fc::temp_directory tempdir; public: unique_ptr control; + unique_ptr pbft_ctrl; std::map block_signing_private_keys; protected: controller::config cfg; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8c93df9c48e..68c41064f4d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory(producer_plugin) add_subdirectory(producer_api_plugin) add_subdirectory(history_plugin) add_subdirectory(history_api_plugin) +add_subdirectory(pbft_plugin) add_subdirectory(state_history_plugin) add_subdirectory(wallet_plugin) diff --git a/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp b/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp index b62915b5220..766a73df56f 100644 --- a/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp +++ b/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace eosio { namespace chain { namespace plugin_interface { using namespace eosio::chain; @@ -61,4 +62,24 @@ namespace eosio { namespace chain { namespace plugin_interface { } } + namespace pbft { + namespace incoming { + using prepare_channel = channel_decl; + using commit_channel = channel_decl; + using view_change_channel = channel_decl; + using new_view_channel = channel_decl; + using checkpoint_channel = channel_decl; + + } + + namespace outgoing { + using prepare_channel = channel_decl; + using commit_channel = channel_decl; + using view_change_channel = channel_decl; + using new_view_channel = channel_decl; + using checkpoint_channel = channel_decl; + + } + } + } } } diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 91d130d3ec5..aef6628f9eb 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -150,24 +150,41 @@ class chain_plugin_impl { ,incoming_block_channel(app().get_channel()) ,incoming_block_sync_method(app().get_method()) ,incoming_transaction_async_method(app().get_method()) + //pbft channels + ,pbft_outgoing_prepare_channel(app().get_channel()) + ,pbft_incoming_prepare_channel(app().get_channel()) + ,pbft_outgoing_commit_channel(app().get_channel()) + ,pbft_incoming_commit_channel(app().get_channel()) + ,pbft_outgoing_view_change_channel(app().get_channel()) + ,pbft_incoming_view_change_channel(app().get_channel()) + ,pbft_outgoing_new_view_channel(app().get_channel()) + ,pbft_incoming_new_view_channel(app().get_channel()) + ,pbft_outgoing_checkpoint_channel(app().get_channel()) + ,pbft_incoming_checkpoint_channel(app().get_channel()) {} bfs::path blocks_dir; bool readonly = false; flat_map loaded_checkpoints; - fc::optional fork_db; +// fc::optional fork_db; fc::optional block_logger; fc::optional chain_config; fc::optional chain; fc::optional chain_id; + fc::optional pbft_ctrl; //txn_msg_rate_limits rate_limits; fc::optional wasm_runtime; fc::microseconds abi_serializer_max_time_ms; fc::optional snapshot_path; + void on_pbft_incoming_prepare(pbft_prepare p); + void on_pbft_incoming_commit(pbft_commit c); + void on_pbft_incoming_view_change(pbft_view_change vc); + void on_pbft_incoming_new_view(pbft_new_view nv); + void on_pbft_incoming_checkpoint(pbft_checkpoint cp); - // retained references to channels for easy publication + // retained references to channels for easy publication channels::pre_accepted_block::channel_type& pre_accepted_block_channel; channels::accepted_block_header::channel_type& accepted_block_header_channel; channels::accepted_block::channel_type& accepted_block_channel; @@ -196,7 +213,31 @@ class chain_plugin_impl { fc::optional applied_transaction_connection; fc::optional accepted_confirmation_connection; - + //pbft + fc::optional pbft_outgoing_prepare_connection; + pbft::incoming::prepare_channel::channel_type::handle pbft_incoming_prepare_subscription; + pbft::outgoing::prepare_channel::channel_type& pbft_outgoing_prepare_channel; + pbft::incoming::prepare_channel::channel_type& pbft_incoming_prepare_channel; + + fc::optional pbft_outgoing_commit_connection; + pbft::incoming::commit_channel::channel_type::handle pbft_incoming_commit_subscription; + pbft::outgoing::commit_channel::channel_type& pbft_outgoing_commit_channel; + pbft::incoming::commit_channel::channel_type& pbft_incoming_commit_channel; + + fc::optional pbft_outgoing_view_change_connection; + pbft::incoming::view_change_channel::channel_type::handle pbft_incoming_view_change_subscription; + pbft::outgoing::view_change_channel::channel_type& pbft_outgoing_view_change_channel; + pbft::incoming::view_change_channel::channel_type& pbft_incoming_view_change_channel; + + fc::optional pbft_outgoing_new_view_connection; + pbft::incoming::new_view_channel::channel_type::handle pbft_incoming_new_view_subscription; + pbft::outgoing::new_view_channel::channel_type& pbft_outgoing_new_view_channel; + pbft::incoming::new_view_channel::channel_type& pbft_incoming_new_view_channel; + + fc::optional pbft_outgoing_checkpoint_connection; + pbft::incoming::checkpoint_channel::channel_type::handle pbft_incoming_checkpoint_subscription; + pbft::outgoing::checkpoint_channel::channel_type& pbft_outgoing_checkpoint_channel; + pbft::incoming::checkpoint_channel::channel_type& pbft_incoming_checkpoint_channel; }; chain_plugin::chain_plugin() @@ -294,12 +335,25 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip } +template +T dejsonify(const string& s) { + return fc::json::from_string(s).as(); +} + #define LOAD_VALUE_SET(options, name, container) \ if( options.count(name) ) { \ const std::vector& ops = options[name].as>(); \ std::copy(ops.begin(), ops.end(), std::inserter(container, container.end())); \ } +static signature_provider_type +make_key_signature_provider(const private_key_type& key) { + return [key]( const chain::digest_type& digest ) { + return key.sign(digest); + }; +} + + fc::time_point calculate_genesis_timestamp( string tstr ) { fc::time_point genesis_timestamp; if( strcasecmp (tstr.c_str(), "now") == 0 ) { @@ -350,6 +404,48 @@ void chain_plugin::plugin_initialize(const variables_map& options) { LOAD_VALUE_SET( options, "actor-blacklist", my->chain_config->actor_blacklist ); LOAD_VALUE_SET( options, "contract-whitelist", my->chain_config->contract_whitelist ); LOAD_VALUE_SET( options, "contract-blacklist", my->chain_config->contract_blacklist ); + LOAD_VALUE_SET( options, "producer-name", my->chain_config->my_producers); + if( options.count("private-key") ) + { + const std::vector key_id_to_wif_pair_strings = options["private-key"].as>(); + for (const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings) + { + try { + auto key_id_to_wif_pair = dejsonify>(key_id_to_wif_pair_string); + my->chain_config->my_signature_providers[key_id_to_wif_pair.first] = make_key_signature_provider(key_id_to_wif_pair.second); + auto blanked_privkey = std::string(std::string(key_id_to_wif_pair.second).size(), '*' ); + wlog("\"private-key\" is DEPRECATED, use \"signature-provider=${pub}=KEY:${priv}\"", ("pub",key_id_to_wif_pair.first)("priv", blanked_privkey)); + } catch ( fc::exception& e ) { + elog("Malformed private key pair"); + } + } + } + + if( options.count("signature-provider") ) { + const std::vector key_spec_pairs = options["signature-provider"].as>(); + for (const auto& key_spec_pair : key_spec_pairs) { + try { + auto delim = key_spec_pair.find("="); + EOS_ASSERT(delim != std::string::npos, plugin_config_exception, "Missing \"=\" in the key spec pair"); + auto pub_key_str = key_spec_pair.substr(0, delim); + auto spec_str = key_spec_pair.substr(delim + 1); + + auto spec_delim = spec_str.find(":"); + EOS_ASSERT(spec_delim != std::string::npos, plugin_config_exception, "Missing \":\" in the key spec pair"); + auto spec_type_str = spec_str.substr(0, spec_delim); + auto spec_data = spec_str.substr(spec_delim + 1); + + auto pubkey = public_key_type(pub_key_str); + + if (spec_type_str == "KEY") { + my->chain_config->my_signature_providers[pubkey] = make_key_signature_provider(private_key_type(spec_data)); + } + + } catch (...) { + elog("Malformed signature provider: \"${val}\", ignoring!", ("val", key_spec_pair)); + } + } + } LOAD_VALUE_SET( options, "trusted-producer", my->chain_config->trusted_producers ); @@ -641,6 +737,9 @@ void chain_plugin::plugin_initialize(const variables_map& options) { my->chain.emplace( *my->chain_config ); my->chain_id.emplace( my->chain->get_chain_id()); + ilog("include pbft controller..."); + my->pbft_ctrl.emplace(*my->chain); + // set up method providers my->get_block_by_number_provider = app().get_method().register_provider( [this]( uint32_t block_num ) -> signed_block_ptr { @@ -703,11 +802,81 @@ void chain_plugin::plugin_initialize(const variables_map& options) { my->accepted_confirmation_channel.publish( conf ); } ); + + + //pbft + my->pbft_incoming_prepare_subscription = my->pbft_incoming_prepare_channel.subscribe( [this]( pbft_prepare p ){ + my->on_pbft_incoming_prepare(p); + }); + + my->pbft_incoming_commit_subscription = my->pbft_incoming_commit_channel.subscribe( [this]( pbft_commit c ){ + my->on_pbft_incoming_commit(c); + }); + + my->pbft_incoming_view_change_subscription = my->pbft_incoming_view_change_channel.subscribe( [this]( pbft_view_change vc ){ + my->on_pbft_incoming_view_change(vc); + }); + + my->pbft_incoming_new_view_subscription = my->pbft_incoming_new_view_channel.subscribe( [this]( pbft_new_view nv ){ + my->on_pbft_incoming_new_view(nv); + }); + + my->pbft_incoming_checkpoint_subscription = my->pbft_incoming_checkpoint_channel.subscribe( [this]( pbft_checkpoint cp ){ + my->on_pbft_incoming_checkpoint(cp); + }); + + my->pbft_outgoing_prepare_connection = my->pbft_ctrl->pbft_db.pbft_outgoing_prepare.connect( + [this]( const pbft_prepare& prepare ) { + my->pbft_outgoing_prepare_channel.publish( prepare ); + }); + + my->pbft_outgoing_commit_connection = my->pbft_ctrl->pbft_db.pbft_outgoing_commit.connect( + [this]( const pbft_commit& commit ) { + my->pbft_outgoing_commit_channel.publish( commit ); + }); + + my->pbft_outgoing_view_change_connection = my->pbft_ctrl->pbft_db.pbft_outgoing_view_change.connect( + [this]( const pbft_view_change& view_change ) { + my->pbft_outgoing_view_change_channel.publish( view_change ); + }); + + my->pbft_outgoing_new_view_connection = my->pbft_ctrl->pbft_db.pbft_outgoing_new_view.connect( + [this]( const pbft_new_view& new_view ) { + my->pbft_outgoing_new_view_channel.publish( new_view ); + }); + + my->pbft_outgoing_checkpoint_connection = my->pbft_ctrl->pbft_db.pbft_outgoing_checkpoint.connect( + [this]( const pbft_checkpoint& checkpoint ) { + my->pbft_outgoing_checkpoint_channel.publish( checkpoint ); + }); + my->chain->add_indices(); } FC_LOG_AND_RETHROW() + +} + +void chain_plugin_impl::on_pbft_incoming_prepare(pbft_prepare p){ + pbft_ctrl->on_pbft_prepare(p); +} + +void chain_plugin_impl::on_pbft_incoming_commit(pbft_commit c){ + pbft_ctrl->on_pbft_commit(c); +} + +void chain_plugin_impl::on_pbft_incoming_view_change(pbft_view_change vc){ + pbft_ctrl->on_pbft_view_change(vc); +} + +void chain_plugin_impl::on_pbft_incoming_new_view(pbft_new_view nv){ + pbft_ctrl->on_pbft_new_view(nv); } +void chain_plugin_impl::on_pbft_incoming_checkpoint(pbft_checkpoint cp){ + pbft_ctrl->on_pbft_checkpoint(cp); +} + + void chain_plugin::plugin_startup() { try { try { @@ -979,6 +1148,8 @@ bool chain_plugin::export_reversible_blocks( const fc::path& reversible_dir, controller& chain_plugin::chain() { return *my->chain; } const controller& chain_plugin::chain() const { return *my->chain; } +pbft_controller& chain_plugin::pbft_ctrl() { return *my->pbft_ctrl; } +const pbft_controller& chain_plugin::pbft_ctrl() const { return *my->pbft_ctrl; } chain::chain_id_type chain_plugin::get_chain_id()const { EOS_ASSERT( my->chain_id.valid(), chain_id_type_exception, "chain ID has not been initialized yet" ); @@ -1038,6 +1209,9 @@ read_only::get_info_results read_only::get_info(const read_only::get_info_params db.fork_db_head_block_id(), db.fork_db_head_block_time(), db.fork_db_head_block_producer(), + pbft_ctrl.state_machine.get_current_view(), + pbft_ctrl.state_machine.get_target_view(), + db.last_stable_checkpoint_block_num(), rm.get_virtual_block_cpu_limit(), rm.get_virtual_block_net_limit(), rm.get_block_cpu_limit(), 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 4a41d99455c..1f1c991cd42 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -20,6 +20,7 @@ #include #include +#include namespace fc { class variant; } @@ -73,12 +74,13 @@ class read_only { const controller& db; const fc::microseconds abi_serializer_max_time; bool shorten_abi_errors = true; + const chain::pbft_controller& pbft_ctrl; public: static const string KEYi64; - read_only(const controller& db, const fc::microseconds& abi_serializer_max_time) - : db(db), abi_serializer_max_time(abi_serializer_max_time) {} + read_only(const controller& db, const fc::microseconds& abi_serializer_max_time, const chain::pbft_controller& pbft_ctrl) + : db(db), abi_serializer_max_time(abi_serializer_max_time), pbft_ctrl(pbft_ctrl) {} void validate() const {} @@ -95,7 +97,9 @@ class read_only { chain::block_id_type head_block_id; fc::time_point head_block_time; account_name head_block_producer; - + uint32_t current_view = 0; + uint32_t target_view = 0; + uint32_t last_stable_checkpoint_block_num = 0; uint64_t virtual_block_cpu_limit = 0; uint64_t virtual_block_net_limit = 0; @@ -659,7 +663,7 @@ class chain_plugin : public plugin { void plugin_startup(); void plugin_shutdown(); - chain_apis::read_only get_read_only_api() const { return chain_apis::read_only(chain(), get_abi_serializer_max_time()); } + chain_apis::read_only get_read_only_api() const { return chain_apis::read_only(chain(), get_abi_serializer_max_time(), pbft_ctrl()); } chain_apis::read_write get_read_write_api() { return chain_apis::read_write(chain(), get_abi_serializer_max_time()); } void accept_block( const chain::signed_block_ptr& block ); @@ -688,6 +692,11 @@ class chain_plugin : public plugin { // Only call this after plugin_initialize()! const controller& chain() const; + // Only call this after plugin_initialize()! + chain::pbft_controller& pbft_ctrl(); + // Only call this after plugin_initialize()! + const chain::pbft_controller& pbft_ctrl() const; + chain::chain_id_type get_chain_id() const; fc::microseconds get_abi_serializer_max_time() const; @@ -705,7 +714,7 @@ class chain_plugin : public plugin { 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) ) +(server_version)(chain_id)(head_block_num)(last_irreversible_block_num)(last_irreversible_block_id)(head_block_id)(head_block_time)(head_block_producer)(current_view)(target_view)(last_stable_checkpoint_block_num)(virtual_block_cpu_limit)(virtual_block_net_limit)(block_cpu_limit)(block_net_limit)(server_version_string) ) 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/net_plugin/include/eosio/net_plugin/net_plugin.hpp b/plugins/net_plugin/include/eosio/net_plugin/net_plugin.hpp index d732b18cf0c..7fb193a979b 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/net_plugin.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/net_plugin.hpp @@ -30,12 +30,14 @@ namespace eosio { void plugin_startup(); void plugin_shutdown(); + void broadcast_block(const chain::signed_block &sb); string connect( const string& endpoint ); string disconnect( const string& endpoint ); optional status( const string& endpoint )const; vector connections()const; + bool is_syncing()const; size_t num_peers() const; private: diff --git a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp index 7170c1abd20..70fd94476a0 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp @@ -5,6 +5,7 @@ #pragma once #include #include +#include #include namespace eosio { @@ -132,6 +133,11 @@ namespace eosio { uint32_t end_block; }; + struct checkpoint_request_message { + uint32_t start_block; + uint32_t end_block; + }; + using net_message = static_variant; // which = 8 + packed_transaction, // which = 8 + pbft_prepare, + pbft_commit, + pbft_view_change, + pbft_new_view, + pbft_checkpoint, + pbft_stable_checkpoint, + checkpoint_request_message>; + + using pbft_message = static_variant; } // namespace eosio @@ -159,6 +180,8 @@ FC_REFLECT( eosio::time_message, (org)(rec)(xmt)(dst) ) FC_REFLECT( eosio::notice_message, (known_trx)(known_blocks) ) FC_REFLECT( eosio::request_message, (req_trx)(req_blocks) ) FC_REFLECT( eosio::sync_request_message, (start_block)(end_block) ) +FC_REFLECT( eosio::checkpoint_request_message, (start_block)(end_block) ) + /** * diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 1b398a8b53a..954cd6be496 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -26,6 +26,8 @@ #include #include +#include + using namespace eosio::chain::plugin_interface::compat; namespace fc { @@ -99,7 +101,11 @@ namespace eosio { node_transaction_index; class net_plugin_impl { + private: + std::shared_ptr> encode_pbft_message(const net_message &msg)const; public: + net_plugin_impl(); + unique_ptr acceptor; tcp::endpoint listen_endpoint; string p2p_address; @@ -130,10 +136,14 @@ namespace eosio { unique_ptr connector_check; unique_ptr transaction_check; unique_ptr keepalive_timer; + unique_ptr pbft_message_cache_timer; + unique_ptr connection_monitor_timer; boost::asio::steady_timer::duration connector_period; boost::asio::steady_timer::duration txn_exp_period; boost::asio::steady_timer::duration resp_expected_period; boost::asio::steady_timer::duration keepalive_interval{std::chrono::seconds{32}}; + boost::asio::steady_timer::duration pbft_message_cache_tick_interval{std::chrono::seconds{10}}; + boost::asio::steady_timer::duration connection_monitor_tick_interval{std::chrono::seconds{2}}; int max_cleanup_time_ms = 0; const std::chrono::system_clock::duration peer_authentication_interval{std::chrono::seconds{1}}; ///< Peer clock may be no more than 1 second skewed from our clock, including network latency. @@ -153,7 +163,22 @@ namespace eosio { bool use_socket_read_watermark = false; + std::unordered_map pbft_message_cache{}; + const int pbft_message_cache_TTL = 600; + const int pbft_message_TTL = 10; + channels::transaction_ack::channel_type::handle incoming_transaction_ack_subscription; + eosio::chain::plugin_interface::pbft::outgoing::prepare_channel::channel_type::handle pbft_outgoing_prepare_subscription; + eosio::chain::plugin_interface::pbft::outgoing::commit_channel::channel_type::handle pbft_outgoing_commit_subscription; + eosio::chain::plugin_interface::pbft::outgoing::view_change_channel::channel_type::handle pbft_outgoing_view_change_subscription; + eosio::chain::plugin_interface::pbft::outgoing::new_view_channel::channel_type::handle pbft_outgoing_new_view_subscription; + eosio::chain::plugin_interface::pbft::outgoing::checkpoint_channel::channel_type::handle pbft_outgoing_checkpoint_subscription; + + eosio::chain::plugin_interface::pbft::incoming::prepare_channel::channel_type& pbft_incoming_prepare_channel; + eosio::chain::plugin_interface::pbft::incoming::commit_channel::channel_type& pbft_incoming_commit_channel; + eosio::chain::plugin_interface::pbft::incoming::view_change_channel::channel_type& pbft_incoming_view_change_channel; + eosio::chain::plugin_interface::pbft::incoming::new_view_channel::channel_type& pbft_incoming_new_view_channel; + eosio::chain::plugin_interface::pbft::incoming::checkpoint_channel::channel_type& pbft_incoming_checkpoint_channel; void connect(const connection_ptr& c); void connect(const connection_ptr& c, tcp::resolver::iterator endpoint_itr); @@ -197,6 +222,31 @@ namespace eosio { void handle_message(const connection_ptr& c, const signed_block_ptr& msg); void handle_message(const connection_ptr& c, const packed_transaction& msg) = delete; // packed_transaction_ptr overload used instead void handle_message(const connection_ptr& c, const packed_transaction_ptr& msg); + //pbft messages + bool maybe_add_pbft_cache(const string &uuid); + void clean_expired_pbft_cache(); + template + bool is_pbft_msg_outdated(M const & msg); + template + bool is_pbft_msg_valid(M const & msg); + + void bcast_pbft_msg(const net_message &msg); + + void forward_pbft_msg(connection_ptr c, const net_message &msg); + + void pbft_outgoing_prepare(const pbft_prepare &prepare); + void pbft_outgoing_commit(const pbft_commit &commit); + void pbft_outgoing_view_change(const pbft_view_change &view_change); + void pbft_outgoing_new_view(const pbft_new_view &new_view); + void pbft_outgoing_checkpoint(const pbft_checkpoint &checkpoint); + + void handle_message( connection_ptr c, const pbft_prepare &msg); + void handle_message( connection_ptr c, const pbft_commit &msg); + void handle_message( connection_ptr c, const pbft_view_change &msg); + void handle_message( connection_ptr c, const pbft_new_view &msg); + void handle_message( connection_ptr c, const pbft_checkpoint &msg); + void handle_message( connection_ptr c, const pbft_stable_checkpoint &msg); + void handle_message( connection_ptr c, const checkpoint_request_message &msg); void start_conn_timer(boost::asio::steady_timer::duration du, std::weak_ptr from_connection); void start_txn_timer(); @@ -205,6 +255,9 @@ namespace eosio { void expire_txns(); void expire_local_txns(); void connection_monitor(std::weak_ptr from_connection); + + void pbft_message_cache_ticker(); + void connection_monitor_ticker(); /** \name Peer Timestamps * Time message handling * @{ @@ -420,6 +473,8 @@ namespace eosio { uint32_t write_queue_size() const { return _write_queue_size; } + uint32_t out_queue_size() const { return _out_queue.size(); } + bool is_out_queue_empty() const { return _out_queue.empty(); } bool ready_to_send() const { @@ -500,6 +555,14 @@ namespace eosio { fc::message_buffer<1024*1024> pending_message_buffer; fc::optional outstanding_read_bytes; + struct queued_pbft_message { + std::shared_ptr> message; + fc::time_point_sec deadline; + }; + const int OUT_QUEUE_SIZE_LIMIT_FROM_WRITE_QUEUE = 100; + const int OUT_QUEUE_SIZE_LIMIT = 200; + + deque pbft_queue; queued_buffer buffer_queue; @@ -511,6 +574,8 @@ namespace eosio { int16_t sent_handshake_count = 0; bool connecting = false; bool syncing = false; + int connecting_timeout_in_seconds = 10; + fc::time_point_sec connecting_deadline; uint16_t protocol_version = 0; string peer_addr; unique_ptr response_expected; @@ -549,6 +614,7 @@ namespace eosio { bool connected(); bool current(); + bool pbft_ready(); void reset(); void close(); void send_handshake(); @@ -589,11 +655,13 @@ namespace eosio { void enqueue_buffer( const std::shared_ptr>& send_buffer, bool trigger_send, go_away_reason close_after_send, bool to_sync_queue = false); + void enqueue_pbft( const std::shared_ptr>& m, const time_point_sec deadline); void cancel_sync(go_away_reason); void flush_queues(); bool enqueue_sync_block(); void request_sync_blocks(uint32_t start, uint32_t end); + void request_sync_checkpoints(uint32_t start, uint32_t end); void cancel_wait(); void sync_wait(); void fetch_wait(); @@ -605,6 +673,7 @@ namespace eosio { std::function callback, bool to_sync_queue = false); void do_queue_write(); + void do_queue_write_from_pbft_queue(std::vector &bufs); /** \brief Process the next message from the pending message buffer * @@ -711,6 +780,8 @@ namespace eosio { void recv_block(const connection_ptr& c, const block_id_type& blk_id, uint32_t blk_num); void recv_handshake(const connection_ptr& c, const handshake_message& msg); void recv_notice(const connection_ptr& c, const notice_message& msg); + bool is_syncing(); + void set_in_sync(); }; class dispatch_manager { @@ -768,6 +839,7 @@ namespace eosio { sent_handshake_count(0), connecting(true), syncing(false), + connecting_deadline(fc::time_point::now()+fc::seconds(connecting_timeout_in_seconds)), protocol_version(0), peer_addr(), response_expected(), @@ -799,6 +871,10 @@ namespace eosio { return (connected() && !syncing); } + bool connection::pbft_ready(){ + return current(); + } + void connection::reset() { peer_requested.reset(); blk_state.clear(); @@ -807,6 +883,7 @@ namespace eosio { void connection::flush_queues() { buffer_queue.clear_write_queue(); + pbft_queue.clear(); } void connection::close() { @@ -818,6 +895,7 @@ namespace eosio { } flush_queues(); connecting = false; + connecting_deadline = fc::time_point::min(); syncing = false; if( last_req ) { my_impl->dispatcher->retry_fetch(shared_from_this()); @@ -855,6 +933,7 @@ namespace eosio { void connection::blk_send_branch() { controller& cc = my_impl->chain_plug->chain(); + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); uint32_t head_num = cc.fork_db_head_block_num(); notice_message note; note.known_blocks.mode = normal; @@ -978,8 +1057,9 @@ namespace eosio { } void connection::do_queue_write() { - if( !buffer_queue.ready_to_send() ) - return; + if( !(buffer_queue.ready_to_send() || (!pbft_queue.empty() && buffer_queue.is_out_queue_empty()))) + return; + connection_wptr c(shared_from_this()); if(!socket->is_open()) { fc_elog(logger,"socket not open to ${p}",("p",peer_name())); @@ -988,6 +1068,9 @@ namespace eosio { } std::vector bufs; buffer_queue.fill_out_buffer( bufs ); + + do_queue_write_from_pbft_queue( bufs ); + boost::asio::async_write(*socket, bufs, [c](boost::system::error_code ec, std::size_t w) { try { auto conn = c.lock(); @@ -1029,6 +1112,50 @@ namespace eosio { }); } + void connection::do_queue_write_from_pbft_queue(std::vector &bufs){ + //delete timeout pbft message + auto now = time_point::now(); + int drop_pbft_count = 0; + while (pbft_queue.size()>0) { + if (pbft_queue.front().deadline <= now) { + pbft_queue.pop_front(); + ++drop_pbft_count; + } else { + break; + } + } + + //drop timeout messages in mem, init send buffer only when actual send happens + //copied from function connection::enqueue + connection_wptr weak_this = shared_from_this(); + go_away_reason close_after_send = no_reason; + std::function callback = [weak_this, close_after_send](boost::system::error_code ec, std::size_t ) { + connection_ptr conn = weak_this.lock(); + if (conn) { + if (close_after_send != no_reason) { + elog ("sent a go away message: ${r}, closing connection to ${p}",("r", reason_str(close_after_send))("p", conn->peer_name())); + my_impl->close(conn); + return; + } + } else { + fc_wlog(logger, "connection expired before enqueued net_message called callback!"); + } + }; + + //push to out queue + while (buffer_queue.out_queue_size() < OUT_QUEUE_SIZE_LIMIT){ + if (pbft_queue.empty()) break; + + queued_pbft_message pbft = pbft_queue.front(); + pbft_queue.pop_front(); + auto m = pbft.message; + if (m) { + bufs.push_back(boost::asio::buffer(*m)); + buffer_queue.add_write_queue( m, callback, true ); + } + } + } + void connection::cancel_sync(go_away_reason reason) { fc_dlog(logger,"cancel sync reason = ${m}, write queue size ${o} bytes peer ${p}", ("m",reason_str(reason)) ("o", buffer_queue.write_queue_size())("p", peer_name())); @@ -1057,9 +1184,14 @@ namespace eosio { } try { controller& cc = my_impl->chain_plug->chain(); + pbft_controller& pcc = my_impl->chain_plug->pbft_ctrl(); signed_block_ptr sb = cc.fetch_block_by_number(num); if(sb) { enqueue_block( sb, trigger_send, true); + auto scp = pcc.pbft_db.get_stable_checkpoint_by_id((*sb).id()); + if (!(scp == pbft_stable_checkpoint{})) { + enqueue(scp); + } return true; } } catch ( ... ) { @@ -1129,6 +1261,15 @@ namespace eosio { to_sync_queue); } + void connection::enqueue_pbft(const std::shared_ptr>& m, + const time_point_sec deadline = time_point_sec(static_cast(600))) + { + pbft_queue.push_back(queued_pbft_message{m, deadline }); + if (buffer_queue.is_out_queue_empty()) { + do_queue_write(); + } + } + void connection::cancel_wait() { if (response_expected) response_expected->cancel(); @@ -1205,6 +1346,14 @@ namespace eosio { sync_wait(); } + void connection::request_sync_checkpoints(uint32_t start, uint32_t end) { + fc_dlog(logger, "request sync checkpoints"); + checkpoint_request_message crm = {start,end}; + enqueue( net_message(crm)); + sync_wait(); + } + + bool connection::process_next_message(net_plugin_impl& impl, uint32_t message_length) { try { auto ds = pending_message_buffer.create_datastream(); @@ -1307,6 +1456,14 @@ namespace eosio { chain_plug->chain().fork_db_head_block_num() < sync_last_requested_num ); } + bool sync_manager::is_syncing() { + return state != in_sync; + } + + void sync_manager::set_in_sync() { + set_state(in_sync); + } + void sync_manager::request_next_chunk( const connection_ptr& conn ) { uint32_t head_block = chain_plug->chain().fork_db_head_block_num(); @@ -1834,6 +1991,21 @@ namespace eosio { } //------------------------------------------------------------------------ + std::shared_ptr> net_plugin_impl::encode_pbft_message(const net_message &msg) const { + + uint32_t payload_size = fc::raw::pack_size( msg ); + + char* header = reinterpret_cast(&payload_size); + size_t header_size = sizeof(payload_size); + size_t buffer_size = header_size + payload_size; + + auto send_buffer = std::make_shared>(buffer_size); + fc::datastream ds( send_buffer->data(), buffer_size); + ds.write( header, header_size ); + fc::raw::pack( ds, msg ); + + return send_buffer; + } void net_plugin_impl::connect(const connection_ptr& c) { if( c->no_retry != go_away_reason::no_reason) { @@ -1885,6 +2057,7 @@ namespace eosio { auto current_endpoint = *endpoint_itr; ++endpoint_itr; c->connecting = true; + c->connecting_deadline = fc::time_point::now()+fc::seconds(c->connecting_timeout_in_seconds); connection_wptr weak_conn = c; c->socket->async_connect( current_endpoint, [weak_conn, endpoint_itr, this] ( const boost::system::error_code& err ) { auto c = weak_conn.lock(); @@ -2461,6 +2634,30 @@ namespace eosio { trx->get_signatures().size() * sizeof(signature_type); } + void net_plugin_impl::handle_message( connection_ptr c, const checkpoint_request_message &msg) { + + if ( msg.end_block == 0 || msg.end_block < msg.start_block) return; + + fc_dlog(logger, "received checkpoint request message"); + vector scp_stack; + controller &cc = my_impl->chain_plug->chain(); + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); + + for (auto i = msg.end_block; i >= msg.start_block && i>0; --i) { + auto bid = cc.get_block_id_for_num(i); + auto scp = pcc.pbft_db.get_stable_checkpoint_by_id(bid); + if (!(scp == pbft_stable_checkpoint{})) { + scp_stack.push_back(scp); + } + } + fc_dlog(logger, "Sent ${n} stable checkpoints on my node",("n",scp_stack.size())); + + while (scp_stack.size()) { + c->enqueue(scp_stack.back()); + scp_stack.pop_back(); + } + } + void net_plugin_impl::handle_message(const connection_ptr& c, const packed_transaction_ptr& trx) { fc_dlog(logger, "got a packed transaction, cancel wait"); peer_ilog(c, "received packed_transaction"); @@ -2529,6 +2726,24 @@ namespace eosio { try { chain_plug->accept_block(msg); //, sync_master->is_active(c)); reason = no_reason; + auto blk = msg; + auto ext = blk->block_extensions; + auto &pcc = chain_plug->pbft_ctrl(); + + if (!ext.empty() && ext.back().first == 0) { + auto scp_v = ext.back().second; + fc::datastream ds_decode( scp_v.data(), scp_v.size()); + + pbft_stable_checkpoint scp_decode; + fc::raw::unpack(ds_decode, scp_decode); + + if (pcc.pbft_db.is_valid_stable_checkpoint(scp_decode)) { + handle_message(c, scp_decode); + } else { + //remove bad stable checkpoint from block extension + ext.pop_back(); + } + } } catch( const unlinkable_block_exception &ex) { peer_elog(c, "bad signed_block : ${m}", ("m",ex.what())); reason = unlinkable; @@ -2568,6 +2783,228 @@ namespace eosio { } } + + template + bool net_plugin_impl::is_pbft_msg_outdated(M const & msg) { + return (time_point_sec(time_point::now()) > time_point_sec(msg.timestamp) + pbft_message_TTL); + } + + template + bool net_plugin_impl::is_pbft_msg_valid(M const & msg) { + // Do some basic validations of an incoming pbft msg, bad msgs should be quickly discarded without affecting state. + return (chain_id == msg.chain_id && !is_pbft_msg_outdated(msg) && !sync_master->is_syncing()); + } + + void net_plugin_impl::bcast_pbft_msg(const net_message &msg) { + if (sync_master->is_syncing()) return; + + auto deadline = time_point_sec(time_point::now()) + pbft_message_TTL; + +// uint32_t payload_size = fc::raw::pack_size( msg ); +// +// char* header = reinterpret_cast(&payload_size); +// size_t header_size = sizeof(payload_size); +// size_t buffer_size = header_size + payload_size; +// +// auto send_buffer = std::make_shared>(buffer_size); +// fc::datastream ds( send_buffer->data(), buffer_size); +// ds.write( header, header_size ); +// fc::raw::pack( ds, msg ); + + for (auto &conn: connections) { + if (conn->pbft_ready()) { + conn->enqueue_pbft(encode_pbft_message(msg), deadline); + } + } + } + + void net_plugin_impl::forward_pbft_msg(connection_ptr c, const net_message &msg) { + auto deadline = time_point_sec(time_point::now()) + pbft_message_TTL; + +// uint32_t payload_size = fc::raw::pack_size( msg ); +// +// char* header = reinterpret_cast(&payload_size); +// size_t header_size = sizeof(payload_size); +// size_t buffer_size = header_size + payload_size; +// +// auto send_buffer = std::make_shared>(buffer_size); +// fc::datastream ds( send_buffer->data(), buffer_size); +// ds.write( header, header_size ); +// fc::raw::pack( ds, msg ); + + for (auto &conn: connections) { + if (conn != c && conn->pbft_ready()) { + conn->enqueue_pbft(encode_pbft_message(msg), deadline); + } + } + } + + void net_plugin_impl::pbft_outgoing_prepare(const pbft_prepare &msg) { + auto added = maybe_add_pbft_cache(msg.uuid); + if (!added) return; + + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); + if (!pcc.pbft_db.is_valid_prepare(msg)) return; + + bcast_pbft_msg(msg); + } + + void net_plugin_impl::pbft_outgoing_commit(const pbft_commit &msg) { + auto added = maybe_add_pbft_cache(msg.uuid); + if (!added) return; + + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); + if (!pcc.pbft_db.is_valid_commit(msg)) return; + + bcast_pbft_msg(msg); + } + + void net_plugin_impl::pbft_outgoing_view_change(const pbft_view_change &msg) { + auto added = maybe_add_pbft_cache(msg.uuid); + if (!added) return; + + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); + if (!pcc.pbft_db.is_valid_view_change(msg)) return; + + bcast_pbft_msg(msg); + } + + void net_plugin_impl::pbft_outgoing_new_view(const pbft_new_view &msg) { + auto added = maybe_add_pbft_cache(msg.uuid); + if (!added) return; + + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); + if (!pcc.pbft_db.is_valid_new_view(msg)) return; + + bcast_pbft_msg(msg); + } + + void net_plugin_impl::pbft_outgoing_checkpoint(const pbft_checkpoint &msg) { + auto added = maybe_add_pbft_cache(msg.uuid); + if (!added) return; + + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); + if (!pcc.pbft_db.is_valid_checkpoint(msg)) return; + + bcast_pbft_msg(msg); + } + + bool net_plugin_impl::maybe_add_pbft_cache(const string &uuid){ + auto itr = pbft_message_cache.find(uuid); + if (itr == pbft_message_cache.end()) { + //add to cache + pbft_message_cache[uuid] = time_point_sec(time_point::now()) + pbft_message_cache_TTL; + return true; + } + return false; + } + + void net_plugin_impl::clean_expired_pbft_cache(){ + auto itr = pbft_message_cache.begin(); + auto now = time_point::now(); + + while (itr != pbft_message_cache.end()) { + if (itr->second <= now) { + itr = pbft_message_cache.erase(itr); + } else + itr++; + } + } + + void net_plugin_impl::handle_message( connection_ptr c, const pbft_prepare &msg) { + + if (!is_pbft_msg_valid(msg)) return; + + auto added = maybe_add_pbft_cache(msg.uuid); + if (!added) return; + + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); + if (!pcc.pbft_db.is_valid_prepare(msg)) return; + + forward_pbft_msg(c, msg); + fc_ilog( logger, "received prepare at height: ${n}, view: ${v}, from ${k}, ", ("n", msg.block_num)("v", msg.view)("k", msg.public_key)); + + pbft_incoming_prepare_channel.publish(msg); + + } + + void net_plugin_impl::handle_message( connection_ptr c, const pbft_commit &msg) { + + if (!is_pbft_msg_valid(msg)) return; + + auto added = maybe_add_pbft_cache(msg.uuid); + if (!added) return; + + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); + if (!pcc.pbft_db.is_valid_commit(msg)) return; + + forward_pbft_msg(c, msg); + fc_ilog( logger, "received commit at height: ${n}, view: ${v}, from ${k}, ", ("n", msg.block_num)("v", msg.view)("k", msg.public_key)); + + pbft_incoming_commit_channel.publish(msg); + } + + void net_plugin_impl::handle_message( connection_ptr c, const pbft_view_change &msg) { + + if (!is_pbft_msg_valid(msg)) return; + + auto added = maybe_add_pbft_cache(msg.uuid); + if (!added) return; + + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); + if (!pcc.pbft_db.is_valid_view_change(msg)) return; + + forward_pbft_msg(c, msg); + fc_ilog( logger, "received view change {cv: ${cv}, tv: ${tv}} from ${v}", ("cv", msg.current_view)("tv", msg.target_view)("v", msg.public_key)); + + pbft_incoming_view_change_channel.publish(msg); + } + + void net_plugin_impl::handle_message( connection_ptr c, const pbft_new_view &msg) { + + if (!is_pbft_msg_valid(msg)) return; + + auto added = maybe_add_pbft_cache(msg.uuid); + if (!added) return; + + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); + if (!pcc.pbft_db.is_valid_new_view(msg)) return; + + forward_pbft_msg(c, msg); + fc_ilog( logger, "received new view at ${n}, from ${v}", ("n", msg)("v", msg.public_key)); + + pbft_incoming_new_view_channel.publish(msg); + } + + void net_plugin_impl::handle_message( connection_ptr c, const pbft_checkpoint &msg) { + + if (!is_pbft_msg_valid(msg)) return; + + auto added = maybe_add_pbft_cache(msg.uuid); + if (!added) return; + + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); + if (!pcc.pbft_db.is_valid_checkpoint(msg)) return; + + forward_pbft_msg(c, msg); + fc_dlog( logger, "received checkpoint at ${n}, from ${v}", ("n", msg.block_num)("v", msg.public_key)); + + pbft_incoming_checkpoint_channel.publish(msg); + } + + void net_plugin_impl::handle_message( connection_ptr c, const pbft_stable_checkpoint &msg) { + if (chain_id != msg.chain_id) return; + + pbft_controller &pcc = my_impl->chain_plug->pbft_ctrl(); + + if (pcc.pbft_db.is_valid_stable_checkpoint(msg)) { + fc_ilog(logger, "received stable checkpoint at ${n}, from ${v}", ("n", msg.block_num)("v", c->peer_name())); + for (auto cp: msg.checkpoints) { + pbft_incoming_checkpoint_channel.publish(cp); + } + } + } + void net_plugin_impl::start_conn_timer(boost::asio::steady_timer::duration du, std::weak_ptr from_connection) { connector_check->expires_from_now( du); connector_check->async_wait( [this, from_connection](boost::system::error_code ec) { @@ -2594,6 +3031,86 @@ namespace eosio { }); } + void net_plugin_impl::pbft_message_cache_ticker() { + pbft_message_cache_timer->expires_from_now (pbft_message_cache_tick_interval); + pbft_message_cache_timer->async_wait ([this](boost::system::error_code ec) { + pbft_message_cache_ticker (); + if (ec) { + wlog ("pbft message cache ticker error: ${m}", ("m", ec.message())); + } + clean_expired_pbft_cache(); + }); + } + + void net_plugin_impl::connection_monitor_ticker() { + connection_monitor_timer->expires_from_now (connection_monitor_tick_interval); + connection_monitor_timer->async_wait ([this](boost::system::error_code ec) { + connection_monitor_ticker (); + if (ec) { + wlog ("connection monitor ticker error: ${m}", ("m", ec.message())); + } + int total=0; + int current=0; + for(auto &conn: connections){ + if(conn->current()){ + ++current; + } + ++total; + auto is_open = conn->socket && conn->socket->is_open(); +// auto paddr = conn->peer_addr; +// paddr.insert(0, 20 - paddr.length(), ' '); + std::ostringstream ss; + + auto so = is_open?"1":"0"; + auto con = conn->connecting ?"1":"0"; + auto syn = conn->syncing ?"1":"0"; + auto cur = conn->current() ?"1":"0"; + ss << so << con << syn << cur ; + auto status = ss.str(); + + ss.str(""); + ss.clear(); + + ss << std::setfill(' ') << std::setw(22) << conn->peer_addr; + auto paddr = ss.str(); + + ss.str(""); + ss.clear(); + + ss << std::setfill(' ') << std::setw(6) << conn->buffer_queue.write_queue_size(); + auto write_queue = ss.str(); + + ss.str(""); + ss.clear(); + + ss << std::setfill(' ') << std::setw(6) << conn->buffer_queue.out_queue_size(); + auto out_queue = ss.str(); + + ss.str(""); + ss.clear(); + + ss << std::setfill(' ') << std::setw(6) << conn->pbft_queue.size(); + auto pbft_queue = ss.str(); + + auto conn_str = conn->peer_addr; + if(conn_str.empty()) { + try { + conn_str = boost::lexical_cast(conn->socket->remote_endpoint()); + } catch (...) { + + } + } + + dlog("connection: ${conn} \tstatus(socket|connecting|syncing|current): ${status}\t|\twrite_queue: ${write}\t|\tout_queue: ${out}\t|\tpbft_queue: ${pbft}", ("status",status)("conn",conn_str)("write",write_queue)("out",out_queue)("pbft",pbft_queue)); + } + dlog("connections stats: current : ${current}\t total : ${total} ",("current",current)("total",total)); + dlog("================================================================================================"); + auto local_trx_pool_size = local_txns.size(); + fc_dlog(logger, "local trx pool size: ${local_trx_pool_size}",("local_trx_pool_size",local_trx_pool_size)); + fc_dlog(logger, "================================================================================================"); + }); + } + void net_plugin_impl::ticker() { keepalive_timer->expires_from_now(keepalive_interval); keepalive_timer->async_wait([this](boost::system::error_code ec) { @@ -2669,6 +3186,14 @@ namespace eosio { it = connections.erase(it); continue; } + }else if((*it)->connecting && (*it)->connecting_deadline < fc::time_point::now()){ + if( (*it)->peer_addr.length() > 0) { + close(*it); + } + else { + it = connections.erase(it); + continue; + } } ++it; } @@ -2976,6 +3501,10 @@ namespace eosio { my->keepalive_timer.reset( new boost::asio::steady_timer( app().get_io_service())); my->ticker(); + my->pbft_message_cache_timer.reset( new boost::asio::steady_timer( app().get_io_service())); + my->connection_monitor_timer.reset( new boost::asio::steady_timer( app().get_io_service())); + my->pbft_message_cache_ticker(); +// my->connection_monitor_ticker(); } FC_LOG_AND_RETHROW() } @@ -3001,6 +3530,16 @@ namespace eosio { } my->incoming_transaction_ack_subscription = app().get_channel().subscribe(boost::bind(&net_plugin_impl::transaction_ack, my.get(), _1)); + my->pbft_outgoing_prepare_subscription = app().get_channel().subscribe( + boost::bind(&net_plugin_impl::pbft_outgoing_prepare, my.get(), _1)); + my->pbft_outgoing_commit_subscription = app().get_channel().subscribe( + boost::bind(&net_plugin_impl::pbft_outgoing_commit, my.get(), _1)); + my->pbft_outgoing_view_change_subscription = app().get_channel().subscribe( + boost::bind(&net_plugin_impl::pbft_outgoing_view_change, my.get(), _1)); + my->pbft_outgoing_new_view_subscription = app().get_channel().subscribe( + boost::bind(&net_plugin_impl::pbft_outgoing_new_view, my.get(), _1)); + my->pbft_outgoing_checkpoint_subscription = app().get_channel().subscribe( + boost::bind(&net_plugin_impl::pbft_outgoing_checkpoint, my.get(), _1)); if( cc.get_read_mode() == chain::db_read_mode::READ_ONLY ) { my->max_nodes_per_host = 0; @@ -3084,6 +3623,19 @@ namespace eosio { } return result; } + + bool net_plugin::is_syncing()const { + return my->sync_master->is_syncing(); + } + + net_plugin_impl::net_plugin_impl(): + pbft_incoming_prepare_channel(app().get_channel()), + pbft_incoming_commit_channel(app().get_channel()), + pbft_incoming_view_change_channel(app().get_channel()), + pbft_incoming_new_view_channel(app().get_channel()), + pbft_incoming_checkpoint_channel(app().get_channel()) + {} + connection_ptr net_plugin_impl::find_connection(const string& host )const { for( const auto& c : connections ) if( c->peer_addr == host ) return c; diff --git a/plugins/pbft_plugin/CMakeLists.txt b/plugins/pbft_plugin/CMakeLists.txt new file mode 100644 index 00000000000..9ca17f811f9 --- /dev/null +++ b/plugins/pbft_plugin/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB HEADERS "include/eosio/pbft_plugin/*.hpp") +add_library( pbft_plugin + pbft_plugin.cpp + ${HEADERS} ) + +target_link_libraries( pbft_plugin appbase fc eosio_chain chain_plugin net_plugin) +target_include_directories( pbft_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../chain_interface/include") diff --git a/plugins/pbft_plugin/include/eosio/pbft_plugin/pbft_plugin.hpp b/plugins/pbft_plugin/include/eosio/pbft_plugin/pbft_plugin.hpp new file mode 100644 index 00000000000..d94c74785e3 --- /dev/null +++ b/plugins/pbft_plugin/include/eosio/pbft_plugin/pbft_plugin.hpp @@ -0,0 +1,28 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once +#include + +namespace eosio { + +using namespace appbase; + +class pbft_plugin : public appbase::plugin { +public: + pbft_plugin(); + virtual ~pbft_plugin(); + + APPBASE_PLUGIN_REQUIRES() + virtual void set_program_options(options_description&, options_description& cfg) override; + + void plugin_initialize(const variables_map& options); + void plugin_startup(); + void plugin_shutdown(); + +private: + std::unique_ptr my; +}; + +} diff --git a/plugins/pbft_plugin/pbft_plugin.cpp b/plugins/pbft_plugin/pbft_plugin.cpp new file mode 100644 index 00000000000..51bed775c95 --- /dev/null +++ b/plugins/pbft_plugin/pbft_plugin.cpp @@ -0,0 +1,134 @@ +#include + +#include +#include +#include +#include +#include + +namespace eosio { + static appbase::abstract_plugin &_pbft_plugin = app().register_plugin(); + using namespace std; + using namespace eosio::chain; + + class pbft_plugin_impl { + public: + unique_ptr prepare_timer; + unique_ptr commit_timer; + unique_ptr view_change_timer; + unique_ptr checkpoint_timer; + + boost::asio::steady_timer::duration prepare_timeout{std::chrono::milliseconds{1000}}; + boost::asio::steady_timer::duration commit_timeout{std::chrono::milliseconds{1000}}; + boost::asio::steady_timer::duration view_change_timeout{std::chrono::seconds{5}}; + boost::asio::steady_timer::duration checkpoint_timeout{std::chrono::seconds{50}}; + + void prepare_timer_tick(); + + void commit_timer_tick(); + + void view_change_timer_tick(); + + void checkpoint_timer_tick(); + + private: + bool is_replaying(); + bool is_syncing(); + bool pbft_ready(); + }; + + pbft_plugin::pbft_plugin() : my(new pbft_plugin_impl()) {} + + pbft_plugin::~pbft_plugin() = default; + + void pbft_plugin::set_program_options(options_description &, options_description &cfg) { + } + + void pbft_plugin::plugin_initialize(const variables_map &options) { + ilog("Initialize pbft plugin"); + my->prepare_timer = std::make_unique(app().get_io_service()); + my->commit_timer = std::make_unique(app().get_io_service()); + my->view_change_timer = std::make_unique(app().get_io_service()); + my->checkpoint_timer = std::make_unique(app().get_io_service()); + } + + void pbft_plugin::plugin_startup() { + my->prepare_timer_tick(); + my->commit_timer_tick(); + my->view_change_timer_tick(); + my->checkpoint_timer_tick(); + } + + void pbft_plugin::plugin_shutdown() { + } + + void pbft_plugin_impl::prepare_timer_tick() { + chain::pbft_controller &pbft_ctrl = app().get_plugin().pbft_ctrl(); + prepare_timer->expires_from_now(prepare_timeout); + prepare_timer->async_wait([&](boost::system::error_code ec) { + prepare_timer_tick(); + if (ec) { + wlog ("pbft plugin prepare timer tick error: ${m}", ("m", ec.message())); + } else { + if (pbft_ready()) pbft_ctrl.maybe_pbft_prepare(); + } + }); + } + + void pbft_plugin_impl::commit_timer_tick() { + chain::pbft_controller &pbft_ctrl = app().get_plugin().pbft_ctrl(); + commit_timer->expires_from_now(commit_timeout); + commit_timer->async_wait([&](boost::system::error_code ec) { + commit_timer_tick(); + if (ec) { + wlog ("pbft plugin commit timer tick error: ${m}", ("m", ec.message())); + } else { + if (pbft_ready()) pbft_ctrl.maybe_pbft_commit(); + } + }); + } + + void pbft_plugin_impl::view_change_timer_tick() { + chain::pbft_controller &pbft_ctrl = app().get_plugin().pbft_ctrl(); + try { + view_change_timer->cancel(); + } catch (boost::system::system_error &e) { + elog("view change timer cancel error: ${e}", ("e", e.what())); + } + view_change_timer->expires_from_now(view_change_timeout); + view_change_timer->async_wait([&](boost::system::error_code ec) { + view_change_timer_tick(); + if (ec) { + wlog ("pbft plugin view change timer tick error: ${m}", ("m", ec.message())); + } else { + if (pbft_ready()) pbft_ctrl.maybe_pbft_view_change(); + } + }); + } + + void pbft_plugin_impl::checkpoint_timer_tick() { + chain::pbft_controller &pbft_ctrl = app().get_plugin().pbft_ctrl(); + checkpoint_timer->expires_from_now(checkpoint_timeout); + checkpoint_timer->async_wait([&](boost::system::error_code ec) { + checkpoint_timer_tick(); + if (ec) { + wlog ("pbft plugin checkpoint timer tick error: ${m}", ("m", ec.message())); + } else { + if (pbft_ready()) pbft_ctrl.send_pbft_checkpoint(); + } + }); + } + + bool pbft_plugin_impl::is_replaying() { + return app().get_plugin().chain().is_replaying(); + } + + bool pbft_plugin_impl::is_syncing() { + return app().get_plugin().is_syncing(); + } + + bool pbft_plugin_impl::pbft_ready() { + // only trigger pbft related logic if I am in sync and replayed. + return (!is_syncing() && !is_replaying()); + } +} diff --git a/plugins/producer_api_plugin/producer_api_plugin.cpp b/plugins/producer_api_plugin/producer_api_plugin.cpp index 7fcde1ac98c..62f4664ed82 100644 --- a/plugins/producer_api_plugin/producer_api_plugin.cpp +++ b/plugins/producer_api_plugin/producer_api_plugin.cpp @@ -90,6 +90,8 @@ void producer_api_plugin::plugin_startup() { INVOKE_R_V(producer, get_integrity_hash), 201), CALL(producer, producer, create_snapshot, INVOKE_R_V(producer, create_snapshot), 201), + CALL(producer, producer, set_pbft_current_view, + INVOKE_V_R(producer, set_pbft_current_view, uint32_t), 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 c43f0e0f38b..f6d10eb277b 100644 --- a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp +++ b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp @@ -82,6 +82,8 @@ class producer_plugin : public appbase::plugin { integrity_hash_information get_integrity_hash() const; snapshot_information create_snapshot() const; + void set_pbft_current_view(const uint32_t view); + signal confirmed_block; private: std::shared_ptr my; diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 947ef48f46a..d06390d6dfc 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -339,10 +339,10 @@ class producer_plugin_impl : public std::enable_shared_from_thistimestamp < fc::minutes(5) || (block->block_num() % 1000 == 0) ) { - ilog("Received block ${id}... #${n} @ ${t} signed by ${p} [trxs: ${count}, lib: ${lib}, conf: ${confs}, latency: ${latency} ms]", + ilog("Received block ${id}... #${n} @ ${t} signed by ${p} [trxs: ${count}, lib: ${lib}, lscb: ${lscb}, latency: ${latency} ms]", ("p",block->producer)("id",fc::variant(block->id()).as_string().substr(8,16)) ("n",block_header::num_from_id(block->id()))("t",block->timestamp) - ("count",block->transactions.size())("lib",chain.last_irreversible_block_num())("confs", block->confirmed)("latency", (fc::time_point::now() - block->timestamp).count()/1000 ) ); + ("count",block->transactions.size())("lib",chain.last_irreversible_block_num())("lscb", chain.last_stable_checkpoint_block_num())("latency", (fc::time_point::now() - block->timestamp).count()/1000 ) ); } } @@ -964,6 +964,11 @@ producer_plugin::snapshot_information producer_plugin::create_snapshot() const { return {head_id, snapshot_path}; } +void producer_plugin::set_pbft_current_view(const uint32_t view) { + pbft_controller& pbft_ctrl = app().get_plugin().pbft_ctrl(); + pbft_ctrl.state_machine.manually_set_current_view(view); +} + 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(); @@ -1028,7 +1033,7 @@ fc::time_point producer_plugin_impl::calculate_pending_block_time() const { fc::time_point block_time = base + fc::microseconds(min_time_to_next_block); - if((block_time - now) < fc::microseconds(config::block_interval_us/10) ) { // we must sleep for at least 50ms + if((block_time - now) < fc::microseconds(config::block_interval_us/5) ) { // we must sleep for at least 50ms block_time += fc::microseconds(config::block_interval_us); } return block_time; @@ -1578,11 +1583,10 @@ void producer_plugin_impl::produce_block() { block_state_ptr new_bs = chain.head_block_state(); _producer_watermarks[new_bs->header.producer] = chain.head_block_num(); - ilog("Produced block ${id}... #${n} @ ${t} signed by ${p} [trxs: ${count}, lib: ${lib}, confirmed: ${confs}]", + ilog("Produced block ${id}... #${n} @ ${t} signed by ${p} [trxs: ${count}, lib: ${lib}, lscb: ${lscb}]", ("p",new_bs->header.producer)("id",fc::variant(new_bs->id).as_string().substr(0,16)) ("n",new_bs->block_num)("t",new_bs->header.timestamp) - ("count",new_bs->block->transactions.size())("lib",chain.last_irreversible_block_num())("confs", new_bs->header.confirmed)); - + ("count",new_bs->block->transactions.size())("lib",chain.last_irreversible_block_num())("lscb", chain.last_stable_checkpoint_block_num())); } } // namespace eosio diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 6bddeacb848..70e9553e9e7 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -57,6 +57,7 @@ target_link_libraries( ${NODE_EXECUTABLE_NAME} PRIVATE -Wl,${whole_archive_flag} chain_api_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} net_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} net_api_plugin -Wl,${no_whole_archive_flag} + PRIVATE -Wl,${whole_archive_flag} pbft_plugin -Wl,${no_whole_archive_flag} # PRIVATE -Wl,${whole_archive_flag} faucet_testnet_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} txn_test_gen_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} db_size_api_plugin -Wl,${no_whole_archive_flag} diff --git a/tests/chain_plugin_tests.cpp b/tests/chain_plugin_tests.cpp index 5a489c255b4..9d94e384708 100644 --- a/tests/chain_plugin_tests.cpp +++ b/tests/chain_plugin_tests.cpp @@ -90,7 +90,7 @@ BOOST_FIXTURE_TEST_CASE( get_block_with_invalid_abi, TESTER ) try { char headnumstr[20]; sprintf(headnumstr, "%d", headnum); chain_apis::read_only::get_block_params param{headnumstr}; - chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX)); + chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX), *(this->pbft_ctrl)); // block should be decoded successfully std::string block_str = json::to_pretty_string(plugin.get_block(param)); diff --git a/tests/get_table_tests.cpp b/tests/get_table_tests.cpp index bb332b9a000..8084ddd2f93 100644 --- a/tests/get_table_tests.cpp +++ b/tests/get_table_tests.cpp @@ -72,7 +72,7 @@ BOOST_FIXTURE_TEST_CASE( get_scope_test, TESTER ) try { produce_blocks(1); // iterate over scope - eosio::chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX)); + eosio::chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX), *(this->pbft_ctrl)); eosio::chain_apis::read_only::get_table_by_scope_params param{N(eosio.token), N(accounts), "inita", "", 10}; eosio::chain_apis::read_only::get_table_by_scope_result result = plugin.read_only::get_table_by_scope(param); @@ -193,7 +193,7 @@ BOOST_FIXTURE_TEST_CASE( get_table_test, TESTER ) try { produce_blocks(1); // get table: normal case - eosio::chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX)); + eosio::chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX), *(this->pbft_ctrl)); eosio::chain_apis::read_only::get_table_rows_params p; p.code = N(eosio.token); p.scope = "inita"; @@ -361,7 +361,7 @@ BOOST_FIXTURE_TEST_CASE( get_table_by_seckey_test, TESTER ) try { produce_blocks(1); // get table: normal case - eosio::chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX)); + eosio::chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX), *(this->pbft_ctrl)); eosio::chain_apis::read_only::get_table_rows_params p; p.code = N(eosio); p.scope = "eosio"; diff --git a/unittests/pbft_tests.cpp b/unittests/pbft_tests.cpp new file mode 100644 index 00000000000..55c2049bf3a --- /dev/null +++ b/unittests/pbft_tests.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +using namespace eosio::chain; +using namespace eosio::testing; + + +BOOST_AUTO_TEST_SUITE(pbft_tests) + + BOOST_AUTO_TEST_CASE(can_init) { + tester tester; + controller &ctrl = *tester.control.get(); + pbft_controller pbft_ctrl{ctrl}; + + tester.produce_block(); + auto p = pbft_ctrl.pbft_db.should_prepared(); + BOOST_CHECK(!p); + } + + BOOST_AUTO_TEST_CASE(can_raise_lib) { + tester tester; + controller &ctrl = *tester.control.get(); + pbft_controller pbft_ctrl{ctrl}; + + auto privkey = tester::get_private_key( N(eosio), "active" ); + auto pubkey = tester::get_public_key( N(eosio), "active"); + auto sp = [privkey]( const eosio::chain::digest_type& digest ) { + return privkey.sign(digest); + }; + std::map msp; + msp[pubkey]=sp; + ctrl.set_my_signature_providers(msp); + + tester.produce_block();//produce block num 2 + BOOST_REQUIRE_EQUAL(ctrl.last_irreversible_block_num(), 0); + BOOST_REQUIRE_EQUAL(ctrl.head_block_num(), 2); + + pbft_ctrl.maybe_pbft_prepare(); + pbft_ctrl.maybe_pbft_commit(); + BOOST_REQUIRE_EQUAL(ctrl.pending_pbft_lib(), true); + tester.produce_block();//produce block num 3 + BOOST_REQUIRE_EQUAL(ctrl.pending_pbft_lib(), false); + BOOST_REQUIRE_EQUAL(ctrl.last_irreversible_block_num(), 2); + BOOST_REQUIRE_EQUAL(ctrl.head_block_num(), 3); + } + + + +BOOST_AUTO_TEST_SUITE_END() From 7bda6a3093fe6b0fb1ede4ed29085367740eeca7 Mon Sep 17 00:00:00 2001 From: oldcold Date: Wed, 20 Mar 2019 16:04:17 +0800 Subject: [PATCH 02/59] fix merge error in get_table_tests --- tests/get_table_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/get_table_tests.cpp b/tests/get_table_tests.cpp index 8eec7a05d5c..8084ddd2f93 100644 --- a/tests/get_table_tests.cpp +++ b/tests/get_table_tests.cpp @@ -193,7 +193,7 @@ BOOST_FIXTURE_TEST_CASE( get_table_test, TESTER ) try { produce_blocks(1); // get table: normal case - eosio::chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX)); + eosio::chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX), *(this->pbft_ctrl)); eosio::chain_apis::read_only::get_table_rows_params p; p.code = N(eosio.token); p.scope = "inita"; @@ -361,7 +361,7 @@ BOOST_FIXTURE_TEST_CASE( get_table_by_seckey_test, TESTER ) try { produce_blocks(1); // get table: normal case - eosio::chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX)); + eosio::chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX), *(this->pbft_ctrl)); eosio::chain_apis::read_only::get_table_rows_params p; p.code = N(eosio); p.scope = "eosio"; From 22acbf9b34dce4719281251dfcb49ada7c3b6a6e Mon Sep 17 00:00:00 2001 From: deadlock Date: Tue, 5 Mar 2019 16:01:08 +0800 Subject: [PATCH 03/59] txn plugin now use system newaccount to create account and support custom core_symbol --- plugins/txn_test_gen_plugin/README.md | 2 +- .../txn_test_gen_plugin.cpp | 106 ++++++++++++++---- 2 files changed, 84 insertions(+), 24 deletions(-) diff --git a/plugins/txn_test_gen_plugin/README.md b/plugins/txn_test_gen_plugin/README.md index 8d74e6a0412..3547a342eda 100644 --- a/plugins/txn_test_gen_plugin/README.md +++ b/plugins/txn_test_gen_plugin/README.md @@ -68,7 +68,7 @@ $ ./cleos set contract eosio ~/eos/build.release/contracts/eosio.bios/ ### Initialize the accounts txn_test_gen_plugin uses ```bash -$ curl --data-binary '["eosio", "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"]' http://127.0.0.1:8888/v1/txn_test_gen/create_test_accounts +$ curl --data-binary '["eosio", "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3", "EOS"]' http://127.0.0.1:8888/v1/txn_test_gen/create_test_accounts ``` ### Start transaction generation, this will submit 20 transactions evey 20ms (total of 1000TPS) diff --git a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp index 707f75bd9de..8695306ec13 100644 --- a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp +++ b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp @@ -24,6 +24,8 @@ #include #include +#include +#include namespace eosio { namespace detail { struct txn_test_gen_empty {}; @@ -82,9 +84,9 @@ using namespace eosio::chain; }\ } -#define INVOKE_ASYNC_R_R(api_handle, call_name, in_param0, in_param1) \ +#define INVOKE_ASYNC_R_R(api_handle, call_name, in_param0, in_param1, in_param2) \ const auto& vs = fc::json::json::from_string(body).as(); \ - api_handle->call_name(vs.at(0).as(), vs.at(1).as(), result_handler); + api_handle->call_name(vs.at(0).as(), vs.at(1).as(), vs.at(2).as(), result_handler); struct txn_test_gen_plugin_impl { @@ -126,14 +128,16 @@ struct txn_test_gen_plugin_impl { push_next_transaction(trxs_copy, 0, next); } - void create_test_accounts(const std::string& init_name, const std::string& init_priv_key, const std::function& next) { + void create_test_accounts(const std::string& init_name, const std::string& init_priv_key, + const std::string& core_symbol, + const std::function& next) { std::vector trxs; trxs.reserve(2); try { - name newaccountA("txn.test.a"); - name newaccountB("txn.test.b"); - name newaccountC("txn.test.t"); + name newaccountA("aaaaaaaaaaaa"); + name newaccountB("bbbbbbbbbbbb"); + name newaccountC("cccccccccccc"); name creator(init_name); abi_def currency_abi_def = fc::json::from_string(eosio_token_abi).as(); @@ -152,6 +156,10 @@ struct txn_test_gen_plugin_impl { fc::crypto::public_key txn_text_receiver_C_pub_key = txn_test_receiver_C_priv_key.get_public_key(); fc::crypto::private_key creator_priv_key = fc::crypto::private_key(init_priv_key); + eosio::chain::asset net{1000000, symbol(4,core_symbol.c_str())}; + eosio::chain::asset cpu{1000000, symbol(4,core_symbol.c_str())}; + eosio::chain::asset ram{1000000, symbol(4,core_symbol.c_str())}; + //create some test accounts { signed_transaction trx; @@ -162,6 +170,14 @@ struct txn_test_gen_plugin_impl { auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_A_pub_key, 1}}, {}}; trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountA, owner_auth, active_auth}); + + //delegate cpu net and buyram + auto act_delegatebw = create_action_delegatebw(creator, newaccountA,net,cpu,abi_serializer_max_time); + auto act_buyram = create_action_buyram(creator, newaccountA, ram, abi_serializer_max_time); + + trx.actions.emplace_back(act_delegatebw); + trx.actions.emplace_back(act_buyram); + } //create "B" account { @@ -169,13 +185,27 @@ struct txn_test_gen_plugin_impl { auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_B_pub_key, 1}}, {}}; trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountB, owner_auth, active_auth}); + + //delegate cpu net and buyram + auto act_delegatebw = create_action_delegatebw(creator, newaccountB,net,cpu,abi_serializer_max_time); + auto act_buyram = create_action_buyram(creator, newaccountB, ram, abi_serializer_max_time); + + trx.actions.emplace_back(act_delegatebw); + trx.actions.emplace_back(act_buyram); } - //create "txn.test.t" account + //create "cccccccccccc" account { auto owner_auth = eosio::chain::authority{1, {{txn_text_receiver_C_pub_key, 1}}, {}}; auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_C_pub_key, 1}}, {}}; trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountC, owner_auth, active_auth}); + + //delegate cpu net and buyram + auto act_delegatebw = create_action_delegatebw(creator, newaccountC,net,cpu,abi_serializer_max_time); + auto act_buyram = create_action_buyram(creator, newaccountC, ram, abi_serializer_max_time); + + trx.actions.emplace_back(act_delegatebw); + trx.actions.emplace_back(act_buyram); } trx.expiration = cc.head_block_time() + fc::seconds(30); @@ -184,7 +214,7 @@ struct txn_test_gen_plugin_impl { trxs.emplace_back(std::move(trx)); } - //set txn.test.t contract to eosio.token & initialize it + //set cccccccccccc contract to eosio.token & initialize it { signed_transaction trx; @@ -205,34 +235,34 @@ struct txn_test_gen_plugin_impl { { action act; - act.account = N(txn.test.t); + act.account = N(cccccccccccc); act.name = N(create); act.authorization = vector{{newaccountC,config::active_name}}; - act.data = eosio_token_serializer.variant_to_binary("create", fc::json::from_string("{\"issuer\":\"txn.test.t\",\"maximum_supply\":\"1000000000.0000 CUR\"}}"), abi_serializer_max_time); + act.data = eosio_token_serializer.variant_to_binary("create", fc::json::from_string("{\"issuer\":\"cccccccccccc\",\"maximum_supply\":\"1000000000.0000 CUR\"}}"), abi_serializer_max_time); trx.actions.push_back(act); } { action act; - act.account = N(txn.test.t); + act.account = N(cccccccccccc); act.name = N(issue); act.authorization = vector{{newaccountC,config::active_name}}; - act.data = eosio_token_serializer.variant_to_binary("issue", fc::json::from_string("{\"to\":\"txn.test.t\",\"quantity\":\"600.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); + act.data = eosio_token_serializer.variant_to_binary("issue", fc::json::from_string("{\"to\":\"cccccccccccc\",\"quantity\":\"600.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); trx.actions.push_back(act); } { action act; - act.account = N(txn.test.t); + act.account = N(cccccccccccc); act.name = N(transfer); act.authorization = vector{{newaccountC,config::active_name}}; - act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"txn.test.t\",\"to\":\"txn.test.a\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); + act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"cccccccccccc\",\"to\":\"aaaaaaaaaaaa\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); trx.actions.push_back(act); } { action act; - act.account = N(txn.test.t); + act.account = N(cccccccccccc); act.name = N(transfer); act.authorization = vector{{newaccountC,config::active_name}}; - act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"txn.test.t\",\"to\":\"txn.test.b\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); + act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"cccccccccccc\",\"to\":\"bbbbbbbbbbbb\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); trx.actions.push_back(act); } @@ -250,6 +280,36 @@ struct txn_test_gen_plugin_impl { push_transactions(std::move(trxs), next); } + eosio::chain::action create_action_delegatebw(const name &from, const name &to, const asset &net, const asset &cpu, const fc::microseconds &abi_serializer_max_time){ + fc::variant variant_delegate = fc::mutable_variant_object() + ("from", from.to_string()) + ("receiver", to.to_string()) + ("stake_net_quantity", net.to_string()) + ("stake_cpu_quantity", cpu.to_string()) + ("transfer", true); + abi_serializer eosio_system_serializer{fc::json::from_string(eosio_system_abi).as(), abi_serializer_max_time}; + + auto payload_delegate = eosio_system_serializer.variant_to_binary( "delegatebw", variant_delegate, abi_serializer_max_time); + eosio::chain::action act_delegate{vector{{from,"active"}}, + config::system_account_name, N(delegatebw), payload_delegate}; + + return act_delegate; + } + + eosio::chain::action create_action_buyram(const name &from, const name &to, const asset &quant, const fc::microseconds &abi_serializer_max_time){ + fc::variant variant_buyram = fc::mutable_variant_object() + ("payer", from.to_string()) + ("receiver", to.to_string()) + ("quant", quant.to_string()); + abi_serializer eosio_system_serializer{fc::json::from_string(eosio_system_abi).as(), abi_serializer_max_time}; + + auto payload_buyram = eosio_system_serializer.variant_to_binary( "buyram", variant_buyram, abi_serializer_max_time); + eosio::chain::action act_buyram{vector{{from,"active"}}, + config::system_account_name, N(buyram), payload_buyram}; + + return act_buyram; + } + void start_generation(const std::string& salt, const uint64_t& period, const uint64_t& batch_size) { if(running) throw fc::exception(fc::invalid_operation_exception_code); @@ -266,19 +326,19 @@ struct txn_test_gen_plugin_impl { auto abi_serializer_max_time = app().get_plugin().get_abi_serializer_max_time(); abi_serializer eosio_token_serializer{fc::json::from_string(eosio_token_abi).as(), abi_serializer_max_time}; //create the actions here - act_a_to_b.account = N(txn.test.t); + act_a_to_b.account = N(cccccccccccc); act_a_to_b.name = N(transfer); - act_a_to_b.authorization = vector{{name("txn.test.a"),config::active_name}}; + act_a_to_b.authorization = vector{{name("aaaaaaaaaaaa"),config::active_name}}; act_a_to_b.data = eosio_token_serializer.variant_to_binary("transfer", - fc::json::from_string(fc::format_string("{\"from\":\"txn.test.a\",\"to\":\"txn.test.b\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", + fc::json::from_string(fc::format_string("{\"from\":\"aaaaaaaaaaaa\",\"to\":\"bbbbbbbbbbbb\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", fc::mutable_variant_object()("l", salt))), abi_serializer_max_time); - act_b_to_a.account = N(txn.test.t); + act_b_to_a.account = N(cccccccccccc); act_b_to_a.name = N(transfer); - act_b_to_a.authorization = vector{{name("txn.test.b"),config::active_name}}; + act_b_to_a.authorization = vector{{name("bbbbbbbbbbbb"),config::active_name}}; act_b_to_a.data = eosio_token_serializer.variant_to_binary("transfer", - fc::json::from_string(fc::format_string("{\"from\":\"txn.test.b\",\"to\":\"txn.test.a\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", + fc::json::from_string(fc::format_string("{\"from\":\"bbbbbbbbbbbb\",\"to\":\"aaaaaaaaaaaa\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", fc::mutable_variant_object()("l", salt))), abi_serializer_max_time); @@ -405,7 +465,7 @@ void txn_test_gen_plugin::plugin_initialize(const variables_map& options) { void txn_test_gen_plugin::plugin_startup() { app().get_plugin().add_api({ - CALL_ASYNC(txn_test_gen, my, create_test_accounts, INVOKE_ASYNC_R_R(my, create_test_accounts, std::string, std::string), 200), + CALL_ASYNC(txn_test_gen, my, create_test_accounts, INVOKE_ASYNC_R_R(my, create_test_accounts, std::string, std::string, std::string), 200), CALL(txn_test_gen, my, stop_generation, INVOKE_V_V(my, stop_generation), 200), CALL(txn_test_gen, my, start_generation, INVOKE_V_R_R_R(my, start_generation, std::string, uint64_t, uint64_t), 200) }); From a1ee007541436703377687aaaa528c8ff324f244 Mon Sep 17 00:00:00 2001 From: deadlock Date: Thu, 21 Mar 2019 11:41:50 +0800 Subject: [PATCH 04/59] don't stop if push transaction failed --- plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp index 8695306ec13..c6864fdd978 100644 --- a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp +++ b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp @@ -359,7 +359,7 @@ struct txn_test_gen_plugin_impl { send_transaction([this](const fc::exception_ptr& e){ if (e) { elog("pushing transaction failed: ${e}", ("e", e->to_detail_string())); - stop_generation(); + arm_timer(timer.expires_at()); } else { arm_timer(timer.expires_at()); } From cb89b32b74f33739cb15760fcebf02d030dc20f9 Mon Sep 17 00:00:00 2001 From: deadlock Date: Fri, 22 Mar 2019 15:09:19 +0800 Subject: [PATCH 05/59] don't stop if push transaction failed disconnect all conn then re-connect --- plugins/txn_test_gen_plugin/CMakeLists.txt | 2 +- .../txn_test_gen_plugin.cpp | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/plugins/txn_test_gen_plugin/CMakeLists.txt b/plugins/txn_test_gen_plugin/CMakeLists.txt index e765f3478e6..286066d6149 100644 --- a/plugins/txn_test_gen_plugin/CMakeLists.txt +++ b/plugins/txn_test_gen_plugin/CMakeLists.txt @@ -5,6 +5,6 @@ add_library( txn_test_gen_plugin add_dependencies(txn_test_gen_plugin eosio.token) -target_link_libraries( txn_test_gen_plugin appbase fc http_plugin chain_plugin ) +target_link_libraries( txn_test_gen_plugin appbase fc http_plugin chain_plugin net_plugin) target_include_directories( txn_test_gen_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) target_include_directories( txn_test_gen_plugin PUBLIC ${CMAKE_BINARY_DIR}/contracts ) diff --git a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp index c6864fdd978..436be297d16 100644 --- a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp +++ b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp @@ -4,6 +4,7 @@ */ #include #include +#include #include #include @@ -95,6 +96,10 @@ struct txn_test_gen_plugin_impl { int _remain = 0; + std::string cached_salt; + uint64_t cached_period; + uint64_t cached_batch_size; + void push_next_transaction(const std::shared_ptr>& trxs, size_t index, const std::function& next ) { chain_plugin& cp = app().get_plugin(); @@ -321,6 +326,9 @@ struct txn_test_gen_plugin_impl { throw fc::exception(fc::invalid_operation_exception_code); running = true; + cached_salt = salt; + cached_period = period; + cached_batch_size = batch_size; controller& cc = app().get_plugin().chain(); auto abi_serializer_max_time = app().get_plugin().get_abi_serializer_max_time(); @@ -359,7 +367,15 @@ struct txn_test_gen_plugin_impl { send_transaction([this](const fc::exception_ptr& e){ if (e) { elog("pushing transaction failed: ${e}", ("e", e->to_detail_string())); - arm_timer(timer.expires_at()); + stop_generation(); + auto peers_conn = app().get_plugin().connections(); + for(const auto c : peers_conn){ + app().get_plugin().disconnect(c.peer); + } + for(const auto c : peers_conn){ + app().get_plugin().connect(c.peer); + } + start_generation(cached_salt,cached_period,cached_batch_size); } else { arm_timer(timer.expires_at()); } From 037dbb6514312fbf7ed115861ae6a970c910f1e9 Mon Sep 17 00:00:00 2001 From: oldcold Date: Mon, 25 Mar 2019 15:20:02 +0800 Subject: [PATCH 06/59] bug fix: cannot find block, use block state ptr to mark pbft prepare (my prepare) fork. --- libraries/chain/controller.cpp | 58 +++++++++++++------ libraries/chain/fork_database.cpp | 12 ++-- .../include/eosio/chain/fork_database.hpp | 4 +- libraries/chain/pbft_database.cpp | 4 +- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index edd04c4d45c..2b71da614b7 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -126,8 +126,8 @@ struct controller_impl { optional pending_pbft_checkpoint; optional last_proposed_schedule_block_num; optional last_promoted_proposed_schedule_block_num; - optional pbft_prepared; - optional my_prepare; + block_state_ptr pbft_prepared; + block_state_ptr my_prepare; block_state_ptr head; fork_database fork_db; wasm_interface wasmif; @@ -790,11 +790,19 @@ struct controller_impl { pending->_pending_block_state->validated = true; auto new_bsp = fork_db.add(pending->_pending_block_state, true); emit(self.accepted_block_header, pending->_pending_block_state); - fork_db.mark_pbft_prepared_fork(head->id); - fork_db.mark_pbft_my_prepare_fork(head->id); - if (pbft_prepared) fork_db.mark_pbft_prepared_fork(*pbft_prepared); - if (my_prepare) fork_db.mark_pbft_my_prepare_fork(*my_prepare); + if (pbft_prepared) { + fork_db.mark_pbft_prepared_fork(pbft_prepared); + } else if (head) { + fork_db.mark_pbft_prepared_fork(head); + } + + if (my_prepare) { + fork_db.mark_pbft_my_prepare_fork(my_prepare); + } else if (head) { + fork_db.mark_pbft_my_prepare_fork(head); + } + head = fork_db.head(); EOS_ASSERT(new_bsp == head, fork_database_exception, "committed block did not become the new head in fork database"); } @@ -1421,6 +1429,7 @@ struct controller_impl { emit( self.pre_accepted_block, b ); auto current_head = head->id; + auto new_head = new_header_state->id; fork_db.add( new_header_state, false ); @@ -1430,14 +1439,13 @@ struct controller_impl { emit( self.accepted_block_header, new_header_state ); set_pbft_lib(); - set_pbft_lscb(); - fork_db.mark_pbft_my_prepare_fork(current_head); - fork_db.mark_pbft_prepared_fork(current_head); if ( read_mode != db_read_mode::IRREVERSIBLE ) { maybe_switch_forks( s ); } + set_pbft_lscb(); + } FC_LOG_AND_RETHROW( ) } @@ -1510,8 +1518,18 @@ struct controller_impl { void maybe_switch_forks( controller::block_status s ) { - if (pbft_prepared) fork_db.mark_pbft_prepared_fork(*pbft_prepared); - if (my_prepare) fork_db.mark_pbft_my_prepare_fork(*my_prepare); + if (pbft_prepared) { + fork_db.mark_pbft_prepared_fork(pbft_prepared); + } else if (head) { + fork_db.mark_pbft_prepared_fork(head); + } + + if (my_prepare) { + fork_db.mark_pbft_my_prepare_fork(my_prepare); + } else if (head) { + fork_db.mark_pbft_my_prepare_fork(head); + } + auto new_head = fork_db.head(); if( new_head->header.previous == head->id ) { @@ -2353,27 +2371,33 @@ chain_id_type controller::get_chain_id()const { void controller::set_pbft_prepared(const block_id_type& id) const { my->pbft_prepared.reset(); - my->pbft_prepared.emplace(id); - my->fork_db.mark_pbft_prepared_fork(id); + auto bs = fetch_block_state_by_id(id); + if (bs) { + my->pbft_prepared = bs; + my->fork_db.mark_pbft_prepared_fork(bs); + } dlog( "fork_db head ${h}", ("h", fork_db().head()->id)); dlog( "prepared block id ${b}", ("b", id)); } void controller::set_pbft_my_prepare(const block_id_type& id) const { my->my_prepare.reset(); - my->my_prepare.emplace(id); - my->fork_db.mark_pbft_my_prepare_fork(id); + auto bs = fetch_block_state_by_id(id); + if (bs) { + my->my_prepare = bs; + my->fork_db.mark_pbft_my_prepare_fork(bs); + } dlog( "fork_db head ${h}", ("h", fork_db().head()->id)); dlog( "my prepare block id ${b}", ("b", id)); } block_id_type controller::get_pbft_my_prepare() const { - if (my->my_prepare) return *my->my_prepare; + if (my->my_prepare) return my->my_prepare->id; return block_id_type{}; } void controller::reset_pbft_my_prepare() const { - if (my->my_prepare) my->fork_db.remove_pbft_my_prepare_fork(*my->my_prepare); + if (my->my_prepare) my->fork_db.remove_pbft_my_prepare_fork(my->my_prepare->id); my->my_prepare.reset(); } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index d49f3af2828..a80d54f782a 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -286,9 +286,9 @@ namespace eosio { namespace chain { return block_state_ptr(); } - void fork_database::mark_pbft_prepared_fork(const block_id_type &id) const { + void fork_database::mark_pbft_prepared_fork(const block_state_ptr& h) const { auto& by_id_idx = my->index.get(); - auto itr = by_id_idx.find( id ); + auto itr = by_id_idx.find( h->id ); EOS_ASSERT( itr != by_id_idx.end(), fork_db_block_not_found, "could not find block in fork database" ); by_id_idx.modify( itr, [&]( auto& bsp ) { bsp->pbft_prepared = true; }); @@ -310,16 +310,16 @@ namespace eosio { namespace chain { return updated; }; - vector queue{id}; + vector queue{ h->id }; while(!queue.empty()) { queue = update( queue ); } my->head = *my->index.get().begin(); } - void fork_database::mark_pbft_my_prepare_fork(const block_id_type &id) const { + void fork_database::mark_pbft_my_prepare_fork(const block_state_ptr& h) const { auto& by_id_idx = my->index.get(); - auto itr = by_id_idx.find( id ); + auto itr = by_id_idx.find( h->id ); EOS_ASSERT( itr != by_id_idx.end(), fork_db_block_not_found, "could not find block in fork database" ); by_id_idx.modify( itr, [&]( auto& bsp ) { bsp->pbft_my_prepare = true; }); @@ -341,7 +341,7 @@ namespace eosio { namespace chain { return updated; }; - vector queue{id}; + vector queue{ h->id }; while(!queue.empty()) { queue = update( queue ); } diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 5cac3e3c024..99e021b2e4b 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -74,9 +74,9 @@ namespace eosio { namespace chain { void set_latest_checkpoint( block_id_type id); - void mark_pbft_prepared_fork(const block_id_type &id) const; + void mark_pbft_prepared_fork(const block_state_ptr& h) const; - void mark_pbft_my_prepare_fork(const block_id_type &id) const; + void mark_pbft_my_prepare_fork(const block_state_ptr& h) const; void remove_pbft_my_prepare_fork(const block_id_type &id) const; diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index 2fbe159ec76..aa8d9eff921 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -845,7 +845,7 @@ namespace eosio { try { auto &ext = b->block_extensions; - for (auto it = ext.begin(); it != ext.end(); it++) { + for (auto it = ext.begin(); it != ext.end();) { if (it->first == static_cast(block_extension_type::pbft_stable_checkpoint)) { auto scp_v = it->second; @@ -859,6 +859,8 @@ namespace eosio { } else { it = ext.erase(it); } + } else { + it++; } } } catch(...) { From 5679479816639088ca904e0b0ed0e76d3fcdc4f3 Mon Sep 17 00:00:00 2001 From: oldcold Date: Thu, 28 Mar 2019 10:16:20 +0800 Subject: [PATCH 07/59] clear prepared (my prepare) id when it becomes invalid. --- libraries/chain/controller.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 2b71da614b7..b4702b56a66 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1511,8 +1511,20 @@ struct controller_impl { void set_pbft_lscb() { if ((!pending || pending->_block_status != controller::block_status::incomplete) && pending_pbft_checkpoint ) { - fork_db.set_latest_checkpoint(*pending_pbft_checkpoint); + + auto checkpoint_block_state = fork_db.get_block(*pending_pbft_checkpoint); + if (checkpoint_block_state) { + fork_db.set_latest_checkpoint(*pending_pbft_checkpoint); + auto checkpoint_num = checkpoint_block_state->block_num; + if (pbft_prepared && pbft_prepared->block_num < checkpoint_num) { + pbft_prepared.reset(); + } + if (my_prepare && my_prepare->block_num < checkpoint_num) { + my_prepare.reset(); + } + } pending_pbft_checkpoint.reset(); + } } From 1977fc00c5ba3278b20e4d963c604ba67b6cf499 Mon Sep 17 00:00:00 2001 From: oldcold Date: Fri, 29 Mar 2019 19:43:18 +0800 Subject: [PATCH 08/59] init commit for upgrade solution --- libraries/chain/block_header_state.cpp | 45 ++++++++++++------- libraries/chain/controller.cpp | 14 +++--- .../include/eosio/chain/block_header.hpp | 2 +- .../chain/include/eosio/chain/config.hpp | 4 +- .../chain/include/eosio/chain/controller.hpp | 3 ++ .../eosio/chain/global_property_object.hpp | 22 +++++++++ libraries/chain/include/eosio/chain/types.hpp | 1 + plugins/pbft_plugin/pbft_plugin.cpp | 4 +- 8 files changed, 69 insertions(+), 26 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index fb1f6a7b173..985f245f71b 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -60,30 +60,37 @@ namespace eosio { namespace chain { result.active_schedule = active_schedule; result.pending_schedule = pending_schedule; -// result.dpos_proposed_irreversible_blocknum = dpos_proposed_irreversible_blocknum; + result.dpos_proposed_irreversible_blocknum = dpos_proposed_irreversible_blocknum; result.bft_irreversible_blocknum = bft_irreversible_blocknum; result.pbft_stable_checkpoint_blocknum = pbft_stable_checkpoint_blocknum; - result.producer_to_last_implied_irb[prokey.producer_name] = result.dpos_proposed_irreversible_blocknum; -// result.dpos_irreversible_blocknum = result.calc_dpos_last_irreversible(); + + if (header.block_num() > 50000) { + result.dpos_irreversible_blocknum = dpos_irreversible_blocknum; + } else { + result.producer_to_last_implied_irb[prokey.producer_name] = result.dpos_proposed_irreversible_blocknum; + result.dpos_irreversible_blocknum = result.calc_dpos_last_irreversible(); + } + + /// grow the confirmed count static_assert(std::numeric_limits::max() >= (config::max_producers * 2 / 3) + 1, "8bit confirmations may not be able to hold all of the needed confirmations"); // This uses the previous block active_schedule because thats the "schedule" that signs and therefore confirms _this_ block -// auto num_active_producers = active_schedule.producers.size(); -// uint32_t required_confs = (uint32_t)(num_active_producers * 2 / 3) + 1; - -// if( confirm_count.size() < config::maximum_tracked_dpos_confirmations ) { -// result.confirm_count.reserve( confirm_count.size() + 1 ); -// result.confirm_count = confirm_count; -// result.confirm_count.resize( confirm_count.size() + 1 ); -// result.confirm_count.back() = (uint8_t)required_confs; -// } else { -// result.confirm_count.resize( confirm_count.size() ); -// memcpy( &result.confirm_count[0], &confirm_count[1], confirm_count.size() - 1 ); -// result.confirm_count.back() = (uint8_t)required_confs; -// } + auto num_active_producers = active_schedule.producers.size(); + uint32_t required_confs = (uint32_t)(num_active_producers * 2 / 3) + 1; + + if( confirm_count.size() < config::maximum_tracked_dpos_confirmations ) { + result.confirm_count.reserve( confirm_count.size() + 1 ); + result.confirm_count = confirm_count; + result.confirm_count.resize( confirm_count.size() + 1 ); + result.confirm_count.back() = (uint8_t)required_confs; + } else { + result.confirm_count.resize( confirm_count.size() ); + memcpy( &result.confirm_count[0], &confirm_count[1], confirm_count.size() - 1 ); + result.confirm_count.back() = (uint8_t)required_confs; + } return result; } /// generate_next @@ -163,7 +170,9 @@ namespace eosio { namespace chain { /// below this point is state changes that cannot be validated with headers alone, but never-the-less, /// must result in header state changes -// result.set_confirmed( h.confirmed ); + + result.set_confirmed( h.confirmed ); + auto was_pending_promoted = result.maybe_promote_pending(); @@ -195,6 +204,8 @@ namespace eosio { namespace chain { std::cerr << "confirm_count["< 50000) return; header.confirmed = num_prev_blocks; int32_t i = (int32_t)(confirm_count.size() - 1); diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index b4702b56a66..44127299ce8 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -35,6 +35,7 @@ using controller_index_set = index_set< global_property_multi_index, global_property2_multi_index, dynamic_global_property_multi_index, + upgrade_property_multi_index, block_summary_multi_index, transaction_multi_index, generated_transaction_multi_index, @@ -673,6 +674,9 @@ struct controller_impl { // *bos end* + + db.create([](auto&){}); + authorization.initialize_database(); resource_limits.initialize_database(); @@ -1240,7 +1244,7 @@ struct controller_impl { pending->_pending_block_state = std::make_shared( *head, when ); // promotes pending schedule (if any) to active pending->_pending_block_state->in_current_chain = true; -// pending->_pending_block_state->set_confirmed(confirm_block_count); + pending->_pending_block_state->set_confirmed(confirm_block_count); auto was_pending_promoted = pending->_pending_block_state->maybe_promote_pending(); @@ -1428,14 +1432,11 @@ struct controller_impl { auto& b = new_header_state->block; emit( self.pre_accepted_block, b ); - auto current_head = head->id; - auto new_head = new_header_state->id; - fork_db.add( new_header_state, false ); if (conf.trusted_producers.count(b->producer)) { trusted_producer_light_validation = true; - }; + } emit( self.accepted_block_header, new_header_state ); set_pbft_lib(); @@ -2582,4 +2583,7 @@ void controller::set_lib() const { my->set_pbft_lscb(); } +const upgrade_property_object& controller::get_upgrade_properties()const { + return my->db.get(); +} } } /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/block_header.hpp b/libraries/chain/include/eosio/chain/block_header.hpp index 6d6c053b53e..e1943741bc9 100644 --- a/libraries/chain/include/eosio/chain/block_header.hpp +++ b/libraries/chain/include/eosio/chain/block_header.hpp @@ -24,7 +24,7 @@ namespace eosio { namespace chain { * behavior. When producing a block a producer is always confirming at least the block he * is building off of. A producer cannot confirm "this" block, only prior blocks. */ - uint16_t confirmed = 0; + uint16_t confirmed = 1; block_id_type previous; diff --git a/libraries/chain/include/eosio/chain/config.hpp b/libraries/chain/include/eosio/chain/config.hpp index 9f24dfc2968..cb294eecc7f 100644 --- a/libraries/chain/include/eosio/chain/config.hpp +++ b/libraries/chain/include/eosio/chain/config.hpp @@ -108,8 +108,8 @@ const static uint32_t default_abi_serializer_max_time_ms = 15*1000; ///< defau const static int producer_repetitions = 12; const static int max_producers = 125; -//const static size_t maximum_tracked_dpos_confirmations = 1024; ///< -//static_assert(maximum_tracked_dpos_confirmations >= ((max_producers * 2 / 3) + 1) * producer_repetitions, "Settings never allow for DPOS irreversibility" ); +const static size_t maximum_tracked_dpos_confirmations = 1024; ///< +static_assert(maximum_tracked_dpos_confirmations >= ((max_producers * 2 / 3) + 1) * producer_repetitions, "Settings never allow for DPOS irreversibility" ); /** diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index af502331b84..8630ae21d65 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -30,6 +30,7 @@ namespace eosio { namespace chain { class dynamic_global_property_object; class global_property_object; class global_property2_object; // *bos* + class upgrade_property_object; class permission_object; class account_object; using resource_limits::resource_limits_manager; @@ -306,6 +307,8 @@ namespace eosio { namespace chain { void set_lib()const; + const upgrade_property_object& get_upgrade_properties()const; + /* signal pre_apply_block; signal post_apply_block; diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index bdb49d3ce06..17e941bf96b 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -44,6 +44,15 @@ namespace eosio { namespace chain { guaranteed_minimum_resources gmr;//guaranteed_minimum_resources }; + class upgrade_property_object : public chainbase::object + { + OBJECT_CTOR(upgrade_property_object) + //TODO: should use a more complicated struct to include id, digest and status of every single upgrade. + + id_type id; + block_num_type upgrade_target_block_num = 44500; + }; + /** * @class dynamic_global_property_object @@ -89,6 +98,15 @@ namespace eosio { namespace chain { > > >; + + using upgrade_property_multi_index = chainbase::shared_multi_index_container< + upgrade_property_object, + indexed_by< + ordered_unique, + BOOST_MULTI_INDEX_MEMBER(upgrade_property_object, upgrade_property_object::id_type, id) + > + > + >; }} CHAINBASE_SET_INDEX_TYPE(eosio::chain::global_property_object, eosio::chain::global_property_multi_index) @@ -96,6 +114,7 @@ CHAINBASE_SET_INDEX_TYPE(eosio::chain::dynamic_global_property_object, eosio::chain::dynamic_global_property_multi_index) // *bos* CHAINBASE_SET_INDEX_TYPE(eosio::chain::global_property2_object, eosio::chain::global_property2_multi_index) +CHAINBASE_SET_INDEX_TYPE(eosio::chain::upgrade_property_object, eosio::chain::upgrade_property_multi_index) FC_REFLECT(eosio::chain::dynamic_global_property_object, (global_action_sequence) @@ -107,4 +126,7 @@ FC_REFLECT(eosio::chain::global_property_object, // *bos* FC_REFLECT(eosio::chain::global_property2_object, (cfg)(gmr) + ) +FC_REFLECT(eosio::chain::upgrade_property_object, + (upgrade_target_block_num) ) \ No newline at end of file diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index bddeb1dd553..06ab7c884fd 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -165,6 +165,7 @@ namespace eosio { namespace chain { global_property_object_type, global_property2_object_type, dynamic_global_property_object_type, + upgrade_property_object_type, block_summary_object_type, transaction_object_type, generated_transaction_object_type, diff --git a/plugins/pbft_plugin/pbft_plugin.cpp b/plugins/pbft_plugin/pbft_plugin.cpp index 51bed775c95..e27d2518ed7 100644 --- a/plugins/pbft_plugin/pbft_plugin.cpp +++ b/plugins/pbft_plugin/pbft_plugin.cpp @@ -129,6 +129,8 @@ namespace eosio { bool pbft_plugin_impl::pbft_ready() { // only trigger pbft related logic if I am in sync and replayed. - return (!is_syncing() && !is_replaying()); + bool upgraded; + if (true) upgraded = true; else upgraded = false; + return (upgraded && (!is_syncing() && !is_replaying())); } } From 1ec967e4cb7c20954f20d4e15f33eab08911e1c2 Mon Sep 17 00:00:00 2001 From: deadlock Date: Sat, 30 Mar 2019 21:05:29 +0800 Subject: [PATCH 09/59] add migration function, hack load function for forkdb.dat --- libraries/chain/fork_database.cpp | 65 ++++++++++++++++--- .../eosio/chain/block_header_state.hpp | 6 +- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index a80d54f782a..672ca6fc138 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -61,18 +61,59 @@ namespace eosio { namespace chain { if( fc::exists( fork_db_dat ) ) { string content; fc::read_file_contents( fork_db_dat, content ); - fc::datastream ds( content.data(), content.size() ); - unsigned_int size; fc::raw::unpack( ds, size ); - for( uint32_t i = 0, n = size.value; i < n; ++i ) { - block_state s; - fc::raw::unpack( ds, s ); - set( std::make_shared( move( s ) ) ); + + string version_label = content.substr(1,7);//start from position 1 because fc pack type in pos 0 + bool is_version_1 = version_label != "version"; + if(is_version_1){ + /*start upgrade migration and this is a hack and ineffecient, but lucky we only need to do it once */ + + auto start = ds.pos(); + unsigned_int size; fc::raw::unpack( ds, size ); + auto skipped_size_pos = ds.pos(); + + vector data(content.begin()+(skipped_size_pos - start), content.end()); + + for( uint32_t i = 0, n = size.value; i < n; ++i ) { + vector tmp = data; + tmp.insert(tmp.begin(), {0,0,0,0}); + fc::datastream tmp_ds(tmp.data(), tmp.size()); + block_state s; + fc::raw::unpack( tmp_ds, s ); + //prepend 4bytes for pbft_stable_checkpoint_blocknum and append 2 bytes for pbft_prepared and pbft_my_prepare + auto tmp_data_length = tmp_ds.tellp() - 6; + data.erase(data.begin(),data.begin()+tmp_data_length); + s.pbft_prepared = false; + s.pbft_my_prepare = false; + set( std::make_shared( move( s ) ) ); + } + fc::datastream head_id_stream(data.data(), data.size()); + block_id_type head_id; + fc::raw::unpack( head_id_stream, head_id ); + + my->head = get_block( head_id ); + /*end upgrade migration*/ + }else{ + //get version number + fc::raw::unpack( ds, version_label ); + EOS_ASSERT(version_label=="version", fork_database_exception, "invalid version label in forkdb.dat"); + uint8_t version_num; + fc::raw::unpack( ds, version_num ); + + EOS_ASSERT(version_num==2, fork_database_exception, "invalid version num in forkdb.dat"); + + unsigned_int size; fc::raw::unpack( ds, size ); + for( uint32_t i = 0, n = size.value; i < n; ++i ) { + block_state s; + fc::raw::unpack( ds, s ); + set( std::make_shared( move( s ) ) ); + } + block_id_type head_id; + fc::raw::unpack( ds, head_id ); + + my->head = get_block( head_id ); } - block_id_type head_id; - fc::raw::unpack( ds, head_id ); - my->head = get_block( head_id ); fc::remove( fork_db_dat ); } @@ -83,6 +124,12 @@ namespace eosio { namespace chain { auto fork_db_dat = my->datadir / config::forkdb_filename; std::ofstream out( fork_db_dat.generic_string().c_str(), std::ios::out | std::ios::binary | std::ofstream::trunc ); + + string version_label = "version"; + fc::raw::pack( out, version_label ); + uint8_t version_num = 2; + fc::raw::pack( out, version_num ); + uint32_t num_blocks_in_fork_db = my->index.size(); fc::raw::pack( out, unsigned_int{num_blocks_in_fork_db} ); for( const auto& s : my->index ) { diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 3f38d329885..31b91fc2c0a 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -10,13 +10,14 @@ namespace eosio { namespace chain { * @brief defines the minimum state necessary to validate transaction headers */ struct block_header_state { + uint32_t pbft_stable_checkpoint_blocknum = 0; block_id_type id; uint32_t block_num = 0; signed_block_header header; uint32_t dpos_proposed_irreversible_blocknum = 0; uint32_t dpos_irreversible_blocknum = 0; uint32_t bft_irreversible_blocknum = 0; - uint32_t pbft_stable_checkpoint_blocknum = 0; + uint32_t pending_schedule_lib_num = 0; /// last irr block num digest_type pending_schedule_hash; producer_schedule_type pending_schedule; @@ -61,8 +62,9 @@ struct block_header_state { } } /// namespace eosio::chain FC_REFLECT( eosio::chain::block_header_state, - (id)(block_num)(header)(dpos_proposed_irreversible_blocknum)(dpos_irreversible_blocknum)(bft_irreversible_blocknum) (pbft_stable_checkpoint_blocknum) + (id)(block_num)(header)(dpos_proposed_irreversible_blocknum)(dpos_irreversible_blocknum)(bft_irreversible_blocknum) + (pending_schedule_lib_num)(pending_schedule_hash) (pending_schedule)(active_schedule)(blockroot_merkle) (producer_to_last_produced)(producer_to_last_implied_irb)(block_signing_key) From 55fcd208b95d406d335031d40aeaaee4c275512f Mon Sep 17 00:00:00 2001 From: oldcold Date: Tue, 2 Apr 2019 22:22:45 +0800 Subject: [PATCH 10/59] hardcoded consensus upgrade at 1000. --- contracts/eosiolib/eosiolib.cpp | 7 + contracts/eosiolib/privileged.h | 1 + contracts/eosiolib/privileged.hpp | 10 ++ libraries/chain/block_header_state.cpp | 55 +++--- libraries/chain/block_state.cpp | 8 +- libraries/chain/controller.cpp | 156 +++++++++++++----- libraries/chain/fork_database.cpp | 10 +- .../eosio/chain/block_header_state.hpp | 10 +- .../chain/include/eosio/chain/block_state.hpp | 4 +- .../include/eosio/chain/fork_database.hpp | 2 +- .../eosio/chain/global_property_object.hpp | 2 +- libraries/chain/include/eosio/chain/types.hpp | 2 +- libraries/chain/wasm_interface.cpp | 11 ++ plugins/pbft_plugin/pbft_plugin.cpp | 16 +- plugins/producer_plugin/producer_plugin.cpp | 14 +- 15 files changed, 224 insertions(+), 84 deletions(-) diff --git a/contracts/eosiolib/eosiolib.cpp b/contracts/eosiolib/eosiolib.cpp index 48d80b1037b..35bc7460c70 100644 --- a/contracts/eosiolib/eosiolib.cpp +++ b/contracts/eosiolib/eosiolib.cpp @@ -55,6 +55,13 @@ namespace eosio { ds >> params; } + void set_upgrade_parameters(const eosio::upgrade_parameters& params) { + char buf[sizeof(eosio::upgrade_parameters)]; + eosio::datastream ds( buf, sizeof(buf) ); + ds << params; + set_upgrade_parameters_packed( buf, ds.tellp() ); + } + using ::memset; using ::memcpy; diff --git a/contracts/eosiolib/privileged.h b/contracts/eosiolib/privileged.h index 8943a09db23..d6d6761e9e3 100644 --- a/contracts/eosiolib/privileged.h +++ b/contracts/eosiolib/privileged.h @@ -92,6 +92,7 @@ extern "C" { */ uint32_t get_blockchain_parameters_packed(char* data, uint32_t datalen); + void set_upgrade_parameters_packed(char* data, uint32_t datalen); /** * @brief Activate new feature * Activate new feature diff --git a/contracts/eosiolib/privileged.hpp b/contracts/eosiolib/privileged.hpp index 3091acf8b3b..b032ac41f8f 100644 --- a/contracts/eosiolib/privileged.hpp +++ b/contracts/eosiolib/privileged.hpp @@ -108,6 +108,14 @@ namespace eosio { ) }; + struct upgrade_parameters { + uint32_t target_block_num; + + EOSLIB_SERIALIZE(upgrade_parameters, + (target_block_num) + ) + }; + /** * @brief Set the blockchain parameters * Set the blockchain parameters @@ -122,6 +130,8 @@ namespace eosio { */ void get_blockchain_parameters(eosio::blockchain_parameters& params); + void set_upgrade_parameters(const eosio::upgrade_parameters& params); + ///@} priviledgedcppapi /** diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 985f245f71b..84d359b45e2 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -33,7 +33,7 @@ namespace eosio { namespace chain { * contain a transaction mroot, action mroot, or new_producers as those components * are derived from chain state. */ - block_header_state block_header_state::generate_next( block_timestamp_type when )const { + block_header_state block_header_state::generate_next( block_timestamp_type when, bool new_version )const { block_header_state result; if( when != block_timestamp_type() ) { @@ -65,7 +65,7 @@ namespace eosio { namespace chain { result.pbft_stable_checkpoint_blocknum = pbft_stable_checkpoint_blocknum; - if (header.block_num() > 50000) { + if (new_version) { result.dpos_irreversible_blocknum = dpos_irreversible_blocknum; } else { result.producer_to_last_implied_irb[prokey.producer_name] = result.dpos_proposed_irreversible_blocknum; @@ -81,24 +81,30 @@ namespace eosio { namespace chain { auto num_active_producers = active_schedule.producers.size(); uint32_t required_confs = (uint32_t)(num_active_producers * 2 / 3) + 1; - if( confirm_count.size() < config::maximum_tracked_dpos_confirmations ) { - result.confirm_count.reserve( confirm_count.size() + 1 ); - result.confirm_count = confirm_count; - result.confirm_count.resize( confirm_count.size() + 1 ); - result.confirm_count.back() = (uint8_t)required_confs; - } else { - result.confirm_count.resize( confirm_count.size() ); - memcpy( &result.confirm_count[0], &confirm_count[1], confirm_count.size() - 1 ); - result.confirm_count.back() = (uint8_t)required_confs; + if (!new_version) { + if (confirm_count.size() < config::maximum_tracked_dpos_confirmations) { + result.confirm_count.reserve(confirm_count.size() + 1); + result.confirm_count = confirm_count; + result.confirm_count.resize(confirm_count.size() + 1); + result.confirm_count.back() = (uint8_t) required_confs; + } else { + result.confirm_count.resize(confirm_count.size()); + memcpy(&result.confirm_count[0], &confirm_count[1], confirm_count.size() - 1); + result.confirm_count.back() = (uint8_t) required_confs; + } } return result; } /// generate_next - bool block_header_state::maybe_promote_pending() { - if (pending_schedule.producers.size()) - //TODO: is this actually safe? -// bft_irreversible_blocknum >= pending_schedule_lib_num ) + bool block_header_state::maybe_promote_pending( bool new_version ) { + + bool should_promote_pending = pending_schedule.producers.size(); + if ( !new_version ) { + should_promote_pending = should_promote_pending && dpos_irreversible_blocknum >= pending_schedule_lib_num; + } + + if (should_promote_pending) { active_schedule = move( pending_schedule ); @@ -108,6 +114,7 @@ namespace eosio { namespace chain { if( existing != producer_to_last_produced.end() ) { new_producer_to_last_produced[pro.producer_name] = existing->second; } else { + //TODO: max of bft and dpos lib new_producer_to_last_produced[pro.producer_name] = bft_irreversible_blocknum; } } @@ -118,6 +125,7 @@ namespace eosio { namespace chain { if( existing != producer_to_last_implied_irb.end() ) { new_producer_to_last_implied_irb[pro.producer_name] = existing->second; } else { + //TODO: max of bft and dpos lib new_producer_to_last_implied_irb[pro.producer_name] = bft_irreversible_blocknum; } } @@ -150,18 +158,19 @@ 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 { + block_header_state block_header_state::next( const signed_block_header& h, bool skip_validate_signee, bool new_version )const { EOS_ASSERT( h.timestamp != block_timestamp_type(), block_validate_exception, "", ("h",h) ); //EOS_ASSERT( h.header_extensions.size() == 0, block_validate_exception, "no supported extensions" ); EOS_ASSERT( h.timestamp > header.timestamp, block_validate_exception, "block must be later in time" ); EOS_ASSERT( h.previous == id, unlinkable_block_exception, "block must link to current state" ); - auto result = generate_next( h.timestamp ); + auto result = generate_next( h.timestamp, new_version); EOS_ASSERT( result.header.producer == h.producer, wrong_producer, "wrong producer specified" ); EOS_ASSERT( result.header.schedule_version == h.schedule_version, producer_schedule_exception, "schedule_version in signed block is corrupted" ); auto itr = producer_to_last_produced.find(h.producer); if( itr != producer_to_last_produced.end() ) { + ilog("itr: ${i} header: ${h}", ("i", *itr)("h", h.confirmed)); EOS_ASSERT( itr->second < result.block_num - h.confirmed, producer_double_confirm, "producer ${prod} double-confirming known range", ("prod", h.producer) ); } @@ -171,10 +180,10 @@ namespace eosio { namespace chain { /// must result in header state changes - result.set_confirmed( h.confirmed ); + result.set_confirmed(h.confirmed, new_version); - auto was_pending_promoted = result.maybe_promote_pending(); + auto was_pending_promoted = result.maybe_promote_pending(new_version); 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" ); @@ -196,7 +205,7 @@ namespace eosio { namespace chain { return result; } /// next - void block_header_state::set_confirmed( uint16_t num_prev_blocks ) { + void block_header_state::set_confirmed( uint16_t num_prev_blocks, bool new_version ) { /* idump((num_prev_blocks)(confirm_count.size())); @@ -204,8 +213,10 @@ namespace eosio { namespace chain { std::cerr << "confirm_count["< 50000) return; + if (new_version) { + header.confirmed = 0; + return; + } header.confirmed = num_prev_blocks; int32_t i = (int32_t)(confirm_count.size() - 1); diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index b4834775951..54cfd5c3211 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -3,15 +3,15 @@ namespace eosio { namespace chain { - block_state::block_state( const block_header_state& prev, block_timestamp_type when ) - :block_header_state( prev.generate_next( when ) ), + block_state::block_state( const block_header_state& prev, block_timestamp_type when, bool new_version ) + :block_header_state( prev.generate_next( when, new_version) ), block( std::make_shared() ) { static_cast(*block) = header; } - block_state::block_state( const block_header_state& prev, signed_block_ptr b, bool skip_validate_signee ) - :block_header_state( prev.next( *b, skip_validate_signee )), block( move(b) ) + block_state::block_state( const block_header_state& prev, signed_block_ptr b, bool skip_validate_signee, bool new_version ) + :block_header_state( prev.next( *b, skip_validate_signee, new_version)), block( move(b) ) { } diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 44127299ce8..63720e79e9b 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -675,7 +675,7 @@ struct controller_impl { // *bos end* - db.create([](auto&){}); + db.create([](auto&){}); authorization.initialize_database(); resource_limits.initialize_database(); @@ -792,19 +792,32 @@ struct controller_impl { set_pbft_lscb(); if (add_to_fork_db) { pending->_pending_block_state->validated = true; - auto new_bsp = fork_db.add(pending->_pending_block_state, true); - emit(self.accepted_block_header, pending->_pending_block_state); - if (pbft_prepared) { - fork_db.mark_pbft_prepared_fork(pbft_prepared); - } else if (head) { - fork_db.mark_pbft_prepared_fork(head); + auto new_version = false; + + try { + const auto& upo = db.get(); + new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + } catch( const boost::exception& e) { + wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + db.create([](auto&){}); } - if (my_prepare) { - fork_db.mark_pbft_my_prepare_fork(my_prepare); - } else if (head) { - fork_db.mark_pbft_my_prepare_fork(head); + auto new_bsp = fork_db.add(pending->_pending_block_state, true); + emit(self.accepted_block_header, pending->_pending_block_state); + + if (new_version) { + if (pbft_prepared) { + fork_db.mark_pbft_prepared_fork(pbft_prepared); + } else if (head) { + fork_db.mark_pbft_prepared_fork(head); + } + + if (my_prepare) { + fork_db.mark_pbft_my_prepare_fork(my_prepare); + } else if (head) { + fork_db.mark_pbft_my_prepare_fork(head); + } } head = fork_db.head(); @@ -1238,45 +1251,69 @@ struct controller_impl { pending.emplace(maybe_session()); } + auto new_version = false; + + try { + const auto& upo = db.get(); + new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + } catch( const boost::exception& e) { + wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + db.create([](auto&){}); + } + + ilog("upo ${n}, dpos ${l}", ("n", db.get().upgrade_target_block_num)("l", head->dpos_irreversible_blocknum)); + + pending->_block_status = s; pending->_producer_block_id = producer_block_id; pending->_signer = signer; - pending->_pending_block_state = std::make_shared( *head, when ); // promotes pending schedule (if any) to active + pending->_pending_block_state = std::make_shared( *head, when, new_version); // promotes pending schedule (if any) to active pending->_pending_block_state->in_current_chain = true; - pending->_pending_block_state->set_confirmed(confirm_block_count); + pending->_pending_block_state->set_confirmed(confirm_block_count, new_version); - auto was_pending_promoted = pending->_pending_block_state->maybe_promote_pending(); + + auto was_pending_promoted = pending->_pending_block_state->maybe_promote_pending(new_version); //modify state in speculative block only if we are speculative reads mode (other wise we need clean state for head or irreversible reads) if ( read_mode == db_read_mode::SPECULATIVE || pending->_block_status != controller::block_status::incomplete ) { const auto& gpo = db.get(); - if (gpo.proposed_schedule_block_num) { - last_proposed_schedule_block_num.reset(); - last_proposed_schedule_block_num.emplace(*gpo.proposed_schedule_block_num); + if (new_version) { + if (gpo.proposed_schedule_block_num) { + last_proposed_schedule_block_num.reset(); + last_proposed_schedule_block_num.emplace(*gpo.proposed_schedule_block_num); + } + } + + bool should_promote_pending_schedule = gpo.proposed_schedule_block_num.valid() // if there is a proposed schedule that was proposed in a block ... + && pending->_pending_block_state->pending_schedule.producers.size() == 0 // ... and there is room for a new pending schedule ... + && !was_pending_promoted; // ... and not just because it was promoted to active at the start of this block, then: + + if (new_version) { + should_promote_pending_schedule = should_promote_pending_schedule && pending->_pending_block_state->block_num > *gpo.proposed_schedule_block_num; + } else { + should_promote_pending_schedule = should_promote_pending_schedule && ( *gpo.proposed_schedule_block_num <= pending->_pending_block_state->dpos_irreversible_blocknum ); } - if( gpo.proposed_schedule_block_num.valid() && // if there is a proposed schedule that was proposed in a block ... -// ( *gpo.proposed_schedule_block_num <= pending->_pending_block_state->pbft_stable_checkpoint_blocknum ) && // ... that has now become irreversible ... - pending->_pending_block_state->pending_schedule.producers.size() == 0 && // ... and there is room for a new pending schedule ... - !was_pending_promoted && // ... and not just because it was promoted to active at the start of this block, then: - pending->_pending_block_state->block_num > *gpo.proposed_schedule_block_num //TODO: to be optimised. - ) + + if ( should_promote_pending_schedule ) { // Promote proposed schedule to pending schedule. if( !replaying ) { ilog( "promoting proposed schedule (set in block ${proposed_num}) to pending; current block: ${n} lib: ${lib} schedule: ${schedule} ", ("proposed_num", *gpo.proposed_schedule_block_num)("n", pending->_pending_block_state->block_num) - ("lib", pending->_pending_block_state->bft_irreversible_blocknum) + ("lib", std::max(pending->_pending_block_state->bft_irreversible_blocknum, pending->_pending_block_state->dpos_irreversible_blocknum)) ("schedule", static_cast(gpo.proposed_schedule) ) ); - last_promoted_proposed_schedule_block_num.reset(); - last_promoted_proposed_schedule_block_num.emplace(pending->_pending_block_state->block_num); + } pending->_pending_block_state->set_new_producers( gpo.proposed_schedule ); db.modify( gpo, [&]( auto& gp ) { gp.proposed_schedule_block_num = optional(); gp.proposed_schedule.clear(); }); + + last_promoted_proposed_schedule_block_num.reset(); + last_promoted_proposed_schedule_block_num.emplace(pending->_pending_block_state->block_num); } try { @@ -1415,9 +1452,19 @@ struct controller_impl { auto prev = fork_db.get_block( b->previous ); EOS_ASSERT( prev, unlinkable_block_exception, "unlinkable block ${id}", ("id", id)("previous", b->previous) ); - return async_thread_pool( thread_pool, [b, prev]() { + auto new_version = false; + + try { + const auto& upo = db.get(); + new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + } catch( const boost::exception& e) { + wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + db.create([](auto&){}); + } + + return async_thread_pool( thread_pool, [b, prev, new_version]() { const bool skip_validate_signee = false; - return std::make_shared( *prev, move( b ), skip_validate_signee ); + return std::make_shared( *prev, move( b ), skip_validate_signee, new_version); } ); } @@ -1432,7 +1479,17 @@ struct controller_impl { auto& b = new_header_state->block; emit( self.pre_accepted_block, b ); - fork_db.add( new_header_state, false ); + auto new_version = false; + + try { + const auto& upo = db.get(); + new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + } catch( const boost::exception& e) { + wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + db.create([](auto&){}); + } + + fork_db.add( new_header_state, false); if (conf.trusted_producers.count(b->producer)) { trusted_producer_light_validation = true; @@ -1462,7 +1519,18 @@ struct controller_impl { block_validate_exception, "invalid block status for replay" ); emit( self.pre_accepted_block, b ); const bool skip_validate_signee = !conf.force_all_checks; - auto new_header_state = fork_db.add( b, skip_validate_signee ); + + auto new_version = false; + + try { + const auto& upo = db.get(); + new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + } catch( const boost::exception& e) { + wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + db.create([](auto&){}); + } + + auto new_header_state = fork_db.add( b, skip_validate_signee, new_version); emit( self.accepted_block_header, new_header_state ); @@ -1531,16 +1599,28 @@ struct controller_impl { void maybe_switch_forks( controller::block_status s ) { - if (pbft_prepared) { - fork_db.mark_pbft_prepared_fork(pbft_prepared); - } else if (head) { - fork_db.mark_pbft_prepared_fork(head); + auto new_version = false; + + try { + const auto& upo = db.get(); + new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + } catch( const boost::exception& e) { + wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + db.create([](auto&){}); } - if (my_prepare) { - fork_db.mark_pbft_my_prepare_fork(my_prepare); - } else if (head) { - fork_db.mark_pbft_my_prepare_fork(head); + if (new_version) { + if (pbft_prepared) { + fork_db.mark_pbft_prepared_fork(pbft_prepared); + } else if (head) { + fork_db.mark_pbft_prepared_fork(head); + } + + if (my_prepare) { + fork_db.mark_pbft_my_prepare_fork(my_prepare); + } else if (head) { + fork_db.mark_pbft_my_prepare_fork(head); + } } auto new_head = fork_db.head(); diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 672ca6fc138..edfbb82cedd 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -32,8 +32,8 @@ namespace eosio { namespace chain { >, ordered_non_unique< tag, composite_key< block_state, -// member, member, +// member, member, member, member @@ -144,7 +144,7 @@ namespace eosio { namespace chain { /// we cannot normally prune the lib if it is the head block because /// the next block needs to build off of the head block. We are exiting /// now so we can prune this block as irreversible before exiting. - auto lib = my->head->bft_irreversible_blocknum; + auto lib = std::max(my->head->bft_irreversible_blocknum, my->head->dpos_irreversible_blocknum); auto checkpoint = my->head->pbft_stable_checkpoint_blocknum; auto oldest = *my->index.get().begin(); if( oldest->block_num < lib && oldest->block_num < checkpoint ) { @@ -188,7 +188,7 @@ namespace eosio { namespace chain { my->head = *my->index.get().begin(); - auto lib = my->head->bft_irreversible_blocknum; + auto lib = std::max(my->head->bft_irreversible_blocknum, my->head->dpos_irreversible_blocknum); auto checkpoint = my->head->pbft_stable_checkpoint_blocknum; auto oldest = *my->index.get().begin(); @@ -199,7 +199,7 @@ namespace eosio { namespace chain { return n; } - block_state_ptr fork_database::add( signed_block_ptr b, bool skip_validate_signee ) { + block_state_ptr fork_database::add( signed_block_ptr b, bool skip_validate_signee, bool new_version ) { EOS_ASSERT( b, fork_database_exception, "attempt to add null block" ); EOS_ASSERT( my->head, fork_db_block_not_found, "no head block set" ); const auto& by_id_idx = my->index.get(); @@ -209,7 +209,7 @@ namespace eosio { namespace chain { auto prior = by_id_idx.find( b->previous ); EOS_ASSERT( prior != by_id_idx.end(), unlinkable_block_exception, "unlinkable block", ("id", string(b->id()))("previous", string(b->previous)) ); - auto result = std::make_shared( **prior, move(b), skip_validate_signee ); + auto result = std::make_shared( **prior, move(b), skip_validate_signee, new_version); EOS_ASSERT( result, fork_database_exception , "fail to add new block state" ); return add(result, true); } diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 31b91fc2c0a..26ba42cc9f8 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -10,7 +10,7 @@ namespace eosio { namespace chain { * @brief defines the minimum state necessary to validate transaction headers */ struct block_header_state { - uint32_t pbft_stable_checkpoint_blocknum = 0; + uint32_t pbft_stable_checkpoint_blocknum = 0; block_id_type id; uint32_t block_num = 0; signed_block_header header; @@ -29,13 +29,13 @@ struct block_header_state { vector confirm_count; vector confirmations; - block_header_state next( const signed_block_header& h, bool trust = false )const; - block_header_state generate_next( block_timestamp_type when )const; + block_header_state next( const signed_block_header& h, bool trust = false, bool new_version = false)const; + block_header_state generate_next( block_timestamp_type when, bool new_version = false )const; void set_new_producers( producer_schedule_type next_pending ); - void set_confirmed( uint16_t num_prev_blocks ); + void set_confirmed( uint16_t num_prev_blocks, bool new_version = false ); void add_confirmation( const header_confirmation& c ); - bool maybe_promote_pending(); + bool maybe_promote_pending( bool new_version = false); bool has_pending_producers()const { return pending_schedule.producers.size(); } diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 170cdd37abc..3745beaaeaf 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -13,8 +13,8 @@ namespace eosio { namespace chain { struct block_state : public block_header_state { explicit block_state( const block_header_state& cur ):block_header_state(cur){} - block_state( const block_header_state& prev, signed_block_ptr b, bool skip_validate_signee ); - block_state( const block_header_state& prev, block_timestamp_type when ); + block_state( const block_header_state& prev, signed_block_ptr b, bool skip_validate_signee, bool new_version ); + block_state( const block_header_state& prev, block_timestamp_type when, bool new_version ); block_state() = default; /// weak_ptr prev_block_state.... diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 99e021b2e4b..231790c7bda 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -40,7 +40,7 @@ namespace eosio { namespace chain { * block_state and will return a pointer to the new block state or * throw on error. */ - block_state_ptr add( signed_block_ptr b, bool skip_validate_signee ); + block_state_ptr add( signed_block_ptr b, bool skip_validate_signee, bool new_version); block_state_ptr add( const block_state_ptr& next_block, bool skip_validate_previous ); void remove( const block_id_type& id ); diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index 17e941bf96b..dfcc3f6106d 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -50,7 +50,7 @@ namespace eosio { namespace chain { //TODO: should use a more complicated struct to include id, digest and status of every single upgrade. id_type id; - block_num_type upgrade_target_block_num = 44500; + block_num_type upgrade_target_block_num = 1000; }; diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index 06ab7c884fd..f8360c0bc7d 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -165,7 +165,6 @@ namespace eosio { namespace chain { global_property_object_type, global_property2_object_type, dynamic_global_property_object_type, - upgrade_property_object_type, block_summary_object_type, transaction_object_type, generated_transaction_object_type, @@ -190,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, + upgrade_property_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 7d3553e379b..13d4094de17 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -193,6 +193,17 @@ class privileged_api : public context_aware_api { }); } + void set_upgrade_parameters_packed( array_ptr packed_upgrade_parameters, size_t datalen) { + datastream ds( packed_upgrade_parameters, datalen ); + uint32_t target_num; + fc::raw::unpack(ds, target_num); + + context.db.modify( context.control.get_upgrade_properties(), + [&]( auto& uprops ) { + uprops.upgrade_target_block_num = target_num; + }); + } + // *bos begin* void set_name_list_packed(int64_t list, int64_t action, array_ptr packed_name_list, size_t datalen) { diff --git a/plugins/pbft_plugin/pbft_plugin.cpp b/plugins/pbft_plugin/pbft_plugin.cpp index e27d2518ed7..abd7765d10b 100644 --- a/plugins/pbft_plugin/pbft_plugin.cpp +++ b/plugins/pbft_plugin/pbft_plugin.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace eosio { static appbase::abstract_plugin &_pbft_plugin = app().register_plugin(); @@ -129,8 +130,17 @@ namespace eosio { bool pbft_plugin_impl::pbft_ready() { // only trigger pbft related logic if I am in sync and replayed. - bool upgraded; - if (true) upgraded = true; else upgraded = false; - return (upgraded && (!is_syncing() && !is_replaying())); + + auto new_version = false; + auto& chain = app().get_plugin().chain(); + + try { + const auto& upo = chain.get_upgrade_properties().upgrade_target_block_num; + new_version = chain.last_irreversible_block_num() >= upo + 500; + } catch( const boost::exception& e) { + wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + } + + return (new_version && (!is_syncing() && !is_replaying())); } } diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index ffb9a2815cc..0a19c05a737 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -224,10 +224,20 @@ class producer_plugin_impl : public std::enable_shared_from_thisheader; new_block_header.timestamp = new_block_header.timestamp.next(); new_block_header.previous = bsp->id; - auto new_bs = bsp->generate_next(new_block_header.timestamp); + + auto new_version = false; + + try { + const auto& upo = chain.get_upgrade_properties(); + new_version = chain.last_irreversible_block_num() >= upo.upgrade_target_block_num; + } catch( const boost::exception& e) { + wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + } + + auto new_bs = bsp->generate_next(new_block_header.timestamp, new_version); // for newly installed producers we can set their watermarks to the block they became active - if (new_bs.maybe_promote_pending() && bsp->active_schedule.version != new_bs.active_schedule.version) { + if (new_bs.maybe_promote_pending(new_version) && bsp->active_schedule.version != new_bs.active_schedule.version) { flat_set new_producers; new_producers.reserve(new_bs.active_schedule.producers.size()); for( const auto& p: new_bs.active_schedule.producers) { From 0493444c1ed5950526c2acf0d49c8e1b7ad1c9ef Mon Sep 17 00:00:00 2001 From: oldcold Date: Wed, 3 Apr 2019 11:28:17 +0800 Subject: [PATCH 11/59] disable proposed schedule promotion during upgrading --- libraries/chain/block_header_state.cpp | 14 +++++++++-- libraries/chain/controller.cpp | 23 ++++++++++++------- .../eosio/chain/global_property_object.hpp | 6 ++--- plugins/pbft_plugin/pbft_plugin.cpp | 8 +++---- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 84d359b45e2..28ff88b4d4a 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -115,7 +115,12 @@ namespace eosio { namespace chain { new_producer_to_last_produced[pro.producer_name] = existing->second; } else { //TODO: max of bft and dpos lib - new_producer_to_last_produced[pro.producer_name] = bft_irreversible_blocknum; + if (new_version) { + new_producer_to_last_produced[pro.producer_name] = bft_irreversible_blocknum; + } else { + new_producer_to_last_produced[pro.producer_name] = dpos_irreversible_blocknum; + } + } } @@ -126,7 +131,12 @@ namespace eosio { namespace chain { new_producer_to_last_implied_irb[pro.producer_name] = existing->second; } else { //TODO: max of bft and dpos lib - new_producer_to_last_implied_irb[pro.producer_name] = bft_irreversible_blocknum; + if (new_version) { + new_producer_to_last_implied_irb[pro.producer_name] = bft_irreversible_blocknum; + } else { + new_producer_to_last_implied_irb[pro.producer_name] = dpos_irreversible_blocknum; + } + } } diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 63720e79e9b..b06c1961e5a 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1252,10 +1252,13 @@ struct controller_impl { } auto new_version = false; + auto under_upgradation = false; try { const auto& upo = db.get(); new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + under_upgradation = (head->block_num + 1 >= upo.upgrade_target_block_num) + && head->dpos_irreversible_blocknum <= upo.upgrade_target_block_num + 12; } catch( const boost::exception& e) { wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); db.create([](auto&){}); @@ -1298,15 +1301,19 @@ struct controller_impl { if ( should_promote_pending_schedule ) { - // Promote proposed schedule to pending schedule. - if( !replaying ) { - ilog( "promoting proposed schedule (set in block ${proposed_num}) to pending; current block: ${n} lib: ${lib} schedule: ${schedule} ", - ("proposed_num", *gpo.proposed_schedule_block_num)("n", pending->_pending_block_state->block_num) - ("lib", std::max(pending->_pending_block_state->bft_irreversible_blocknum, pending->_pending_block_state->dpos_irreversible_blocknum)) - ("schedule", static_cast(gpo.proposed_schedule) ) ); - + if (!under_upgradation) { + // Promote proposed schedule to pending schedule. + if (!replaying) { + ilog("promoting proposed schedule (set in block ${proposed_num}) to pending; current block: ${n} lib: ${lib} schedule: ${schedule} ", + ("proposed_num", *gpo.proposed_schedule_block_num)("n", + pending->_pending_block_state->block_num) + ("lib", std::max(pending->_pending_block_state->bft_irreversible_blocknum, + pending->_pending_block_state->dpos_irreversible_blocknum)) + ("schedule", static_cast(gpo.proposed_schedule))); + + } + pending->_pending_block_state->set_new_producers(gpo.proposed_schedule); } - pending->_pending_block_state->set_new_producers( gpo.proposed_schedule ); db.modify( gpo, [&]( auto& gp ) { gp.proposed_schedule_block_num = optional(); gp.proposed_schedule.clear(); diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index dfcc3f6106d..4f2e355bde9 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -46,11 +46,11 @@ namespace eosio { namespace chain { class upgrade_property_object : public chainbase::object { - OBJECT_CTOR(upgrade_property_object) + OBJECT_CTOR(upgrade_property_object) //TODO: should use a more complicated struct to include id, digest and status of every single upgrade. - id_type id; - block_num_type upgrade_target_block_num = 1000; + id_type id; + block_num_type upgrade_target_block_num = 2000; }; diff --git a/plugins/pbft_plugin/pbft_plugin.cpp b/plugins/pbft_plugin/pbft_plugin.cpp index abd7765d10b..149a953e9c9 100644 --- a/plugins/pbft_plugin/pbft_plugin.cpp +++ b/plugins/pbft_plugin/pbft_plugin.cpp @@ -33,9 +33,9 @@ namespace eosio { void checkpoint_timer_tick(); private: - bool is_replaying(); - bool is_syncing(); - bool pbft_ready(); + static bool is_replaying(); + static bool is_syncing(); + static bool pbft_ready(); }; pbft_plugin::pbft_plugin() : my(new pbft_plugin_impl()) {} @@ -136,7 +136,7 @@ namespace eosio { try { const auto& upo = chain.get_upgrade_properties().upgrade_target_block_num; - new_version = chain.last_irreversible_block_num() >= upo + 500; + new_version = chain.last_irreversible_block_num() >= upo; } catch( const boost::exception& e) { wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); } From 8b737ccc9ebbe0b8394fbb80f05613fe539bea42 Mon Sep 17 00:00:00 2001 From: oldcold Date: Wed, 3 Apr 2019 18:08:25 +0800 Subject: [PATCH 12/59] add system contract support --- contracts/eosiolib/privileged.hpp | 8 ++-- libraries/chain/block_header_state.cpp | 1 - libraries/chain/controller.cpp | 41 +++++++++---------- .../eosio/chain/global_property_object.hpp | 2 +- libraries/chain/wasm_interface.cpp | 1 + plugins/pbft_plugin/pbft_plugin.cpp | 4 +- plugins/producer_plugin/producer_plugin.cpp | 4 +- 7 files changed, 32 insertions(+), 29 deletions(-) diff --git a/contracts/eosiolib/privileged.hpp b/contracts/eosiolib/privileged.hpp index b032ac41f8f..e6541f31eb6 100644 --- a/contracts/eosiolib/privileged.hpp +++ b/contracts/eosiolib/privileged.hpp @@ -109,11 +109,11 @@ namespace eosio { }; struct upgrade_parameters { - uint32_t target_block_num; + uint32_t target_block_num; - EOSLIB_SERIALIZE(upgrade_parameters, - (target_block_num) - ) + EOSLIB_SERIALIZE(upgrade_parameters, + (target_block_num) + ) }; /** diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 28ff88b4d4a..96838c2fc25 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -180,7 +180,6 @@ namespace eosio { namespace chain { auto itr = producer_to_last_produced.find(h.producer); if( itr != producer_to_last_produced.end() ) { - ilog("itr: ${i} header: ${h}", ("i", *itr)("h", h.confirmed)); EOS_ASSERT( itr->second < result.block_num - h.confirmed, producer_double_confirm, "producer ${prod} double-confirming known range", ("prod", h.producer) ); } diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index b06c1961e5a..8fac0d2dac5 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -797,7 +797,9 @@ struct controller_impl { try { const auto& upo = db.get(); - new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + if (upo.upgrade_target_block_num > head->block_num) { + new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + } } catch( const boost::exception& e) { wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); db.create([](auto&){}); @@ -1252,20 +1254,21 @@ struct controller_impl { } auto new_version = false; - auto under_upgradation = false; + auto upgrading = false; try { const auto& upo = db.get(); - new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; - under_upgradation = (head->block_num + 1 >= upo.upgrade_target_block_num) - && head->dpos_irreversible_blocknum <= upo.upgrade_target_block_num + 12; + ilog("upgrade target block num: ${n}", ("n", upo.upgrade_target_block_num)); + if (upo.upgrade_target_block_num > head->block_num) { + new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + upgrading = (head->block_num + 1 >= upo.upgrade_target_block_num) + && head->dpos_irreversible_blocknum <= upo.upgrade_target_block_num + 12; + } } catch( const boost::exception& e) { wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); db.create([](auto&){}); } - ilog("upo ${n}, dpos ${l}", ("n", db.get().upgrade_target_block_num)("l", head->dpos_irreversible_blocknum)); - pending->_block_status = s; pending->_producer_block_id = producer_block_id; @@ -1301,7 +1304,7 @@ struct controller_impl { if ( should_promote_pending_schedule ) { - if (!under_upgradation) { + if (!upgrading) { // Promote proposed schedule to pending schedule. if (!replaying) { ilog("promoting proposed schedule (set in block ${proposed_num}) to pending; current block: ${n} lib: ${lib} schedule: ${schedule} ", @@ -1463,7 +1466,9 @@ struct controller_impl { try { const auto& upo = db.get(); - new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + if (upo.upgrade_target_block_num > head->block_num) { + new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + } } catch( const boost::exception& e) { wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); db.create([](auto&){}); @@ -1486,16 +1491,6 @@ struct controller_impl { auto& b = new_header_state->block; emit( self.pre_accepted_block, b ); - auto new_version = false; - - try { - const auto& upo = db.get(); - new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; - } catch( const boost::exception& e) { - wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); - db.create([](auto&){}); - } - fork_db.add( new_header_state, false); if (conf.trusted_producers.count(b->producer)) { @@ -1531,7 +1526,9 @@ struct controller_impl { try { const auto& upo = db.get(); - new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + if (upo.upgrade_target_block_num > head->block_num) { + new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + } } catch( const boost::exception& e) { wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); db.create([](auto&){}); @@ -1610,7 +1607,9 @@ struct controller_impl { try { const auto& upo = db.get(); - new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + if (upo.upgrade_target_block_num > head->block_num) { + new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; + } } catch( const boost::exception& e) { wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); db.create([](auto&){}); diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index 4f2e355bde9..4325ec0433e 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -50,7 +50,7 @@ namespace eosio { namespace chain { //TODO: should use a more complicated struct to include id, digest and status of every single upgrade. id_type id; - block_num_type upgrade_target_block_num = 2000; + block_num_type upgrade_target_block_num = 0; }; diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 13d4094de17..83440405c59 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -1843,6 +1843,7 @@ REGISTER_INTRINSICS(privileged_api, (set_guaranteed_minimum_resources, void(int64_t,int64_t,int64_t) ) (is_privileged, int(int64_t) ) (set_privileged, void(int64_t, int) ) + (set_upgrade_parameters_packed, void(int, int) ) ); REGISTER_INJECTED_INTRINSICS(transaction_context, diff --git a/plugins/pbft_plugin/pbft_plugin.cpp b/plugins/pbft_plugin/pbft_plugin.cpp index 149a953e9c9..82dad7e852d 100644 --- a/plugins/pbft_plugin/pbft_plugin.cpp +++ b/plugins/pbft_plugin/pbft_plugin.cpp @@ -136,7 +136,9 @@ namespace eosio { try { const auto& upo = chain.get_upgrade_properties().upgrade_target_block_num; - new_version = chain.last_irreversible_block_num() >= upo; + if (upo > chain.head_block_num()) { + new_version = chain.last_irreversible_block_num() >= upo; + } } catch( const boost::exception& e) { wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); } diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 0a19c05a737..e75a4705cd6 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -229,7 +229,9 @@ class producer_plugin_impl : public std::enable_shared_from_this= upo.upgrade_target_block_num; + if (upo.upgrade_target_block_num > chain.head_block_num()) { + new_version = chain.last_irreversible_block_num() >= upo.upgrade_target_block_num; + } } catch( const boost::exception& e) { wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); } From 35ce2cf70d695b38caf3bf366bd865e341131603 Mon Sep 17 00:00:00 2001 From: oldcold Date: Thu, 4 Apr 2019 11:47:57 +0800 Subject: [PATCH 13/59] add upo assertion, target block num must be at least 100 more than current head. --- libraries/chain/controller.cpp | 49 +++++++++------------ libraries/chain/wasm_interface.cpp | 2 + plugins/pbft_plugin/pbft_plugin.cpp | 6 +-- plugins/producer_plugin/producer_plugin.cpp | 8 ++-- 4 files changed, 28 insertions(+), 37 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 8fac0d2dac5..c6e6009d608 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -796,12 +796,10 @@ struct controller_impl { auto new_version = false; try { - const auto& upo = db.get(); - if (upo.upgrade_target_block_num > head->block_num) { - new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; - } + const auto& upo = db.get().upgrade_target_block_num; + new_version = head->dpos_irreversible_blocknum >= upo && upo > 0; } catch( const boost::exception& e) { - wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + wlog("get upo failed, regenerating..."); db.create([](auto&){}); } @@ -1257,18 +1255,19 @@ struct controller_impl { auto upgrading = false; try { - const auto& upo = db.get(); - ilog("upgrade target block num: ${n}", ("n", upo.upgrade_target_block_num)); - if (upo.upgrade_target_block_num > head->block_num) { - new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; - upgrading = (head->block_num + 1 >= upo.upgrade_target_block_num) - && head->dpos_irreversible_blocknum <= upo.upgrade_target_block_num + 12; - } + const auto& upo = db.get().upgrade_target_block_num; + ilog("upgrade target block num: ${n}", ("n", upo)); + new_version = head->dpos_irreversible_blocknum >= upo && upo > 0; + upgrading = (head->block_num + 1 >= upo) && head->dpos_irreversible_blocknum <= upo + 12; } catch( const boost::exception& e) { - wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + wlog("get upo failed, regenerating..."); db.create([](auto&){}); } + if (upgrading) { + ilog("SYSTEM IS UPGRADING, no producer schedule changes will happen until fully upgraded."); + } + pending->_block_status = s; pending->_producer_block_id = producer_block_id; @@ -1465,12 +1464,10 @@ struct controller_impl { auto new_version = false; try { - const auto& upo = db.get(); - if (upo.upgrade_target_block_num > head->block_num) { - new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; - } + const auto& upo = db.get().upgrade_target_block_num; + new_version = head->dpos_irreversible_blocknum >= upo && upo > 0; } catch( const boost::exception& e) { - wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + wlog("get upo failed, regenerating..."); db.create([](auto&){}); } @@ -1525,12 +1522,10 @@ struct controller_impl { auto new_version = false; try { - const auto& upo = db.get(); - if (upo.upgrade_target_block_num > head->block_num) { - new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; - } + const auto& upo = db.get().upgrade_target_block_num; + new_version = head->dpos_irreversible_blocknum >= upo && upo > 0; } catch( const boost::exception& e) { - wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + wlog("get upo failed, regenerating..."); db.create([](auto&){}); } @@ -1606,12 +1601,10 @@ struct controller_impl { auto new_version = false; try { - const auto& upo = db.get(); - if (upo.upgrade_target_block_num > head->block_num) { - new_version = head->dpos_irreversible_blocknum >= upo.upgrade_target_block_num; - } + const auto& upo = db.get().upgrade_target_block_num; + new_version = head->dpos_irreversible_blocknum >= upo && upo > 0; } catch( const boost::exception& e) { - wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + wlog("get upo failed, regenerating..."); db.create([](auto&){}); } diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 83440405c59..a1ac695140b 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -198,6 +198,8 @@ class privileged_api : public context_aware_api { uint32_t target_num; fc::raw::unpack(ds, target_num); + EOS_ASSERT( context.control.head_block_num() < target_num - 100, wasm_execution_error, "upgrade target block is too close"); + context.db.modify( context.control.get_upgrade_properties(), [&]( auto& uprops ) { uprops.upgrade_target_block_num = target_num; diff --git a/plugins/pbft_plugin/pbft_plugin.cpp b/plugins/pbft_plugin/pbft_plugin.cpp index 82dad7e852d..171b30a67be 100644 --- a/plugins/pbft_plugin/pbft_plugin.cpp +++ b/plugins/pbft_plugin/pbft_plugin.cpp @@ -136,11 +136,9 @@ namespace eosio { try { const auto& upo = chain.get_upgrade_properties().upgrade_target_block_num; - if (upo > chain.head_block_num()) { - new_version = chain.last_irreversible_block_num() >= upo; - } + new_version = chain.last_irreversible_block_num() >= upo && upo > 0; } catch( const boost::exception& e) { - wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + wlog("get upo failed, regenerating..."); } return (new_version && (!is_syncing() && !is_replaying())); diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index e75a4705cd6..b91758404b3 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -228,12 +228,10 @@ class producer_plugin_impl : public std::enable_shared_from_this chain.head_block_num()) { - new_version = chain.last_irreversible_block_num() >= upo.upgrade_target_block_num; - } + const auto& upo = chain.get_upgrade_properties().upgrade_target_block_num; + new_version = chain.last_irreversible_block_num() >= upo && upo > 0; } catch( const boost::exception& e) { - wlog("get upo failed: ${e}, regenerating...", ("e", boost::diagnostic_information(e))); + wlog("get upo failed, regenerating..."); } auto new_bs = bsp->generate_next(new_block_header.timestamp, new_version); From 2e9676614d62d89c689d9fa14bd8a2de439360e3 Mon Sep 17 00:00:00 2001 From: oldcold Date: Thu, 4 Apr 2019 16:43:27 +0800 Subject: [PATCH 14/59] some code reformat --- libraries/chain/controller.cpp | 69 +++++++------------ .../chain/include/eosio/chain/controller.hpp | 1 + plugins/pbft_plugin/pbft_plugin.cpp | 28 +++++--- plugins/producer_plugin/producer_plugin.cpp | 9 +-- 4 files changed, 45 insertions(+), 62 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index c6e6009d608..1310f589d65 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -777,6 +777,18 @@ struct controller_impl { // "bos end" + bool is_new_version() { + try { + const auto& upo = db.get().upgrade_target_block_num; +// dlog("upgrade target block num: ${n}", ("n", upo)); + return head->dpos_irreversible_blocknum >= upo && upo > 0; + } catch( const boost::exception& e) { + wlog("no upo found, regenerating..."); + db.create([](auto&){}); + return false; + } + } + /** * @post regardless of the success of commit block there is no active pending block */ @@ -793,15 +805,7 @@ struct controller_impl { if (add_to_fork_db) { pending->_pending_block_state->validated = true; - auto new_version = false; - - try { - const auto& upo = db.get().upgrade_target_block_num; - new_version = head->dpos_irreversible_blocknum >= upo && upo > 0; - } catch( const boost::exception& e) { - wlog("get upo failed, regenerating..."); - db.create([](auto&){}); - } + auto new_version = is_new_version(); auto new_bsp = fork_db.add(pending->_pending_block_state, true); emit(self.accepted_block_header, pending->_pending_block_state); @@ -1251,16 +1255,13 @@ struct controller_impl { pending.emplace(maybe_session()); } - auto new_version = false; + auto new_version = is_new_version(); auto upgrading = false; try { const auto& upo = db.get().upgrade_target_block_num; - ilog("upgrade target block num: ${n}", ("n", upo)); - new_version = head->dpos_irreversible_blocknum >= upo && upo > 0; upgrading = (head->block_num + 1 >= upo) && head->dpos_irreversible_blocknum <= upo + 12; } catch( const boost::exception& e) { - wlog("get upo failed, regenerating..."); db.create([](auto&){}); } @@ -1461,15 +1462,7 @@ struct controller_impl { auto prev = fork_db.get_block( b->previous ); EOS_ASSERT( prev, unlinkable_block_exception, "unlinkable block ${id}", ("id", id)("previous", b->previous) ); - auto new_version = false; - - try { - const auto& upo = db.get().upgrade_target_block_num; - new_version = head->dpos_irreversible_blocknum >= upo && upo > 0; - } catch( const boost::exception& e) { - wlog("get upo failed, regenerating..."); - db.create([](auto&){}); - } + auto new_version = is_new_version(); return async_thread_pool( thread_pool, [b, prev, new_version]() { const bool skip_validate_signee = false; @@ -1519,15 +1512,7 @@ struct controller_impl { emit( self.pre_accepted_block, b ); const bool skip_validate_signee = !conf.force_all_checks; - auto new_version = false; - - try { - const auto& upo = db.get().upgrade_target_block_num; - new_version = head->dpos_irreversible_blocknum >= upo && upo > 0; - } catch( const boost::exception& e) { - wlog("get upo failed, regenerating..."); - db.create([](auto&){}); - } + auto new_version = is_new_version(); auto new_header_state = fork_db.add( b, skip_validate_signee, new_version); @@ -1598,15 +1583,7 @@ struct controller_impl { void maybe_switch_forks( controller::block_status s ) { - auto new_version = false; - - try { - const auto& upo = db.get().upgrade_target_block_num; - new_version = head->dpos_irreversible_blocknum >= upo && upo > 0; - } catch( const boost::exception& e) { - wlog("get upo failed, regenerating..."); - db.create([](auto&){}); - } + auto new_version = is_new_version(); if (new_version) { if (pbft_prepared) { @@ -2468,8 +2445,8 @@ void controller::set_pbft_prepared(const block_id_type& id) const { my->pbft_prepared = bs; my->fork_db.mark_pbft_prepared_fork(bs); } - dlog( "fork_db head ${h}", ("h", fork_db().head()->id)); - dlog( "prepared block id ${b}", ("b", id)); +// dlog( "fork_db head ${h}", ("h", fork_db().head()->id)); +// dlog( "prepared block id ${b}", ("b", id)); } void controller::set_pbft_my_prepare(const block_id_type& id) const { @@ -2479,8 +2456,8 @@ void controller::set_pbft_my_prepare(const block_id_type& id) const { my->my_prepare = bs; my->fork_db.mark_pbft_my_prepare_fork(bs); } - dlog( "fork_db head ${h}", ("h", fork_db().head()->id)); - dlog( "my prepare block id ${b}", ("b", id)); +// dlog( "fork_db head ${h}", ("h", fork_db().head()->id)); +// dlog( "my prepare block id ${b}", ("b", id)); } block_id_type controller::get_pbft_my_prepare() const { @@ -2665,4 +2642,8 @@ void controller::set_lib() const { const upgrade_property_object& controller::get_upgrade_properties()const { return my->db.get(); } + +bool controller::is_upgraded() const { + return my->is_new_version(); +} } } /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 8630ae21d65..c8d89f448c7 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -308,6 +308,7 @@ namespace eosio { namespace chain { void set_lib()const; const upgrade_property_object& get_upgrade_properties()const; + bool is_upgraded()const; /* signal pre_apply_block; diff --git a/plugins/pbft_plugin/pbft_plugin.cpp b/plugins/pbft_plugin/pbft_plugin.cpp index 171b30a67be..a5caa667691 100644 --- a/plugins/pbft_plugin/pbft_plugin.cpp +++ b/plugins/pbft_plugin/pbft_plugin.cpp @@ -33,9 +33,10 @@ namespace eosio { void checkpoint_timer_tick(); private: - static bool is_replaying(); - static bool is_syncing(); - static bool pbft_ready(); + bool upgraded = false; + bool is_replaying(); + bool is_syncing(); + bool pbft_ready(); }; pbft_plugin::pbft_plugin() : my(new pbft_plugin_impl()) {} @@ -131,14 +132,21 @@ namespace eosio { bool pbft_plugin_impl::pbft_ready() { // only trigger pbft related logic if I am in sync and replayed. - auto new_version = false; auto& chain = app().get_plugin().chain(); - - try { - const auto& upo = chain.get_upgrade_properties().upgrade_target_block_num; - new_version = chain.last_irreversible_block_num() >= upo && upo > 0; - } catch( const boost::exception& e) { - wlog("get upo failed, regenerating..."); + auto new_version = chain.is_upgraded(); + + if (new_version && !upgraded) { + wlog( "\n" + "*********** PBFT ENABLED ***********\n" + "* *\n" + "* -- The blockchain -- *\n" + "* - has successfully switched - *\n" + "* - into the new version - *\n" + "* - Please enjoy a - *\n" + "* - better performance! - *\n" + "* *\n" + "************************************\n" ); + upgraded = true; } return (new_version && (!is_syncing() && !is_replaying())); diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index b91758404b3..0a9b1e99bf4 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -225,14 +225,7 @@ class producer_plugin_impl : public std::enable_shared_from_thisid; - auto new_version = false; - - try { - const auto& upo = chain.get_upgrade_properties().upgrade_target_block_num; - new_version = chain.last_irreversible_block_num() >= upo && upo > 0; - } catch( const boost::exception& e) { - wlog("get upo failed, regenerating..."); - } + auto new_version = chain.is_upgraded(); auto new_bs = bsp->generate_next(new_block_header.timestamp, new_version); From d424438df39955d337b544815275a11ca4be60fa Mon Sep 17 00:00:00 2001 From: oldcold Date: Thu, 4 Apr 2019 17:14:14 +0800 Subject: [PATCH 15/59] add newly built system contract to `contracts/eosio.system` folder --- contracts/eosio.system/eosio.system.abi | 2058 ++++++++++++++++------ contracts/eosio.system/eosio.system.wasm | Bin 0 -> 174107 bytes 2 files changed, 1481 insertions(+), 577 deletions(-) create mode 100755 contracts/eosio.system/eosio.system.wasm diff --git a/contracts/eosio.system/eosio.system.abi b/contracts/eosio.system/eosio.system.abi index 87937c787f9..6da34ae4796 100644 --- a/contracts/eosio.system/eosio.system.abi +++ b/contracts/eosio.system/eosio.system.abi @@ -1,578 +1,1482 @@ { - "version": "eosio::abi/1.0", - "types": [{ - "new_type_name": "account_name", - "type": "name" - },{ - "new_type_name": "permission_name", - "type": "name" - },{ - "new_type_name": "action_name", - "type": "name" - },{ - "new_type_name": "transaction_id_type", - "type": "checksum256" - },{ - "new_type_name": "weight_type", - "type": "uint16" - }], - "____comment": "eosio.bios structs: set_account_limits, setpriv, set_global_limits, producer_key, set_producers, require_auth are provided so abi available for deserialization in future.", - "structs": [{ - "name": "permission_level", - "base": "", - "fields": [ - {"name":"actor", "type":"account_name"}, - {"name":"permission", "type":"permission_name"} - ] - },{ - "name": "key_weight", - "base": "", - "fields": [ - {"name":"key", "type":"public_key"}, - {"name":"weight", "type":"weight_type"} - ] - },{ - "name": "bidname", - "base": "", - "fields": [ - {"name":"bidder", "type":"account_name"}, - {"name":"newname", "type":"account_name"}, - {"name":"bid", "type":"asset"} - ] - },{ - "name": "permission_level_weight", - "base": "", - "fields": [ - {"name":"permission", "type":"permission_level"}, - {"name":"weight", "type":"weight_type"} - ] - },{ - "name": "wait_weight", - "base": "", - "fields": [ - {"name":"wait_sec", "type":"uint32"}, - {"name":"weight", "type":"weight_type"} - ] - },{ - "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": "newaccount", - "base": "", - "fields": [ - {"name":"creator", "type":"account_name"}, - {"name":"name", "type":"account_name"}, - {"name":"owner", "type":"authority"}, - {"name":"active", "type":"authority"} - ] - },{ - "name": "setcode", - "base": "", - "fields": [ - {"name":"account", "type":"account_name"}, - {"name":"vmtype", "type":"uint8"}, - {"name":"vmversion", "type":"uint8"}, - {"name":"code", "type":"bytes"} - ] - },{ - "name": "setabi", - "base": "", - "fields": [ - {"name":"account", "type":"account_name"}, - {"name":"abi", "type":"bytes"} - ] - },{ - "name": "updateauth", - "base": "", - "fields": [ - {"name":"account", "type":"account_name"}, - {"name":"permission", "type":"permission_name"}, - {"name":"parent", "type":"permission_name"}, - {"name":"auth", "type":"authority"} - ] - },{ - "name": "deleteauth", - "base": "", - "fields": [ - {"name":"account", "type":"account_name"}, - {"name":"permission", "type":"permission_name"} - ] - },{ - "name": "linkauth", - "base": "", - "fields": [ - {"name":"account", "type":"account_name"}, - {"name":"code", "type":"account_name"}, - {"name":"type", "type":"action_name"}, - {"name":"requirement", "type":"permission_name"} - ] - },{ - "name": "unlinkauth", - "base": "", - "fields": [ - {"name":"account", "type":"account_name"}, - {"name":"code", "type":"account_name"}, - {"name":"type", "type":"action_name"} - ] - },{ - "name": "canceldelay", - "base": "", - "fields": [ - {"name":"canceling_auth", "type":"permission_level"}, - {"name":"trx_id", "type":"transaction_id_type"} - ] - },{ - "name": "onerror", - "base": "", - "fields": [ - {"name":"sender_id", "type":"uint128"}, - {"name":"sent_trx", "type":"bytes"} - ] - },{ - "name": "buyrambytes", - "base": "", - "fields": [ - {"name":"payer", "type":"account_name"}, - {"name":"receiver", "type":"account_name"}, - {"name":"bytes", "type":"uint32"} - ] - },{ - "name": "sellram", - "base": "", - "fields": [ - {"name":"account", "type":"account_name"}, - {"name":"bytes", "type":"uint64"} - ] - },{ - "name": "buyram", - "base": "", - "fields": [ - {"name":"payer", "type":"account_name"}, - {"name":"receiver", "type":"account_name"}, - {"name":"quant", "type":"asset"} - ] - },{ - "name": "delegatebw", - "base": "", - "fields": [ - {"name":"from", "type":"account_name"}, - {"name":"receiver", "type":"account_name"}, - {"name":"stake_net_quantity", "type":"asset"}, - {"name":"stake_cpu_quantity", "type":"asset"}, - {"name":"transfer", "type":"bool"} - ] - },{ - "name": "undelegatebw", - "base": "", - "fields": [ - {"name":"from", "type":"account_name"}, - {"name":"receiver", "type":"account_name"}, - {"name":"unstake_net_quantity", "type":"asset"}, - {"name":"unstake_cpu_quantity", "type":"asset"} - ] - },{ - "name": "refund", - "base": "", - "fields": [ - {"name":"owner", "type":"account_name"} - ] - },{ - "name": "delegated_bandwidth", - "base": "", - "fields": [ - {"name":"from", "type":"account_name"}, - {"name":"to", "type":"account_name"}, - {"name":"net_weight", "type":"asset"}, - {"name":"cpu_weight", "type":"asset"} - ] - },{ - "name": "user_resources", - "base": "", - "fields": [ - {"name":"owner", "type":"account_name"}, - {"name":"net_weight", "type":"asset"}, - {"name":"cpu_weight", "type":"asset"}, - {"name":"ram_bytes", "type":"uint64"} - ] - },{ - "name": "total_resources", - "base": "", - "fields": [ - {"name":"owner", "type":"account_name"}, - {"name":"net_weight", "type":"asset"}, - {"name":"cpu_weight", "type":"asset"}, - {"name":"ram_bytes", "type":"uint64"} - ] - },{ - "name": "refund_request", - "base": "", - "fields": [ - {"name":"owner", "type":"account_name"}, - {"name":"request_time", "type":"time_point_sec"}, - {"name":"net_amount", "type":"asset"}, - {"name":"cpu_amount", "type":"asset"} - ] - },{ - "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": "eosio_global_state", - "base": "blockchain_parameters", - "fields": [ - {"name":"max_ram_size", "type":"uint64"}, - {"name":"total_ram_bytes_reserved", "type":"uint64"}, - {"name":"total_ram_stake", "type":"int64"}, - {"name":"last_producer_schedule_update", "type":"block_timestamp_type"}, - {"name":"last_pervote_bucket_fill", "type":"uint64"}, - {"name":"pervote_bucket", "type":"int64"}, - {"name":"perblock_bucket", "type":"int64"}, - {"name":"total_unpaid_blocks", "type":"uint32"}, - {"name":"total_activated_stake", "type":"int64"}, - {"name":"thresh_activated_stake_time", "type":"uint64"}, - {"name":"last_producer_schedule_size", "type":"uint16"}, - {"name":"total_producer_vote_weight", "type":"float64"}, - {"name":"last_name_close", "type":"block_timestamp_type"} - ] - },{ - "name": "producer_info", - "base": "", - "fields": [ - {"name":"owner", "type":"account_name"}, - {"name":"total_votes", "type":"float64"}, - {"name":"producer_key", "type":"public_key"}, - {"name":"is_active", "type":"bool"}, - {"name":"url", "type":"string"}, - {"name":"unpaid_blocks", "type":"uint32"}, - {"name":"last_claim_time", "type":"uint64"}, - {"name":"location", "type":"uint16"} - ] - },{ - "name": "regproducer", - "base": "", - "fields": [ - {"name":"producer", "type":"account_name"}, - {"name":"producer_key", "type":"public_key"}, - {"name":"url", "type":"string"}, - {"name":"location", "type":"uint16"} - ] - },{ - "name": "unregprod", - "base": "", - "fields": [ - {"name":"producer", "type":"account_name"} - ] - },{ - "name": "setram", - "base": "", - "fields": [ - {"name":"max_ram_size", "type":"uint64"} - ] - },{ - "name": "regproxy", - "base": "", - "fields": [ - {"name":"proxy", "type":"account_name"}, - {"name":"isproxy", "type":"bool"} - ] - },{ - "name": "voteproducer", - "base": "", - "fields": [ - {"name":"voter", "type":"account_name"}, - {"name":"proxy", "type":"account_name"}, - {"name":"producers", "type":"account_name[]"} - ] - },{ - "name": "voter_info", - "base": "", - "fields": [ - {"name":"owner", "type":"account_name"}, - {"name":"proxy", "type":"account_name"}, - {"name":"producers", "type":"account_name[]"}, - {"name":"staked", "type":"int64"}, - {"name":"last_vote_weight", "type":"float64"}, - {"name":"proxied_vote_weight", "type":"float64"}, - {"name":"is_proxy", "type":"bool"} - ] - },{ - "name": "claimrewards", - "base": "", - "fields": [ - {"name":"owner", "type":"account_name"} - ] - },{ - "name": "setpriv", - "base": "", - "fields": [ - {"name":"account", "type":"account_name"}, - {"name":"is_priv", "type":"int8"} - ] - },{ - "name": "rmvproducer", - "base": "", - "fields": [ - {"name":"producer", "type":"account_name"} - ] - },{ - "name": "set_account_limits", - "base": "", - "fields": [ - {"name":"account", "type":"account_name"}, - {"name":"ram_bytes", "type":"int64"}, - {"name":"net_weight", "type":"int64"}, - {"name":"cpu_weight", "type":"int64"} - ] - },{ - "name": "set_global_limits", - "base": "", - "fields": [ - {"name":"cpu_usec_per_period", "type":"int64"} - ] - },{ - "name": "producer_key", - "base": "", - "fields": [ - {"name":"producer_name", "type":"account_name"}, - {"name":"block_signing_key", "type":"public_key"} - ] - },{ - "name": "set_producers", - "base": "", - "fields": [ - {"name":"schedule", "type":"producer_key[]"} - ] - },{ - "name": "require_auth", - "base": "", - "fields": [ - {"name":"from", "type":"account_name"} - ] - },{ - "name": "setparams", - "base": "", - "fields": [ - {"name":"params", "type":"blockchain_parameters"} - ] - },{ - "name": "connector", - "base": "", - "fields": [ - {"name":"balance", "type":"asset"}, - {"name":"weight", "type":"float64"} - ] - },{ - "name": "exchange_state", - "base": "", - "fields": [ - {"name":"supply", "type":"asset"}, - {"name":"base", "type":"connector"}, - {"name":"quote", "type":"connector"} - ] - }, { - "name": "namebid_info", - "base": "", - "fields": [ - {"name":"newname", "type":"account_name"}, - {"name":"high_bidder", "type":"account_name"}, - {"name":"high_bid", "type":"int64"}, - {"name":"last_bid_time", "type":"uint64"} - ] - } - ], - "actions": [{ - "name": "newaccount", - "type": "newaccount", - "ricardian_contract": "" - },{ - "name": "setcode", - "type": "setcode", - "ricardian_contract": "" - },{ - "name": "setabi", - "type": "setabi", - "ricardian_contract": "" - },{ - "name": "updateauth", - "type": "updateauth", - "ricardian_contract": "" - },{ - "name": "deleteauth", - "type": "deleteauth", - "ricardian_contract": "" - },{ - "name": "linkauth", - "type": "linkauth", - "ricardian_contract": "" - },{ - "name": "unlinkauth", - "type": "unlinkauth", - "ricardian_contract": "" - },{ - "name": "canceldelay", - "type": "canceldelay", - "ricardian_contract": "" - },{ - "name": "onerror", - "type": "onerror", - "ricardian_contract": "" - },{ - "name": "buyrambytes", - "type": "buyrambytes", - "ricardian_contract": "" - },{ - "name": "buyram", - "type": "buyram", - "ricardian_contract": "" - },{ - "name": "sellram", - "type": "sellram", - "ricardian_contract": "" - },{ - "name": "delegatebw", - "type": "delegatebw", - "ricardian_contract": "" - },{ - "name": "undelegatebw", - "type": "undelegatebw", - "ricardian_contract": "" - },{ - "name": "refund", - "type": "refund", - "ricardian_contract": "" - },{ - "name": "regproducer", - "type": "regproducer", - "ricardian_contract": "" - },{ - "name": "setram", - "type": "setram", - "ricardian_contract": "" - },{ - "name": "bidname", - "type": "bidname", - "ricardian_contract": "" - },{ - "name": "unregprod", - "type": "unregprod", - "ricardian_contract": "" - },{ - "name": "regproxy", - "type": "regproxy", - "ricardian_contract": "" - },{ - "name": "voteproducer", - "type": "voteproducer", - "ricardian_contract": "" - },{ - "name": "claimrewards", - "type": "claimrewards", - "ricardian_contract": "" - },{ - "name": "setpriv", - "type": "setpriv", - "ricardian_contract": "" - },{ - "name": "rmvproducer", - "type": "rmvproducer", - "ricardian_contract": "" - },{ - "name": "setalimits", - "type": "set_account_limits", - "ricardian_contract": "" - },{ - "name": "setglimits", - "type": "set_global_limits", - "ricardian_contract": "" - },{ - "name": "setprods", - "type": "set_producers", - "ricardian_contract": "" - },{ - "name": "reqauth", - "type": "require_auth", - "ricardian_contract": "" - },{ - "name": "setparams", - "type": "setparams", - "ricardian_contract": "" - }], - "tables": [{ - "name": "producers", - "type": "producer_info", - "index_type": "i64", - "key_names" : ["owner"], - "key_types" : ["uint64"] - },{ - "name": "global", - "type": "eosio_global_state", - "index_type": "i64", - "key_names" : [], - "key_types" : [] - },{ - "name": "voters", - "type": "voter_info", - "index_type": "i64", - "key_names" : ["owner"], - "key_types" : ["account_name"] - },{ - "name": "userres", - "type": "user_resources", - "index_type": "i64", - "key_names" : ["owner"], - "key_types" : ["uint64"] - },{ - "name": "delband", - "type": "delegated_bandwidth", - "index_type": "i64", - "key_names" : ["to"], - "key_types" : ["uint64"] - },{ - "name": "rammarket", - "type": "exchange_state", - "index_type": "i64", - "key_names" : ["supply"], - "key_types" : ["uint64"] - },{ - "name": "refunds", - "type": "refund_request", - "index_type": "i64", - "key_names" : ["owner"], - "key_types" : ["uint64"] - },{ - "name": "namebids", - "type": "namebid_info", - "index_type": "i64", - "key_names" : ["newname"], - "key_types" : ["account_name"] - } - ], - "ricardian_clauses": [], - "abi_extensions": [] -} + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT Thu Apr 4 16:08:35 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": "bid_refund", + "base": "", + "fields": [ + { + "name": "bidder", + "type": "name" + }, + { + "name": "amount", + "type": "asset" + } + ] + }, + { + "name": "bidname", + "base": "", + "fields": [ + { + "name": "bidder", + "type": "name" + }, + { + "name": "newname", + "type": "name" + }, + { + "name": "bid", + "type": "asset" + } + ] + }, + { + "name": "bidrefund", + "base": "", + "fields": [ + { + "name": "bidder", + "type": "name" + }, + { + "name": "newname", + "type": "name" + } + ] + }, + { + "name": "block_header", + "base": "", + "fields": [ + { + "name": "timestamp", + "type": "uint32" + }, + { + "name": "producer", + "type": "name" + }, + { + "name": "confirmed", + "type": "uint16" + }, + { + "name": "previous", + "type": "checksum256" + }, + { + "name": "transaction_mroot", + "type": "checksum256" + }, + { + "name": "action_mroot", + "type": "checksum256" + }, + { + "name": "schedule_version", + "type": "uint32" + }, + { + "name": "new_producers", + "type": "producer_schedule?" + } + ] + }, + { + "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": "buyram", + "base": "", + "fields": [ + { + "name": "payer", + "type": "name" + }, + { + "name": "receiver", + "type": "name" + }, + { + "name": "quant", + "type": "asset" + } + ] + }, + { + "name": "buyrambytes", + "base": "", + "fields": [ + { + "name": "payer", + "type": "name" + }, + { + "name": "receiver", + "type": "name" + }, + { + "name": "bytes", + "type": "uint32" + } + ] + }, + { + "name": "canceldelay", + "base": "", + "fields": [ + { + "name": "canceling_auth", + "type": "permission_level" + }, + { + "name": "trx_id", + "type": "checksum256" + } + ] + }, + { + "name": "claimrewards", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + } + ] + }, + { + "name": "connector", + "base": "", + "fields": [ + { + "name": "balance", + "type": "asset" + }, + { + "name": "weight", + "type": "float64" + } + ] + }, + { + "name": "delegatebw", + "base": "", + "fields": [ + { + "name": "from", + "type": "name" + }, + { + "name": "receiver", + "type": "name" + }, + { + "name": "stake_net_quantity", + "type": "asset" + }, + { + "name": "stake_cpu_quantity", + "type": "asset" + }, + { + "name": "transfer", + "type": "bool" + } + ] + }, + { + "name": "delegated_bandwidth", + "base": "", + "fields": [ + { + "name": "from", + "type": "name" + }, + { + "name": "to", + "type": "name" + }, + { + "name": "net_weight", + "type": "asset" + }, + { + "name": "cpu_weight", + "type": "asset" + } + ] + }, + { + "name": "deleteauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "permission", + "type": "name" + } + ] + }, + { + "name": "eosio_global_state", + "base": "blockchain_parameters", + "fields": [ + { + "name": "max_ram_size", + "type": "uint64" + }, + { + "name": "total_ram_bytes_reserved", + "type": "uint64" + }, + { + "name": "total_ram_stake", + "type": "int64" + }, + { + "name": "last_producer_schedule_update", + "type": "block_timestamp_type" + }, + { + "name": "last_pervote_bucket_fill", + "type": "time_point" + }, + { + "name": "pervote_bucket", + "type": "int64" + }, + { + "name": "perblock_bucket", + "type": "int64" + }, + { + "name": "total_unpaid_blocks", + "type": "uint32" + }, + { + "name": "total_activated_stake", + "type": "int64" + }, + { + "name": "thresh_activated_stake_time", + "type": "time_point" + }, + { + "name": "last_producer_schedule_size", + "type": "uint16" + }, + { + "name": "total_producer_vote_weight", + "type": "float64" + }, + { + "name": "last_name_close", + "type": "block_timestamp_type" + } + ] + }, + { + "name": "eosio_global_state2", + "base": "", + "fields": [ + { + "name": "new_ram_per_block", + "type": "uint16" + }, + { + "name": "last_ram_increase", + "type": "block_timestamp_type" + }, + { + "name": "last_block_num", + "type": "block_timestamp_type" + }, + { + "name": "total_producer_votepay_share", + "type": "float64" + }, + { + "name": "revision", + "type": "uint8" + } + ] + }, + { + "name": "eosio_global_state3", + "base": "", + "fields": [ + { + "name": "last_vpay_state_update", + "type": "time_point" + }, + { + "name": "total_vpay_share_change_rate", + "type": "float64" + } + ] + }, + { + "name": "eosio_guaranteed_min_res", + "base": "", + "fields": [ + { + "name": "ram", + "type": "uint32" + }, + { + "name": "cpu", + "type": "uint32" + }, + { + "name": "net", + "type": "uint32" + } + ] + }, + { + "name": "exchange_state", + "base": "", + "fields": [ + { + "name": "supply", + "type": "asset" + }, + { + "name": "base", + "type": "connector" + }, + { + "name": "quote", + "type": "connector" + } + ] + }, + { + "name": "init", + "base": "", + "fields": [ + { + "name": "version", + "type": "varuint32" + }, + { + "name": "core", + "type": "symbol" + } + ] + }, + { + "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": "name_bid", + "base": "", + "fields": [ + { + "name": "newname", + "type": "name" + }, + { + "name": "high_bidder", + "type": "name" + }, + { + "name": "high_bid", + "type": "int64" + }, + { + "name": "last_bid_time", + "type": "time_point" + } + ] + }, + { + "name": "namelist", + "base": "", + "fields": [ + { + "name": "list", + "type": "string" + }, + { + "name": "action", + "type": "string" + }, + { + "name": "names", + "type": "name[]" + } + ] + }, + { + "name": "newaccount", + "base": "", + "fields": [ + { + "name": "creator", + "type": "name" + }, + { + "name": "newact", + "type": "name" + }, + { + "name": "owner", + "type": "authority" + }, + { + "name": "active", + "type": "authority" + } + ] + }, + { + "name": "onblock", + "base": "", + "fields": [ + { + "name": "header", + "type": "block_header" + } + ] + }, + { + "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_info", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "total_votes", + "type": "float64" + }, + { + "name": "producer_key", + "type": "public_key" + }, + { + "name": "is_active", + "type": "bool" + }, + { + "name": "url", + "type": "string" + }, + { + "name": "unpaid_blocks", + "type": "uint32" + }, + { + "name": "last_claim_time", + "type": "time_point" + }, + { + "name": "location", + "type": "uint16" + } + ] + }, + { + "name": "producer_info2", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "votepay_share", + "type": "float64" + }, + { + "name": "last_votepay_share_update", + "type": "time_point" + } + ] + }, + { + "name": "producer_key", + "base": "", + "fields": [ + { + "name": "producer_name", + "type": "name" + }, + { + "name": "block_signing_key", + "type": "public_key" + } + ] + }, + { + "name": "producer_schedule", + "base": "", + "fields": [ + { + "name": "version", + "type": "uint32" + }, + { + "name": "producers", + "type": "producer_key[]" + } + ] + }, + { + "name": "refund", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + } + ] + }, + { + "name": "refund_request", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "request_time", + "type": "time_point_sec" + }, + { + "name": "net_amount", + "type": "asset" + }, + { + "name": "cpu_amount", + "type": "asset" + } + ] + }, + { + "name": "regproducer", + "base": "", + "fields": [ + { + "name": "producer", + "type": "name" + }, + { + "name": "producer_key", + "type": "public_key" + }, + { + "name": "url", + "type": "string" + }, + { + "name": "location", + "type": "uint16" + } + ] + }, + { + "name": "regproxy", + "base": "", + "fields": [ + { + "name": "proxy", + "type": "name" + }, + { + "name": "isproxy", + "type": "bool" + } + ] + }, + { + "name": "rmvproducer", + "base": "", + "fields": [ + { + "name": "producer", + "type": "name" + } + ] + }, + { + "name": "sellram", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "bytes", + "type": "int64" + } + ] + }, + { + "name": "setabi", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "abi", + "type": "bytes" + } + ] + }, + { + "name": "setacctcpu", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "cpu_weight", + "type": "int64?" + } + ] + }, + { + "name": "setacctnet", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "net_weight", + "type": "int64?" + } + ] + }, + { + "name": "setacctram", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "ram_bytes", + "type": "int64?" + } + ] + }, + { + "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": "setguaminres", + "base": "", + "fields": [ + { + "name": "ram", + "type": "uint32" + }, + { + "name": "cpu", + "type": "uint32" + }, + { + "name": "net", + "type": "uint32" + } + ] + }, + { + "name": "setparams", + "base": "", + "fields": [ + { + "name": "params", + "type": "blockchain_parameters" + } + ] + }, + { + "name": "setpriv", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "is_priv", + "type": "uint8" + } + ] + }, + { + "name": "setram", + "base": "", + "fields": [ + { + "name": "max_ram_size", + "type": "uint64" + } + ] + }, + { + "name": "setramrate", + "base": "", + "fields": [ + { + "name": "bytes_per_block", + "type": "uint16" + } + ] + }, + { + "name": "setupgrade", + "base": "", + "fields": [ + { + "name": "params", + "type": "upgrade_parameters" + } + ] + }, + { + "name": "undelegatebw", + "base": "", + "fields": [ + { + "name": "from", + "type": "name" + }, + { + "name": "receiver", + "type": "name" + }, + { + "name": "unstake_net_quantity", + "type": "asset" + }, + { + "name": "unstake_cpu_quantity", + "type": "asset" + } + ] + }, + { + "name": "unlinkauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "code", + "type": "name" + }, + { + "name": "type", + "type": "name" + } + ] + }, + { + "name": "unregprod", + "base": "", + "fields": [ + { + "name": "producer", + "type": "name" + } + ] + }, + { + "name": "updateauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "permission", + "type": "name" + }, + { + "name": "parent", + "type": "name" + }, + { + "name": "auth", + "type": "authority" + } + ] + }, + { + "name": "updtrevision", + "base": "", + "fields": [ + { + "name": "revision", + "type": "uint8" + } + ] + }, + { + "name": "upgrade_parameters", + "base": "", + "fields": [ + { + "name": "target_block_num", + "type": "uint32" + } + ] + }, + { + "name": "upgrade_state", + "base": "upgrade_parameters", + "fields": [ + { + "name": "current_version", + "type": "uint16" + } + ] + }, + { + "name": "user_resources", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "net_weight", + "type": "asset" + }, + { + "name": "cpu_weight", + "type": "asset" + }, + { + "name": "ram_bytes", + "type": "int64" + } + ] + }, + { + "name": "voteproducer", + "base": "", + "fields": [ + { + "name": "voter", + "type": "name" + }, + { + "name": "proxy", + "type": "name" + }, + { + "name": "producers", + "type": "name[]" + } + ] + }, + { + "name": "voter_info", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "proxy", + "type": "name" + }, + { + "name": "producers", + "type": "name[]" + }, + { + "name": "staked", + "type": "int64" + }, + { + "name": "last_vote_weight", + "type": "float64" + }, + { + "name": "proxied_vote_weight", + "type": "float64" + }, + { + "name": "is_proxy", + "type": "bool" + }, + { + "name": "flags1", + "type": "uint32" + }, + { + "name": "reserved2", + "type": "uint32" + }, + { + "name": "reserved3", + "type": "asset" + } + ] + }, + { + "name": "wait_weight", + "base": "", + "fields": [ + { + "name": "wait_sec", + "type": "uint32" + }, + { + "name": "weight", + "type": "uint16" + } + ] + } + ], + "types": [], + "actions": [ + { + "name": "bidname", + "type": "bidname", + "ricardian_contract": "" + }, + { + "name": "bidrefund", + "type": "bidrefund", + "ricardian_contract": "" + }, + { + "name": "buyram", + "type": "buyram", + "ricardian_contract": "" + }, + { + "name": "buyrambytes", + "type": "buyrambytes", + "ricardian_contract": "" + }, + { + "name": "canceldelay", + "type": "canceldelay", + "ricardian_contract": "" + }, + { + "name": "claimrewards", + "type": "claimrewards", + "ricardian_contract": "" + }, + { + "name": "delegatebw", + "type": "delegatebw", + "ricardian_contract": "" + }, + { + "name": "deleteauth", + "type": "deleteauth", + "ricardian_contract": "" + }, + { + "name": "init", + "type": "init", + "ricardian_contract": "" + }, + { + "name": "linkauth", + "type": "linkauth", + "ricardian_contract": "" + }, + { + "name": "namelist", + "type": "namelist", + "ricardian_contract": "" + }, + { + "name": "newaccount", + "type": "newaccount", + "ricardian_contract": "" + }, + { + "name": "onblock", + "type": "onblock", + "ricardian_contract": "" + }, + { + "name": "onerror", + "type": "onerror", + "ricardian_contract": "" + }, + { + "name": "refund", + "type": "refund", + "ricardian_contract": "" + }, + { + "name": "regproducer", + "type": "regproducer", + "ricardian_contract": "" + }, + { + "name": "regproxy", + "type": "regproxy", + "ricardian_contract": "" + }, + { + "name": "rmvproducer", + "type": "rmvproducer", + "ricardian_contract": "" + }, + { + "name": "sellram", + "type": "sellram", + "ricardian_contract": "" + }, + { + "name": "setabi", + "type": "setabi", + "ricardian_contract": "" + }, + { + "name": "setacctcpu", + "type": "setacctcpu", + "ricardian_contract": "" + }, + { + "name": "setacctnet", + "type": "setacctnet", + "ricardian_contract": "" + }, + { + "name": "setacctram", + "type": "setacctram", + "ricardian_contract": "" + }, + { + "name": "setalimits", + "type": "setalimits", + "ricardian_contract": "" + }, + { + "name": "setcode", + "type": "setcode", + "ricardian_contract": "" + }, + { + "name": "setguaminres", + "type": "setguaminres", + "ricardian_contract": "" + }, + { + "name": "setparams", + "type": "setparams", + "ricardian_contract": "" + }, + { + "name": "setpriv", + "type": "setpriv", + "ricardian_contract": "" + }, + { + "name": "setram", + "type": "setram", + "ricardian_contract": "" + }, + { + "name": "setramrate", + "type": "setramrate", + "ricardian_contract": "" + }, + { + "name": "setupgrade", + "type": "setupgrade", + "ricardian_contract": "" + }, + { + "name": "undelegatebw", + "type": "undelegatebw", + "ricardian_contract": "" + }, + { + "name": "unlinkauth", + "type": "unlinkauth", + "ricardian_contract": "" + }, + { + "name": "unregprod", + "type": "unregprod", + "ricardian_contract": "" + }, + { + "name": "updateauth", + "type": "updateauth", + "ricardian_contract": "" + }, + { + "name": "updtrevision", + "type": "updtrevision", + "ricardian_contract": "" + }, + { + "name": "voteproducer", + "type": "voteproducer", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "abihash", + "type": "abi_hash", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "bidrefunds", + "type": "bid_refund", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "delband", + "type": "delegated_bandwidth", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "global", + "type": "eosio_global_state", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "global2", + "type": "eosio_global_state2", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "global3", + "type": "eosio_global_state3", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "guaranminres", + "type": "eosio_guaranteed_min_res", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "namebids", + "type": "name_bid", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "producers", + "type": "producer_info", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "producers2", + "type": "producer_info2", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "rammarket", + "type": "exchange_state", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "refunds", + "type": "refund_request", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "upgrade", + "type": "upgrade_state", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "userres", + "type": "user_resources", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "voters", + "type": "voter_info", + "index_type": "i64", + "key_names": [], + "key_types": [] + } + ], + "ricardian_clauses": [], + "variants": [], + "abi_extensions": [] +} \ No newline at end of file diff --git a/contracts/eosio.system/eosio.system.wasm b/contracts/eosio.system/eosio.system.wasm new file mode 100755 index 0000000000000000000000000000000000000000..9bc8d3de02161e9e6b9079de56553863d3a1340e GIT binary patch literal 174107 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 Date: Fri, 5 Apr 2019 13:19:13 +0800 Subject: [PATCH 16/59] add pbft_plugin as default plugins --- contracts/eosio.system/CMakeLists.txt | 9 +- contracts/eosio.system/delegate_bandwidth.cpp | 315 ++++++----- contracts/eosio.system/eosio.system.cpp | 509 +++++++++++++++--- contracts/eosio.system/eosio.system.hpp | 333 +++++++++--- contracts/eosio.system/exchange_state.cpp | 8 +- contracts/eosio.system/exchange_state.hpp | 12 +- contracts/eosio.system/native.hpp | 99 ++-- contracts/eosio.system/producer_pay.cpp | 259 ++++++--- contracts/eosio.system/upgrade.cpp | 14 + contracts/eosio.system/voting.cpp | 220 ++++++-- libraries/chain/controller.cpp | 3 +- programs/nodeos/CMakeLists.txt | 4 +- programs/nodeos/main.cpp | 3 +- 13 files changed, 1358 insertions(+), 430 deletions(-) create mode 100644 contracts/eosio.system/upgrade.cpp diff --git a/contracts/eosio.system/CMakeLists.txt b/contracts/eosio.system/CMakeLists.txt index eb4ff749f77..27e63078a71 100644 --- a/contracts/eosio.system/CMakeLists.txt +++ b/contracts/eosio.system/CMakeLists.txt @@ -2,7 +2,8 @@ file(GLOB ABI_FILES "*.abi") configure_file("${ABI_FILES}" "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY) add_wast_executable(TARGET eosio.system - INCLUDE_FOLDERS ${STANDARD_INCLUDE_FOLDERS} - LIBRARIES libc++ libc eosiolib eosio.token - DESTINATION_FOLDER ${CMAKE_CURRENT_BINARY_DIR} -) + INCLUDE_FOLDERS "${STANDARD_INCLUDE_FOLDERS}" + LIBRARIES libc++ libc eosiolib eosio.token + DESTINATION_FOLDER ${CMAKE_CURRENT_BINARY_DIR} + ) + diff --git a/contracts/eosio.system/delegate_bandwidth.cpp b/contracts/eosio.system/delegate_bandwidth.cpp index a2920d70295..4570df9b76c 100644 --- a/contracts/eosio.system/delegate_bandwidth.cpp +++ b/contracts/eosio.system/delegate_bandwidth.cpp @@ -1,8 +1,8 @@ /** * @file - * @copyright defined in eos/LICENSE + * @copyright defined in eos/LICENSE.txt */ -#include "eosio.system.hpp" +#include #include #include @@ -22,22 +22,22 @@ namespace eosiosystem { using eosio::asset; using eosio::indexed_by; using eosio::const_mem_fun; - using eosio::bytes; using eosio::print; using eosio::permission_level; + using eosio::time_point_sec; using std::map; using std::pair; - static constexpr time refund_delay = 3*24*3600; - static constexpr time refund_expiration_time = 3600; + static constexpr uint32_t refund_delay_sec = 3*24*3600; + static constexpr int64_t ram_gift_bytes = 1400; - struct user_resources { - account_name owner; + struct [[eosio::table, eosio::contract("eosio.system")]] user_resources { + name owner; asset net_weight; asset cpu_weight; int64_t ram_bytes = 0; - uint64_t primary_key()const { return owner; } + uint64_t primary_key()const { return owner.value; } // explicit serialization macro is not necessary, used here only to improve compilation time EOSLIB_SERIALIZE( user_resources, (owner)(net_weight)(cpu_weight)(ram_bytes) ) @@ -47,26 +47,26 @@ namespace eosiosystem { /** * Every user 'from' has a scope/table that uses every receipient 'to' as the primary key. */ - struct delegated_bandwidth { - account_name from; - account_name to; + struct [[eosio::table, eosio::contract("eosio.system")]] delegated_bandwidth { + name from; + name to; asset net_weight; asset cpu_weight; - uint64_t primary_key()const { return to; } + uint64_t primary_key()const { return to.value; } // explicit serialization macro is not necessary, used here only to improve compilation time EOSLIB_SERIALIZE( delegated_bandwidth, (from)(to)(net_weight)(cpu_weight) ) }; - struct refund_request { - account_name owner; - time request_time; - eosio::asset net_amount; - eosio::asset cpu_amount; + struct [[eosio::table, eosio::contract("eosio.system")]] refund_request { + name owner; + time_point_sec request_time; + eosio::asset net_amount; + eosio::asset cpu_amount; - uint64_t primary_key()const { return owner; } + uint64_t primary_key()const { return owner.value; } // explicit serialization macro is not necessary, used here only to improve compilation time EOSLIB_SERIALIZE( refund_request, (owner)(request_time)(net_amount)(cpu_amount) ) @@ -76,19 +76,20 @@ namespace eosiosystem { * These tables are designed to be constructed in the scope of the relevant user, this * facilitates simpler API for per-user queries */ - typedef eosio::multi_index< N(userres), user_resources> user_resources_table; - typedef eosio::multi_index< N(delband), delegated_bandwidth> del_bandwidth_table; - typedef eosio::multi_index< N(refunds), refund_request> refunds_table; + typedef eosio::multi_index< "userres"_n, user_resources > user_resources_table; + typedef eosio::multi_index< "delband"_n, delegated_bandwidth > del_bandwidth_table; + typedef eosio::multi_index< "refunds"_n, refund_request > refunds_table; /** * This action will buy an exact amount of ram and bill the payer the current market price. */ - void system_contract::buyrambytes( account_name payer, account_name receiver, uint32_t bytes ) { - auto itr = _rammarket.find(S(4,RAMCORE)); + void system_contract::buyrambytes( name payer, name receiver, uint32_t bytes ) { + + auto itr = _rammarket.find(ramcore_symbol.raw()); auto tmp = *itr; - auto eosout = tmp.convert( asset(bytes,S(0,RAM)), CORE_SYMBOL ); + auto eosout = tmp.convert( asset(bytes, ram_symbol), core_symbol() ); buyram( payer, receiver, eosout ); } @@ -102,9 +103,12 @@ namespace eosiosystem { * RAM is a scarce resource whose supply is defined by global properties max_ram_size. RAM is * priced using the bancor algorithm such that price-per-byte with a constant reserve ratio of 100:1. */ - void system_contract::buyram( account_name payer, account_name receiver, asset quant ) + void system_contract::buyram( name payer, name receiver, asset quant ) { require_auth( payer ); + update_ram_supply(); + + eosio_assert( quant.symbol == core_symbol(), "must buy ram with core token" ); eosio_assert( quant.amount > 0, "must purchase a positive amount" ); auto fee = quant; @@ -117,19 +121,23 @@ namespace eosiosystem { // quant_after_fee.amount should be > 0 if quant.amount > 1. // If quant.amount == 1, then quant_after_fee.amount == 0 and the next inline transfer will fail causing the buyram action to fail. - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)}, - { payer, N(eosio.ram), quant_after_fee, std::string("buy ram") } ); + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {payer, active_permission}, {ram_account, active_permission} }, + { payer, ram_account, quant_after_fee, std::string("buy ram") } + ); if( fee.amount > 0 ) { - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)}, - { payer, N(eosio.ramfee), fee, std::string("ram fee") } ); + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {payer, active_permission} }, + { payer, ramfee_account, fee, std::string("ram fee") } + ); } int64_t bytes_out; - const auto& market = _rammarket.get(S(4,RAMCORE), "ram market does not exist"); - _rammarket.modify( market, 0, [&]( auto& es ) { - bytes_out = es.convert( quant_after_fee, S(0,RAM) ).amount; + const auto& market = _rammarket.get(ramcore_symbol.raw(), "ram market does not exist"); + _rammarket.modify( market, same_payer, [&]( auto& es ) { + bytes_out = es.convert( quant_after_fee, ram_symbol ).amount; }); eosio_assert( bytes_out > 0, "must reserve a positive amount" ); @@ -137,11 +145,13 @@ namespace eosiosystem { _gstate.total_ram_bytes_reserved += uint64_t(bytes_out); _gstate.total_ram_stake += quant_after_fee.amount; - user_resources_table userres( _self, receiver ); - auto res_itr = userres.find( receiver ); + user_resources_table userres( _self, receiver.value ); + auto res_itr = userres.find( receiver.value ); if( res_itr == userres.end() ) { res_itr = userres.emplace( receiver, [&]( auto& res ) { res.owner = receiver; + res.net_weight = asset( 0, core_symbol() ); + res.cpu_weight = asset( 0, core_symbol() ); res.ram_bytes = bytes_out; }); } else { @@ -149,30 +159,37 @@ namespace eosiosystem { res.ram_bytes += bytes_out; }); } - set_resource_limits( res_itr->owner, res_itr->ram_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); - } + auto voter_itr = _voters.find( res_itr->owner.value ); + if( voter_itr == _voters.end() || !has_field( voter_itr->flags1, voter_info::flags1_fields::ram_managed ) ) { + int64_t ram_bytes, net, cpu; + get_resource_limits( res_itr->owner.value, &ram_bytes, &net, &cpu ); + set_resource_limits( res_itr->owner.value, res_itr->ram_bytes + ram_gift_bytes, net, cpu ); + } + } - /** + /** * The system contract now buys and sells RAM allocations at prevailing market prices. * This may result in traders buying RAM today in anticipation of potential shortages * tomorrow. Overall this will result in the market balancing the supply and demand * for RAM over time. */ - void system_contract::sellram( account_name account, int64_t bytes ) { + void system_contract::sellram( name account, int64_t bytes ) { require_auth( account ); + update_ram_supply(); + eosio_assert( bytes > 0, "cannot sell negative byte" ); - user_resources_table userres( _self, account ); - auto res_itr = userres.find( account ); + user_resources_table userres( _self, account.value ); + auto res_itr = userres.find( account.value ); eosio_assert( res_itr != userres.end(), "no resource row" ); eosio_assert( res_itr->ram_bytes >= bytes, "insufficient quota" ); asset tokens_out; - auto itr = _rammarket.find(S(4,RAMCORE)); - _rammarket.modify( itr, 0, [&]( auto& es ) { + auto itr = _rammarket.find(ramcore_symbol.raw()); + _rammarket.modify( itr, same_payer, [&]( auto& es ) { /// the cast to int64_t of bytes is safe because we certify bytes is <= quota which is limited by prior purchases - tokens_out = es.convert( asset(bytes,S(0,RAM)), CORE_SYMBOL); + tokens_out = es.convert( asset(bytes, ram_symbol), core_symbol()); }); eosio_assert( tokens_out.amount > 1, "token amount received from selling ram is too low" ); @@ -186,46 +203,55 @@ namespace eosiosystem { userres.modify( res_itr, account, [&]( auto& res ) { res.ram_bytes -= bytes; }); - set_resource_limits( res_itr->owner, res_itr->ram_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.ram),N(active)}, - { N(eosio.ram), account, asset(tokens_out), std::string("sell ram") } ); + auto voter_itr = _voters.find( res_itr->owner.value ); + if( voter_itr == _voters.end() || !has_field( voter_itr->flags1, voter_info::flags1_fields::ram_managed ) ) { + int64_t ram_bytes, net, cpu; + get_resource_limits( res_itr->owner.value, &ram_bytes, &net, &cpu ); + set_resource_limits( res_itr->owner.value, res_itr->ram_bytes + ram_gift_bytes, net, cpu ); + } + + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {ram_account, active_permission}, {account, active_permission} }, + { ram_account, account, asset(tokens_out), std::string("sell ram") } + ); auto fee = ( tokens_out.amount + 199 ) / 200; /// .5% fee (round up) // since tokens_out.amount was asserted to be at least 2 earlier, fee.amount < tokens_out.amount - if( fee > 0 ) { - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {account,N(active)}, - { account, N(eosio.ramfee), asset(fee), std::string("sell ram fee") } ); + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {account, active_permission} }, + { account, ramfee_account, asset(fee, core_symbol()), std::string("sell ram fee") } + ); } } - void validate_b1_vesting( int64_t stake ) { - const int64_t base_time = 1527811200; /// 2018-06-01 - const int64_t max_claimable = 100'000'000'0000ll; - const int64_t claimable = int64_t(max_claimable * double(now()-base_time) / (10*seconds_per_year) ); + void validate_bos_vesting( int64_t stake ) { + const int64_t base_time = 1546272000; /// 2019-01-01 00:00:00 + const int64_t max_claimable = 200'000'000'0000ll; + const int64_t claimable = int64_t(max_claimable * double(now()-base_time) / (4*seconds_per_year) ); - eosio_assert( max_claimable - claimable <= stake, "bos can only claim their tokens over 10 years" ); + eosio_assert( max_claimable - claimable <= stake, "bos can only claim their tokens over 4 years" ); } - void system_contract::changebw( account_name from, account_name receiver, + void system_contract::changebw( name from, name receiver, const asset stake_net_delta, const asset stake_cpu_delta, bool transfer ) { require_auth( from ); - eosio_assert( stake_net_delta != asset(0) || stake_cpu_delta != asset(0), "should stake non-zero amount" ); + eosio_assert( stake_net_delta.amount != 0 || stake_cpu_delta.amount != 0, "should stake non-zero amount" ); eosio_assert( std::abs( (stake_net_delta + stake_cpu_delta).amount ) >= std::max( std::abs( stake_net_delta.amount ), std::abs( stake_cpu_delta.amount ) ), "net and cpu deltas cannot be opposite signs" ); - account_name source_stake_from = from; + name source_stake_from = from; if ( transfer ) { from = receiver; } // update stake delegated from "from" to "receiver" { - del_bandwidth_table del_tbl( _self, from); - auto itr = del_tbl.find( receiver ); + del_bandwidth_table del_tbl( _self, from.value ); + auto itr = del_tbl.find( receiver.value ); if( itr == del_tbl.end() ) { itr = del_tbl.emplace( from, [&]( auto& dbo ){ dbo.from = from; @@ -235,22 +261,22 @@ namespace eosiosystem { }); } else { - del_tbl.modify( itr, 0, [&]( auto& dbo ){ + del_tbl.modify( itr, same_payer, [&]( auto& dbo ){ dbo.net_weight += stake_net_delta; dbo.cpu_weight += stake_cpu_delta; }); } - eosio_assert( asset(0) <= itr->net_weight, "insufficient staked net bandwidth" ); - eosio_assert( asset(0) <= itr->cpu_weight, "insufficient staked cpu bandwidth" ); - if ( itr->net_weight == asset(0) && itr->cpu_weight == asset(0) ) { + eosio_assert( 0 <= itr->net_weight.amount, "insufficient staked net bandwidth" ); + eosio_assert( 0 <= itr->cpu_weight.amount, "insufficient staked cpu bandwidth" ); + if ( itr->net_weight.amount == 0 && itr->cpu_weight.amount == 0 ) { del_tbl.erase( itr ); } } // itr can be invalid, should go out of scope // update totals of "receiver" { - user_resources_table totals_tbl( _self, receiver ); - auto tot_itr = totals_tbl.find( receiver ); + user_resources_table totals_tbl( _self, receiver.value ); + auto tot_itr = totals_tbl.find( receiver.value ); if( tot_itr == totals_tbl.end() ) { tot_itr = totals_tbl.emplace( from, [&]( auto& tot ) { tot.owner = receiver; @@ -258,25 +284,46 @@ namespace eosiosystem { tot.cpu_weight = stake_cpu_delta; }); } else { - totals_tbl.modify( tot_itr, from == receiver ? from : 0, [&]( auto& tot ) { + totals_tbl.modify( tot_itr, from == receiver ? from : same_payer, [&]( auto& tot ) { tot.net_weight += stake_net_delta; tot.cpu_weight += stake_cpu_delta; }); } - eosio_assert( asset(0) <= tot_itr->net_weight, "insufficient staked total net bandwidth" ); - eosio_assert( asset(0) <= tot_itr->cpu_weight, "insufficient staked total cpu bandwidth" ); - - set_resource_limits( receiver, tot_itr->ram_bytes, tot_itr->net_weight.amount, tot_itr->cpu_weight.amount ); + eosio_assert( 0 <= tot_itr->net_weight.amount, "insufficient staked total net bandwidth" ); + eosio_assert( 0 <= tot_itr->cpu_weight.amount, "insufficient staked total cpu bandwidth" ); + + { + bool ram_managed = false; + bool net_managed = false; + bool cpu_managed = false; + + auto voter_itr = _voters.find( receiver.value ); + if( voter_itr != _voters.end() ) { + ram_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::ram_managed ); + net_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::net_managed ); + cpu_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::cpu_managed ); + } + + if( !(net_managed && cpu_managed) ) { + int64_t ram_bytes, net, cpu; + get_resource_limits( receiver.value, &ram_bytes, &net, &cpu ); + + set_resource_limits( receiver.value, + ram_managed ? ram_bytes : std::max( tot_itr->ram_bytes + ram_gift_bytes, ram_bytes ), + net_managed ? net : tot_itr->net_weight.amount, + cpu_managed ? cpu : tot_itr->cpu_weight.amount ); + } + } - if ( tot_itr->net_weight == asset(0) && tot_itr->cpu_weight == asset(0) && tot_itr->ram_bytes == 0 ) { + if ( tot_itr->net_weight.amount == 0 && tot_itr->cpu_weight.amount == 0 && tot_itr->ram_bytes == 0 ) { totals_tbl.erase( tot_itr ); } } // tot_itr can be invalid, should go out of scope // create refund or update from existing refund - if ( N(eosio.stake) != source_stake_from ) { //for eosio both transfer and refund make no sense - refunds_table refunds_tbl( _self, from ); - auto req = refunds_tbl.find( from ); + if ( stake_account != source_stake_from ) { //for eosio both transfer and refund make no sense + refunds_table refunds_tbl( _self, from.value ); + auto req = refunds_tbl.find( from.value ); //create/update/delete refund auto net_balance = stake_net_delta; @@ -291,48 +338,51 @@ namespace eosiosystem { if( is_delegating_to_self || is_undelegating ) { if ( req != refunds_tbl.end() ) { //need to update refund - refunds_tbl.modify( req, 0, [&]( refund_request& r ) { - if ( net_balance < asset(0) || cpu_balance < asset(0) ) { - r.request_time = now(); + refunds_tbl.modify( req, same_payer, [&]( refund_request& r ) { + if ( net_balance.amount < 0 || cpu_balance.amount < 0 ) { + r.request_time = current_time_point(); } r.net_amount -= net_balance; - if ( r.net_amount < asset(0) ) { + if ( r.net_amount.amount < 0 ) { net_balance = -r.net_amount; - r.net_amount = asset(0); + r.net_amount.amount = 0; } else { - net_balance = asset(0); + net_balance.amount = 0; } r.cpu_amount -= cpu_balance; - if ( r.cpu_amount < asset(0) ){ + if ( r.cpu_amount.amount < 0 ){ cpu_balance = -r.cpu_amount; - r.cpu_amount = asset(0); + r.cpu_amount.amount = 0; } else { - cpu_balance = asset(0); + cpu_balance.amount = 0; } }); - eosio_assert( asset(0) <= req->net_amount, "negative net refund amount" ); //should never happen - eosio_assert( asset(0) <= req->cpu_amount, "negative cpu refund amount" ); //should never happen + eosio_assert( 0 <= req->net_amount.amount, "negative net refund amount" ); //should never happen + eosio_assert( 0 <= req->cpu_amount.amount, "negative cpu refund amount" ); //should never happen - if ( req->net_amount == asset(0) && req->cpu_amount == asset(0) ) { + if ( req->net_amount.amount == 0 && req->cpu_amount.amount == 0 ) { refunds_tbl.erase( req ); need_deferred_trx = false; } else { need_deferred_trx = true; } - - } else if ( net_balance < asset(0) || cpu_balance < asset(0) ) { //need to create refund + } else if ( net_balance.amount < 0 || cpu_balance.amount < 0 ) { //need to create refund refunds_tbl.emplace( from, [&]( refund_request& r ) { r.owner = from; - if ( net_balance < asset(0) ) { + if ( net_balance.amount < 0 ) { r.net_amount = -net_balance; - net_balance = asset(0); - } // else r.net_amount = 0 by default constructor - if ( cpu_balance < asset(0) ) { + net_balance.amount = 0; + } else { + r.net_amount = asset( 0, core_symbol() ); + } + if ( cpu_balance.amount < 0 ) { r.cpu_amount = -cpu_balance; - cpu_balance = asset(0); - } // else r.cpu_amount = 0 by default constructor - r.request_time = now(); + cpu_balance.amount = 0; + } else { + r.cpu_amount = asset( 0, core_symbol() ); + } + r.request_time = current_time_point(); }); need_deferred_trx = true; } // else stake increase requested with no existing row in refunds_tbl -> nothing to do with refunds_tbl @@ -340,38 +390,44 @@ namespace eosiosystem { if ( need_deferred_trx ) { eosio::transaction out; - out.actions.emplace_back( permission_level{ from, N(active) }, _self, N(refund), from ); - out.delay_sec = refund_delay; - cancel_deferred( from ); // TODO: Remove this line when replacing deferred trxs is fixed - out.send( from, from, true ); + out.actions.emplace_back( permission_level{from, active_permission}, + _self, "refund"_n, + from + ); + out.delay_sec = refund_delay_sec; + cancel_deferred( from.value ); // TODO: Remove this line when replacing deferred trxs is fixed + out.send( from.value, from, true ); } else { - cancel_deferred( from ); + cancel_deferred( from.value ); } auto transfer_amount = net_balance + cpu_balance; - if ( asset(0) < transfer_amount ) { - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {source_stake_from, N(active)}, - { source_stake_from, N(eosio.stake), asset(transfer_amount), std::string("stake bandwidth") } ); + if ( 0 < transfer_amount.amount ) { + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {source_stake_from, active_permission} }, + { source_stake_from, stake_account, asset(transfer_amount), std::string("stake bandwidth") } + ); } } // update voting power { asset total_update = stake_net_delta + stake_cpu_delta; - auto from_voter = _voters.find(from); + auto from_voter = _voters.find( from.value ); if( from_voter == _voters.end() ) { from_voter = _voters.emplace( from, [&]( auto& v ) { v.owner = from; v.staked = total_update.amount; }); } else { - _voters.modify( from_voter, 0, [&]( auto& v ) { + _voters.modify( from_voter, same_payer, [&]( auto& v ) { v.staked += total_update.amount; }); } eosio_assert( 0 <= from_voter->staked, "stake for voting cannot be negative"); - if( from == N(b1) ) { - validate_b1_vesting( from_voter->staked ); + + if( from == "bos"_n ) { + validate_bos_vesting( from_voter->staked ); } if( from_voter->producers.size() || from_voter->proxy ) { @@ -380,44 +436,49 @@ namespace eosiosystem { } } - void system_contract::delegatebw( account_name from, account_name receiver, + void system_contract::delegatebw( name from, name receiver, asset stake_net_quantity, asset stake_cpu_quantity, bool transfer ) { - eosio_assert( stake_cpu_quantity >= asset(0), "must stake a positive amount" ); - eosio_assert( stake_net_quantity >= asset(0), "must stake a positive amount" ); - eosio_assert( stake_net_quantity + stake_cpu_quantity > asset(0), "must stake a positive amount" ); + asset zero_asset( 0, core_symbol() ); + eosio_assert( stake_cpu_quantity >= zero_asset, "must stake a positive amount" ); + eosio_assert( stake_net_quantity >= zero_asset, "must stake a positive amount" ); + eosio_assert( stake_net_quantity.amount + stake_cpu_quantity.amount > 0, "must stake a positive amount" ); eosio_assert( !transfer || from != receiver, "cannot use transfer flag if delegating to self" ); changebw( from, receiver, stake_net_quantity, stake_cpu_quantity, transfer); } // delegatebw - void system_contract::undelegatebw( account_name from, account_name receiver, + void system_contract::undelegatebw( name from, name receiver, asset unstake_net_quantity, asset unstake_cpu_quantity ) { - eosio_assert( asset() <= unstake_cpu_quantity, "must unstake a positive amount" ); - eosio_assert( asset() <= unstake_net_quantity, "must unstake a positive amount" ); - eosio_assert( asset() < unstake_cpu_quantity + unstake_net_quantity, "must unstake a positive amount" ); - eosio_assert( _gstate.total_activated_stake >= min_activated_stake, - "cannot undelegate bandwidth until the chain is activated (at least 15% of all tokens participate in voting)" ); + asset zero_asset( 0, core_symbol() ); + eosio_assert( unstake_cpu_quantity >= zero_asset, "must unstake a positive amount" ); + eosio_assert( unstake_net_quantity >= zero_asset, "must unstake a positive amount" ); + eosio_assert( unstake_cpu_quantity.amount + unstake_net_quantity.amount > 0, "must unstake a positive amount" ); + // eosio_assert( _gstate.total_activated_stake >= min_activated_stake, + // "cannot undelegate bandwidth until the chain is activated (at least 15% of all tokens participate in voting)" ); + eosio_assert( _gstate.thresh_activated_stake_time != time_point(), + "cannot undelegate bandwidth until the chain is activated " ); + changebw( from, receiver, -unstake_net_quantity, -unstake_cpu_quantity, false); } // undelegatebw - - void system_contract::refund( const account_name owner ) { + + void system_contract::refund( const name owner ) { require_auth( owner ); - refunds_table refunds_tbl( _self, owner ); - auto req = refunds_tbl.find( owner ); + refunds_table refunds_tbl( _self, owner.value ); + auto req = refunds_tbl.find( owner.value ); eosio_assert( req != refunds_tbl.end(), "refund request not found" ); - eosio_assert( req->request_time + refund_delay <= now(), "refund is not available yet" ); - // Until now() becomes NOW, the fact that now() is the timestamp of the previous block could in theory - // allow people to get their tokens earlier than the 3 day delay if the unstake happened immediately after many - // consecutive missed blocks. + eosio_assert( req->request_time + seconds(refund_delay_sec) <= current_time_point(), + "refund is not available yet" ); - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.stake),N(active)}, - { N(eosio.stake), req->owner, req->net_amount + req->cpu_amount, std::string("unstake") } ); + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {stake_account, active_permission}, {req->owner, active_permission} }, + { stake_account, req->owner, req->net_amount + req->cpu_amount, std::string("unstake") } + ); refunds_tbl.erase( req ); } diff --git a/contracts/eosio.system/eosio.system.cpp b/contracts/eosio.system/eosio.system.cpp index daf40efbb84..2f030a99e9f 100644 --- a/contracts/eosio.system/eosio.system.cpp +++ b/contracts/eosio.system/eosio.system.cpp @@ -1,41 +1,35 @@ -#include "eosio.system.hpp" +#include #include +#include #include "producer_pay.cpp" #include "delegate_bandwidth.cpp" #include "voting.cpp" #include "exchange_state.cpp" - +#include "upgrade.cpp" +#include namespace eosiosystem { - system_contract::system_contract( account_name s ) - :native(s), - _voters(_self,_self), - _producers(_self,_self), - _global(_self,_self), - _rammarket(_self,_self) + system_contract::system_contract( name s, name code, datastream ds ) + :native(s,code,ds), + _voters(_self, _self.value), + _producers(_self, _self.value), + _producers2(_self, _self.value), + _global(_self, _self.value), + _global2(_self, _self.value), + _global3(_self, _self.value), + _guarantee(_self, _self.value), + _rammarket(_self, _self.value), + _upgrade(_self, _self.value) { + //print( "construct system\n" ); - _gstate = _global.exists() ? _global.get() : get_default_parameters(); - - auto itr = _rammarket.find(S(4,RAMCORE)); - - if( itr == _rammarket.end() ) { - auto system_token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount; - if( system_token_supply > 0 ) { - itr = _rammarket.emplace( _self, [&]( auto& m ) { - m.supply.amount = 100000000000000ll; - m.supply.symbol = S(4,RAMCORE); - m.base.balance.amount = int64_t(_gstate.free_ram()); - m.base.balance.symbol = S(0,RAM); - m.quote.balance.amount = system_token_supply / 1000; - m.quote.balance.symbol = CORE_SYMBOL; - }); - } - } else { - //print( "ram market already created" ); - } + _gstate = _global.exists() ? _global.get() : get_default_parameters(); + _gstate2 = _global2.exists() ? _global2.get() : eosio_global_state2{}; + _gstate3 = _global3.exists() ? _global3.get() : eosio_global_state3{}; + + _ustate = _upgrade.exists() ? _upgrade.get() : upgrade_state{}; } eosio_global_state system_contract::get_default_parameters() { @@ -44,11 +38,27 @@ namespace eosiosystem { return dp; } + time_point system_contract::current_time_point() { + const static time_point ct{ microseconds{ static_cast( current_time() ) } }; + return ct; + } + + block_timestamp system_contract::current_block_time() { + const static block_timestamp cbt{ current_time_point() }; + return cbt; + } + + symbol system_contract::core_symbol()const { + const static auto sym = get_core_symbol( _rammarket ); + return sym; + } system_contract::~system_contract() { - //print( "destruct system\n" ); _global.set( _gstate, _self ); - //eosio_exit(0); + _global2.set( _gstate2, _self ); + _global3.set( _gstate3, _self ); + + _upgrade.set( _ustate, _self ); } void system_contract::setram( uint64_t max_ram_size ) { @@ -59,81 +69,391 @@ namespace eosiosystem { eosio_assert( max_ram_size > _gstate.total_ram_bytes_reserved, "attempt to set max below reserved" ); auto delta = int64_t(max_ram_size) - int64_t(_gstate.max_ram_size); - auto itr = _rammarket.find(S(4,RAMCORE)); + auto itr = _rammarket.find(ramcore_symbol.raw()); /** - * Increase or decrease the amount of ram for sale based upon the change in max - * ram size. + * Increase the amount of ram for sale based upon the change in max ram size. */ - _rammarket.modify( itr, 0, [&]( auto& m ) { + _rammarket.modify( itr, same_payer, [&]( auto& m ) { m.base.balance.amount += delta; }); _gstate.max_ram_size = max_ram_size; - _global.set( _gstate, _self ); + } + + void system_contract::update_ram_supply() { + auto cbt = current_block_time(); + + if( cbt <= _gstate2.last_ram_increase ) return; + + auto itr = _rammarket.find(ramcore_symbol.raw()); + auto new_ram = (cbt.slot - _gstate2.last_ram_increase.slot)*_gstate2.new_ram_per_block; + _gstate.max_ram_size += new_ram; + + /** + * Increase the amount of ram for sale based upon the change in max ram size. + */ + _rammarket.modify( itr, same_payer, [&]( auto& m ) { + m.base.balance.amount += new_ram; + }); + _gstate2.last_ram_increase = cbt; + } + + /** + * Sets the rate of increase of RAM in bytes per block. It is capped by the uint16_t to + * a maximum rate of 3 TB per year. + * + * If update_ram_supply hasn't been called for the most recent block, then new ram will + * be allocated at the old rate up to the present block before switching the rate. + */ + void system_contract::setramrate( uint16_t bytes_per_block ) { + require_auth( _self ); + + update_ram_supply(); + _gstate2.new_ram_per_block = bytes_per_block; } void system_contract::setparams( const eosio::blockchain_parameters& params ) { - require_auth( N(eosio) ); + require_auth( _self ); (eosio::blockchain_parameters&)(_gstate) = params; eosio_assert( 3 <= _gstate.max_authority_depth, "max_authority_depth should be at least 3" ); set_blockchain_parameters( params ); } - void system_contract::setpriv( account_name account, uint8_t ispriv ) { + // *bos begin* + void system_contract::namelist(std::string list, std::string action, const std::vector &names) + { + const int MAX_LIST_LENGTH = 30; + const int MAX_ACTION_LENGTH = 10; + enum class list_type:int64_t + { + actor_blacklist_type = 1, + contract_blacklist_type, + resource_greylist_type, + list_type_count + }; + enum class list_action_type:int64_t + { + insert_type = 1, + remove_type, + list_action_type_count + }; + + std::map list_type_string_to_enum = { + {"actor_blacklist", list_type::actor_blacklist_type}, + {"contract_blacklist", list_type::contract_blacklist_type}, + {"resource_greylist", list_type::resource_greylist_type}}; + + std::map list_action_type_string_to_enum = { + {"insert", list_action_type::insert_type}, + {"remove", list_action_type::remove_type}}; + + std::map::iterator itlt = list_type_string_to_enum.find(list); + std::map::iterator itlat = list_action_type_string_to_enum.find(action); + + require_auth(_self); + eosio_assert(3 <= _gstate.max_authority_depth, "max_authority_depth should be at least 3"); + eosio_assert(list.length() < MAX_LIST_LENGTH, "list string is greater than max length 30"); + eosio_assert(action.length() < MAX_ACTION_LENGTH, " action string is greater than max length 10"); + eosio_assert(itlt != list_type_string_to_enum.end(), " unknown list type string support 'actor_blacklist' ,'contract_blacklist', 'resource_greylist'"); + eosio_assert(itlat != list_action_type_string_to_enum.end(), " unknown list type string support 'insert' or 'remove'"); + + auto packed_names = pack(names); + + set_name_list_packed(static_cast(itlt->second), static_cast(itlat->second), packed_names.data(), packed_names.size()); + } + + void system_contract::setguaminres(uint32_t ram, uint32_t cpu, uint32_t net) + { + require_auth(_self); + + const static uint32_t MAX_BYTE = 100*1024; + const static uint32_t MAX_MICROSEC = 100*1000; + + const static uint32_t STEP_BYTE = 10*1024; + const static uint32_t STEP_MICROSEC = 10*1000; + eosio_assert(3 <= _gstate.max_authority_depth, "max_authority_depth should be at least 3"); + eosio_assert(ram <= MAX_BYTE && net <= MAX_BYTE, "the value of ram, cpu and net should not more then 100 kb"); + eosio_assert(cpu <= MAX_MICROSEC , "the value of cpu should not more then 100 ms"); + + eosio_guaranteed_min_res _gmr = _guarantee.exists() ? _guarantee.get() : eosio_guaranteed_min_res{}; + eosio_assert(ram >= _gmr.ram, "can not reduce ram guarantee "); + eosio_assert(ram <= _gmr.ram + STEP_BYTE, "minimum ram guarantee can not increace more then 10kb every time"); + eosio_assert(cpu <= _gmr.cpu + STEP_MICROSEC, "minimum cpu guarantee can not increace more then 10ms token weight every time"); + eosio_assert(net <= _gmr.net + STEP_BYTE, "minimum net guarantee can not increace more then 10kb token weight every time"); + + _gmr.ram = ram; + _gmr.cpu = cpu; + _gmr.net = net; + + _guarantee.set(_gmr, _self); + set_guaranteed_minimum_resources(ram, cpu, net); + } + // *bos end* + + void system_contract::setpriv( name account, uint8_t ispriv ) { + require_auth( _self ); + set_privileged( account.value, ispriv ); + } + + void system_contract::setalimits( name account, int64_t ram, int64_t net, int64_t cpu ) { + require_auth( _self ); + + user_resources_table userres( _self, account.value ); + auto ritr = userres.find( account.value ); + eosio_assert( ritr == userres.end(), "only supports unlimited accounts" ); + + auto vitr = _voters.find( account.value ); + if( vitr != _voters.end() ) { + bool ram_managed = has_field( vitr->flags1, voter_info::flags1_fields::ram_managed ); + bool net_managed = has_field( vitr->flags1, voter_info::flags1_fields::net_managed ); + bool cpu_managed = has_field( vitr->flags1, voter_info::flags1_fields::cpu_managed ); + eosio_assert( !(ram_managed || net_managed || cpu_managed), "cannot use setalimits on an account with managed resources" ); + } + + set_resource_limits( account.value, ram, net, cpu ); + } + + void system_contract::setacctram( name account, std::optional ram_bytes ) { + require_auth( _self ); + + int64_t current_ram, current_net, current_cpu; + get_resource_limits( account.value, ¤t_ram, ¤t_net, ¤t_cpu ); + + int64_t ram = 0; + + if( !ram_bytes ) { + auto vitr = _voters.find( account.value ); + eosio_assert( vitr != _voters.end() && has_field( vitr->flags1, voter_info::flags1_fields::ram_managed ), + "RAM of account is already unmanaged" ); + + user_resources_table userres( _self, account.value ); + auto ritr = userres.find( account.value ); + + ram = ram_gift_bytes; + if( ritr != userres.end() ) { + ram += ritr->ram_bytes; + } + + _voters.modify( vitr, same_payer, [&]( auto& v ) { + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::ram_managed, false ); + }); + } else { + eosio_assert( *ram_bytes >= 0, "not allowed to set RAM limit to unlimited" ); + + auto vitr = _voters.find( account.value ); + if ( vitr != _voters.end() ) { + _voters.modify( vitr, same_payer, [&]( auto& v ) { + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::ram_managed, true ); + }); + } else { + _voters.emplace( account, [&]( auto& v ) { + v.owner = account; + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::ram_managed, true ); + }); + } + + ram = *ram_bytes; + } + + set_resource_limits( account.value, ram, current_net, current_cpu ); + } + + void system_contract::setacctnet( name account, std::optional net_weight ) { require_auth( _self ); - set_privileged( account, ispriv ); + + int64_t current_ram, current_net, current_cpu; + get_resource_limits( account.value, ¤t_ram, ¤t_net, ¤t_cpu ); + + int64_t net = 0; + + if( !net_weight ) { + auto vitr = _voters.find( account.value ); + eosio_assert( vitr != _voters.end() && has_field( vitr->flags1, voter_info::flags1_fields::net_managed ), + "Network bandwidth of account is already unmanaged" ); + + user_resources_table userres( _self, account.value ); + auto ritr = userres.find( account.value ); + + if( ritr != userres.end() ) { + net = ritr->net_weight.amount; + } + + _voters.modify( vitr, same_payer, [&]( auto& v ) { + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::net_managed, false ); + }); + } else { + eosio_assert( *net_weight >= -1, "invalid value for net_weight" ); + + auto vitr = _voters.find( account.value ); + if ( vitr != _voters.end() ) { + _voters.modify( vitr, same_payer, [&]( auto& v ) { + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::net_managed, true ); + }); + } else { + _voters.emplace( account, [&]( auto& v ) { + v.owner = account; + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::net_managed, true ); + }); + } + + net = *net_weight; + } + + set_resource_limits( account.value, current_ram, net, current_cpu ); } - void system_contract::rmvproducer( account_name producer ) { + void system_contract::setacctcpu( name account, std::optional cpu_weight ) { require_auth( _self ); - auto prod = _producers.find( producer ); + + int64_t current_ram, current_net, current_cpu; + get_resource_limits( account.value, ¤t_ram, ¤t_net, ¤t_cpu ); + + int64_t cpu = 0; + + if( !cpu_weight ) { + auto vitr = _voters.find( account.value ); + eosio_assert( vitr != _voters.end() && has_field( vitr->flags1, voter_info::flags1_fields::cpu_managed ), + "CPU bandwidth of account is already unmanaged" ); + + user_resources_table userres( _self, account.value ); + auto ritr = userres.find( account.value ); + + if( ritr != userres.end() ) { + cpu = ritr->cpu_weight.amount; + } + + _voters.modify( vitr, same_payer, [&]( auto& v ) { + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::cpu_managed, false ); + }); + } else { + eosio_assert( *cpu_weight >= -1, "invalid value for cpu_weight" ); + + auto vitr = _voters.find( account.value ); + if ( vitr != _voters.end() ) { + _voters.modify( vitr, same_payer, [&]( auto& v ) { + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::cpu_managed, true ); + }); + } else { + _voters.emplace( account, [&]( auto& v ) { + v.owner = account; + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::cpu_managed, true ); + }); + } + + cpu = *cpu_weight; + } + + set_resource_limits( account.value, current_ram, current_net, cpu ); + } + + void system_contract::rmvproducer( name producer ) { + require_auth( _self ); + auto prod = _producers.find( producer.value ); eosio_assert( prod != _producers.end(), "producer not found" ); - _producers.modify( prod, 0, [&](auto& p) { + _producers.modify( prod, same_payer, [&](auto& p) { p.deactivate(); }); } - void system_contract::bidname( account_name bidder, account_name newname, asset bid ) { + void system_contract::updtrevision( uint8_t revision ) { + require_auth( _self ); + eosio_assert( _gstate2.revision < 255, "can not increment revision" ); // prevent wrap around + eosio_assert( revision == _gstate2.revision + 1, "can only increment revision by one" ); + eosio_assert( revision <= 1, // set upper bound to greatest revision supported in the code + "specified revision is not yet supported by the code" ); + _gstate2.revision = revision; + } + + void system_contract::bidname( name bidder, name newname, asset bid ) { require_auth( bidder ); - eosio_assert( eosio::name_suffix(newname) == newname, "you can only bid on top-level suffix" ); - eosio_assert( newname != 0, "the empty name is not a valid account name to bid on" ); - eosio_assert( (newname & 0xFull) == 0, "13 character names are not valid account names to bid on" ); - eosio_assert( (newname & 0x1F0ull) == 0, "accounts with 12 character names and no dots can be created without bidding required" ); + eosio_assert( newname.suffix() == newname, "you can only bid on top-level suffix" ); + + eosio_assert( (bool)newname, "the empty name is not a valid account name to bid on" ); + eosio_assert( (newname.value & 0xFull) == 0, "13 character names are not valid account names to bid on" ); + eosio_assert( (newname.value & 0x1F0ull) == 0, "accounts with 12 character names and no dots can be created without bidding required" ); eosio_assert( !is_account( newname ), "account already exists" ); - eosio_assert( bid.symbol == asset().symbol, "asset must be system token" ); + eosio_assert( bid.symbol == core_symbol(), "asset must be system token" ); eosio_assert( bid.amount > 0, "insufficient bid" ); - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {bidder,N(active)}, - { bidder, N(eosio.names), bid, std::string("bid name ")+(name{newname}).to_string() } ); + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {bidder, active_permission} }, + { bidder, names_account, bid, std::string("bid name ")+ newname.to_string() } + ); + + name_bid_table bids(_self, _self.value); + + if (newname.length() < BASE_LENGTH) + { + + auto idx = bids.get_index<"highbid"_n>(); + auto highest = idx.lower_bound(std::numeric_limits::max() / 2); + + if (highest != idx.end() && + highest->high_bid > 0) + { + std::string msg= "newname which length is less than 3 must increase bid by 10% than the highest bid in all bid :current value:"+std::to_string(highest->high_bid ); + eosio_assert(bid.amount - highest->high_bid > (highest->high_bid / 10),msg.c_str()); + } + } - name_bid_table bids(_self,_self); print( name{bidder}, " bid ", bid, " on ", name{newname}, "\n" ); - auto current = bids.find( newname ); + auto current = bids.find( newname.value ); if( current == bids.end() ) { bids.emplace( bidder, [&]( auto& b ) { b.newname = newname; b.high_bidder = bidder; b.high_bid = bid.amount; - b.last_bid_time = current_time(); + b.last_bid_time = current_time_point(); }); } else { eosio_assert( current->high_bid > 0, "this auction has already closed" ); eosio_assert( bid.amount - current->high_bid > (current->high_bid / 10), "must increase bid by 10%" ); eosio_assert( current->high_bidder != bidder, "account is already highest bidder" ); - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.names),N(active)}, - { N(eosio.names), current->high_bidder, asset(current->high_bid), - std::string("refund bid on name ")+(name{newname}).to_string() } ); + bid_refund_table refunds_table(_self, newname.value); + + auto it = refunds_table.find( current->high_bidder.value ); + if ( it != refunds_table.end() ) { + refunds_table.modify( it, same_payer, [&](auto& r) { + r.amount += asset( current->high_bid, core_symbol() ); + }); + } else { + refunds_table.emplace( bidder, [&](auto& r) { + r.bidder = current->high_bidder; + r.amount = asset( current->high_bid, core_symbol() ); + }); + } + + transaction t; + t.actions.emplace_back( permission_level{_self, active_permission}, + _self, "bidrefund"_n, + std::make_tuple( current->high_bidder, newname ) + ); + t.delay_sec = 0; + uint128_t deferred_id = (uint128_t(newname.value) << 64) | current->high_bidder.value; + cancel_deferred( deferred_id ); + t.send( deferred_id, bidder ); bids.modify( current, bidder, [&]( auto& b ) { b.high_bidder = bidder; b.high_bid = bid.amount; - b.last_bid_time = current_time(); + b.last_bid_time = current_time_point(); }); } } + void system_contract::bidrefund( name bidder, name newname ) { + bid_refund_table refunds_table(_self, newname.value); + auto it = refunds_table.find( bidder.value ); + eosio_assert( it != refunds_table.end(), "refund not found" ); + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {names_account, active_permission}, {bidder, active_permission} }, + { names_account, bidder, asset(it->amount), std::string("refund bid on name ")+(name{newname}).to_string() } + ); + refunds_table.erase( it ); + } + /** * Called after a new account is created. This code enforces resource-limits rules * for new accounts as well as new account naming conventions. @@ -143,14 +463,13 @@ namespace eosiosystem { * who can create accounts with the creator's name as a suffix. * */ - void native::newaccount( account_name creator, - account_name newact - /* no need to parse authorities - const authority& owner, - const authority& active*/ ) { + void native::newaccount( name creator, + name newact, + ignore owner, + ignore active ) { if( creator != _self ) { - auto tmp = newact >> 4; + uint64_t tmp = newact.value >> 4; bool has_dot = false; for( uint32_t i = 0; i < 12; ++i ) { @@ -158,10 +477,14 @@ namespace eosiosystem { tmp >>= 5; } if( has_dot ) { // or is less than 12 characters - auto suffix = eosio::name_suffix(newact); + auto suffix = newact.suffix(); if( suffix == newact ) { - name_bid_table bids(_self,_self); - auto current = bids.find( newact ); + if("bos"_n==suffix) + { + require_auth(_self); + } + name_bid_table bids(_self, _self.value); + auto current = bids.find( newact.value ); eosio_assert( current != bids.end(), "no active bid for name" ); eosio_assert( current->high_bidder == creator, "only highest bidder can claim" ); eosio_assert( current->high_bid < 0, "auction for name is not closed yet" ); @@ -169,30 +492,78 @@ namespace eosiosystem { } else { eosio_assert( creator == suffix, "only suffix may create this account" ); } + + const static auto BOS_PREFIX = "bos."; + std::string::size_type p = newact.to_string().find(BOS_PREFIX); + if(p != std::string::npos && 0 == p) + { + require_auth(_self); + } + } } - user_resources_table userres( _self, newact); + user_resources_table userres( _self, newact.value); userres.emplace( newact, [&]( auto& res ) { res.owner = newact; + res.net_weight = asset( 0, system_contract::get_core_symbol() ); + res.cpu_weight = asset( 0, system_contract::get_core_symbol() ); }); - set_resource_limits( newact, 0, 0, 0 ); + set_resource_limits( newact.value, 0, 0, 0 ); } + void native::setabi( name acnt, const std::vector& abi ) { + eosio::multi_index< "abihash"_n, abi_hash > table(_self, _self.value); + auto itr = table.find( acnt.value ); + if( itr == table.end() ) { + table.emplace( acnt, [&]( auto& row ) { + row.owner= acnt; + sha256( const_cast(abi.data()), abi.size(), &row.hash ); + }); + } else { + table.modify( itr, same_payer, [&]( auto& row ) { + sha256( const_cast(abi.data()), abi.size(), &row.hash ); + }); + } + } + + void system_contract::init( unsigned_int version, symbol core ) { + require_auth( _self ); + eosio_assert( version.value == 0, "unsupported version for init action" ); + + auto itr = _rammarket.find(ramcore_symbol.raw()); + eosio_assert( itr == _rammarket.end(), "system contract has already been initialized" ); + + auto system_token_supply = eosio::token::get_supply(token_account, core.code() ); + eosio_assert( system_token_supply.symbol == core, "specified core symbol does not exist (precision mismatch)" ); + + eosio_assert( system_token_supply.amount > 0, "system token supply must be greater than 0" ); + _rammarket.emplace( _self, [&]( auto& m ) { + m.supply.amount = 100000000000000ll; + m.supply.symbol = ramcore_symbol; + m.base.balance.amount = int64_t(_gstate.free_ram()); + m.base.balance.symbol = ram_symbol; + m.quote.balance.amount = system_token_supply.amount / 1000; + m.quote.balance.symbol = core; + }); + } } /// eosio.system -EOSIO_ABI( eosiosystem::system_contract, +EOSIO_DISPATCH( eosiosystem::system_contract, // native.hpp (newaccount definition is actually in eosio.system.cpp) - (newaccount)(updateauth)(deleteauth)(linkauth)(unlinkauth)(canceldelay)(onerror) + (newaccount)(updateauth)(deleteauth)(linkauth)(unlinkauth)(canceldelay)(onerror)(setabi) // eosio.system.cpp - (setram)(setparams)(setpriv)(rmvproducer)(bidname) + (init)(setram)(setramrate)(setparams)(namelist)(setguaminres)(setpriv)(setalimits)(setacctram)(setacctnet)(setacctcpu) + (rmvproducer)(updtrevision)(bidname)(bidrefund) // delegate_bandwidth.cpp (buyrambytes)(buyram)(sellram)(delegatebw)(undelegatebw)(refund) // voting.cpp (regproducer)(unregprod)(voteproducer)(regproxy) // producer_pay.cpp (onblock)(claimrewards) + //upgrade.cpp + (setupgrade) ) diff --git a/contracts/eosio.system/eosio.system.hpp b/contracts/eosio.system/eosio.system.hpp index 2b6d16d3d59..04ddf000803 100644 --- a/contracts/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/eosio.system.hpp @@ -1,6 +1,6 @@ /** * @file - * @copyright defined in eos/LICENSE + * @copyright defined in eos/LICENSE.txt */ #pragma once @@ -12,30 +12,65 @@ #include #include +#include +#include namespace eosiosystem { + using eosio::name; using eosio::asset; + using eosio::symbol; + using eosio::symbol_code; using eosio::indexed_by; using eosio::const_mem_fun; using eosio::block_timestamp; + using eosio::time_point; + using eosio::microseconds; + using eosio::datastream; + + template + static inline auto has_field( F flags, E field ) + -> std::enable_if_t< std::is_integral_v && std::is_unsigned_v && + std::is_enum_v && std::is_same_v< F, std::underlying_type_t >, bool> + { + return ( (flags & static_cast(field)) != 0 ); + } + + template + static inline auto set_field( F flags, E field, bool value = true ) + -> std::enable_if_t< std::is_integral_v && std::is_unsigned_v && + std::is_enum_v && std::is_same_v< F, std::underlying_type_t >, F > + { + if( value ) + return ( flags | static_cast(field) ); + else + return ( flags & ~static_cast(field) ); + } + + struct [[eosio::table, eosio::contract("eosio.system")]] name_bid { + name newname; + name high_bidder; + int64_t high_bid = 0; ///< negative high_bid == closed auction waiting to be claimed + time_point last_bid_time; + + uint64_t primary_key()const { return newname.value; } + uint64_t by_high_bid()const { return static_cast(-high_bid); } + }; - struct name_bid { - account_name newname; - account_name high_bidder; - int64_t high_bid = 0; ///< negative high_bid == closed auction waiting to be claimed - uint64_t last_bid_time = 0; + struct [[eosio::table, eosio::contract("eosio.system")]] bid_refund { + name bidder; + asset amount; - auto primary_key()const { return newname; } - uint64_t by_high_bid()const { return static_cast(-high_bid); } + uint64_t primary_key()const { return bidder.value; } }; - typedef eosio::multi_index< N(namebids), name_bid, - indexed_by > - > name_bid_table; + typedef eosio::multi_index< "namebids"_n, name_bid, + indexed_by<"highbid"_n, const_mem_fun > + > name_bid_table; + typedef eosio::multi_index< "bidrefunds"_n, bid_refund > bid_refund_table; - struct eosio_global_state : eosio::blockchain_parameters { + struct [[eosio::table("global"), eosio::contract("eosio.system")]] eosio_global_state : eosio::blockchain_parameters { uint64_t free_ram()const { return max_ram_size - total_ram_bytes_reserved; } uint64_t max_ram_size = 64ll*1024 * 1024 * 1024; @@ -43,12 +78,12 @@ namespace eosiosystem { int64_t total_ram_stake = 0; block_timestamp last_producer_schedule_update; - uint64_t last_pervote_bucket_fill = 0; + time_point last_pervote_bucket_fill; int64_t pervote_bucket = 0; int64_t perblock_bucket = 0; - uint32_t total_unpaid_blocks = 0; /// all blocks which have been produced but not paid + uint32_t total_unpaid_blocks = 0; /// all blocks which have been produced but not paid int64_t total_activated_stake = 0; - uint64_t thresh_activated_stake_time = 0; + time_point thresh_activated_stake_time; uint16_t last_producer_schedule_size = 0; double total_producer_vote_weight = 0; /// the sum of all producer votes block_timestamp last_name_close; @@ -61,17 +96,48 @@ namespace eosiosystem { (last_producer_schedule_size)(total_producer_vote_weight)(last_name_close) ) }; - struct producer_info { - account_name owner; + /** + * Defines new global state parameters added after version 1.0 + */ + struct [[eosio::table("global2"), eosio::contract("eosio.system")]] eosio_global_state2 { + eosio_global_state2(){} + + uint16_t new_ram_per_block = 0; + block_timestamp last_ram_increase; + block_timestamp last_block_num; /* deprecated */ + double total_producer_votepay_share = 0; + uint8_t revision = 0; ///< used to track version updates in the future. + + EOSLIB_SERIALIZE( eosio_global_state2, (new_ram_per_block)(last_ram_increase)(last_block_num) + (total_producer_votepay_share)(revision) ) + }; + + struct [[eosio::table("global3"), eosio::contract("eosio.system")]] eosio_global_state3 { + eosio_global_state3() { } + time_point last_vpay_state_update; + double total_vpay_share_change_rate = 0; + + EOSLIB_SERIALIZE( eosio_global_state3, (last_vpay_state_update)(total_vpay_share_change_rate) ) + }; + + struct [[eosio::table("upgrade"), eosio::contract("eosio.system")]] upgrade_state : eosio::upgrade_parameters { + uint16_t current_version = 0; + + EOSLIB_SERIALIZE_DERIVED( upgrade_state, eosio::upgrade_parameters, (current_version) ) + }; + + + struct [[eosio::table, eosio::contract("eosio.system")]] producer_info { + name owner; double total_votes = 0; eosio::public_key producer_key; /// a packed public key object bool is_active = true; std::string url; uint32_t unpaid_blocks = 0; - uint64_t last_claim_time = 0; + time_point last_claim_time; uint16_t location = 0; - uint64_t primary_key()const { return owner; } + uint64_t primary_key()const { return owner.value; } double by_votes()const { return is_active ? -total_votes : total_votes; } bool active()const { return is_active; } void deactivate() { producer_key = public_key(); is_active = false; } @@ -81,11 +147,22 @@ namespace eosiosystem { (unpaid_blocks)(last_claim_time)(location) ) }; - struct voter_info { - account_name owner = 0; /// the voter - account_name proxy = 0; /// the proxy set by the voter, if any - std::vector producers; /// the producers approved by this voter if no proxy set - int64_t staked = 0; + struct [[eosio::table, eosio::contract("eosio.system")]] producer_info2 { + name owner; + double votepay_share = 0; + time_point last_votepay_share_update; + + uint64_t primary_key()const { return owner.value; } + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( producer_info2, (owner)(votepay_share)(last_votepay_share_update) ) + }; + + struct [[eosio::table, eosio::contract("eosio.system")]] voter_info { + name owner; /// the voter + name proxy; /// the proxy set by the voter, if any + std::vector producers; /// the producers approved by this voter if no proxy set + int64_t staked = 0; /** * Every time a vote is cast we must first "undo" the last vote weight, before casting the @@ -93,54 +170,116 @@ namespace eosiosystem { * * stated.amount * 2 ^ ( weeks_since_launch/weeks_per_year) */ - double last_vote_weight = 0; /// the vote weight cast the last time the vote was updated + double last_vote_weight = 0; /// the vote weight cast the last time the vote was updated /** * Total vote weight delegated to this voter. */ - double proxied_vote_weight= 0; /// the total vote weight delegated to this voter as a proxy - bool is_proxy = 0; /// whether the voter is a proxy for others + double proxied_vote_weight= 0; /// the total vote weight delegated to this voter as a proxy + bool is_proxy = 0; /// whether the voter is a proxy for others + + uint32_t flags1 = 0; + uint32_t reserved2 = 0; + eosio::asset reserved3; - uint32_t reserved1 = 0; - time reserved2 = 0; - eosio::asset reserved3; + uint64_t primary_key()const { return owner.value; } - uint64_t primary_key()const { return owner; } + enum class flags1_fields : uint32_t { + ram_managed = 1, + net_managed = 2, + cpu_managed = 4 + }; // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( voter_info, (owner)(proxy)(producers)(staked)(last_vote_weight)(proxied_vote_weight)(is_proxy)(reserved1)(reserved2)(reserved3) ) + EOSLIB_SERIALIZE( voter_info, (owner)(proxy)(producers)(staked)(last_vote_weight)(proxied_vote_weight)(is_proxy)(flags1)(reserved2)(reserved3) ) + }; + + // *bos* + struct [[eosio::table("guaranminres"), eosio::contract("eosio.system")]] eosio_guaranteed_min_res{ + eosio_guaranteed_min_res(){} + + uint32_t ram = 0; /// guaranteed minimum ram in kb for every account. + uint32_t cpu = 0; /// guaranteed minimum cpu in bos for every account. + uint32_t net = 0; /// guaranteed minimum net in bos for every account. + + EOSLIB_SERIALIZE( eosio_guaranteed_min_res, (ram)(cpu)(net) ) }; + typedef eosio::multi_index< "voters"_n, voter_info > voters_table; - typedef eosio::multi_index< N(voters), voter_info> voters_table; + typedef eosio::multi_index< "producers"_n, producer_info, + indexed_by<"prototalvote"_n, const_mem_fun > + > producers_table; + typedef eosio::multi_index< "producers2"_n, producer_info2 > producers_table2; - typedef eosio::multi_index< N(producers), producer_info, - indexed_by > - > producers_table; + typedef eosio::singleton< "global"_n, eosio_global_state > global_state_singleton; + typedef eosio::singleton< "global2"_n, eosio_global_state2 > global_state2_singleton; + typedef eosio::singleton< "global3"_n, eosio_global_state3 > global_state3_singleton; + typedef eosio::singleton< "guaranminres"_n, eosio_guaranteed_min_res > guaranteed_min_res_singleton; // *bos* - typedef eosio::singleton global_state_singleton; + typedef eosio::singleton< "upgrade"_n, upgrade_state > upgrade_singleton; // static constexpr uint32_t max_inflation_rate = 5; // 5% annual inflation static constexpr uint32_t seconds_per_day = 24 * 3600; - static constexpr uint64_t system_token_symbol = CORE_SYMBOL; - class system_contract : public native { + class [[eosio::contract("eosio.system")]] system_contract : public native { private: - voters_table _voters; - producers_table _producers; - global_state_singleton _global; - - eosio_global_state _gstate; - rammarket _rammarket; + voters_table _voters; + producers_table _producers; + producers_table2 _producers2; + global_state_singleton _global; + global_state2_singleton _global2; + global_state3_singleton _global3; + guaranteed_min_res_singleton _guarantee; // *bos* + eosio_global_state _gstate; + eosio_global_state2 _gstate2; + eosio_global_state3 _gstate3; + rammarket _rammarket; + upgrade_singleton _upgrade; + upgrade_state _ustate; public: - system_contract( account_name s ); + static constexpr eosio::name active_permission{"active"_n}; + static constexpr eosio::name token_account{"eosio.token"_n}; + static constexpr eosio::name ram_account{"eosio.ram"_n}; + static constexpr eosio::name ramfee_account{"eosio.ramfee"_n}; + static constexpr eosio::name stake_account{"eosio.stake"_n}; + static constexpr eosio::name bpay_account{"eosio.bpay"_n}; + static constexpr eosio::name vpay_account{"eosio.vpay"_n}; + static constexpr eosio::name names_account{"eosio.names"_n}; + static constexpr eosio::name saving_account{"eosio.saving"_n}; + static constexpr eosio::name dev_account{"bos.dev"_n}; + static constexpr eosio::name gov_account{"bos.gov"_n}; + static constexpr symbol ramcore_symbol = symbol(symbol_code("RAMCORE"), 4); + static constexpr symbol ram_symbol = symbol(symbol_code("RAM"), 0); + static const int16_t BASE_LENGTH = 4; + system_contract( name s, name code, datastream ds ); ~system_contract(); + static symbol get_core_symbol( name system_account = "eosio"_n ) { + rammarket rm(system_account, system_account.value); + const static auto sym = get_core_symbol( rm ); + return sym; + } + // Actions: - void onblock( block_timestamp timestamp, account_name producer ); - // const block_header& header ); /// only parse first 3 fields of block header + [[eosio::action]] + void init( unsigned_int version, symbol core ); + [[eosio::action]] + void onblock( ignore header ); + + [[eosio::action]] + void setalimits( name account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight ); + + [[eosio::action]] + void setacctram( name account, std::optional ram_bytes ); + + [[eosio::action]] + void setacctnet( name account, std::optional net_weight ); + + [[eosio::action]] + void setacctcpu( name account, std::optional cpu_weight ); // functions defined in delegate_bandwidth.cpp @@ -149,7 +288,8 @@ namespace eosiosystem { * If transfer == true, then 'receiver' can unstake to their account * Else 'from' can unstake at any time. */ - void delegatebw( account_name from, account_name receiver, + [[eosio::action]] + void delegatebw( name from, name receiver, asset stake_net_quantity, asset stake_cpu_quantity, bool transfer ); @@ -169,67 +309,122 @@ namespace eosiosystem { * The 'from' account loses voting power as a result of this call and * all producer tallies are updated. */ - void undelegatebw( account_name from, account_name receiver, + [[eosio::action]] + void undelegatebw( name from, name receiver, asset unstake_net_quantity, asset unstake_cpu_quantity ); + /** * Increases receiver's ram quota based upon current price and quantity of * tokens provided. An inline transfer from receiver to system contract of * tokens will be executed. */ - void buyram( account_name buyer, account_name receiver, asset tokens ); - void buyrambytes( account_name buyer, account_name receiver, uint32_t bytes ); + [[eosio::action]] + void buyram( name payer, name receiver, asset quant ); + [[eosio::action]] + void buyrambytes( name payer, name receiver, uint32_t bytes ); /** * Reduces quota my bytes and then performs an inline transfer of tokens * to receiver based upon the average purchase price of the original quota. */ - void sellram( account_name receiver, int64_t bytes ); + [[eosio::action]] + void sellram( name account, int64_t bytes ); /** * This action is called after the delegation-period to claim all pending * unstaked tokens belonging to owner */ - void refund( account_name owner ); + [[eosio::action]] + void refund( name owner ); // functions defined in voting.cpp - void regproducer( const account_name producer, const public_key& producer_key, const std::string& url, uint16_t location ); + [[eosio::action]] + void regproducer( const name producer, const public_key& producer_key, const std::string& url, uint16_t location ); - void unregprod( const account_name producer ); + [[eosio::action]] + void unregprod( const name producer ); + [[eosio::action]] void setram( uint64_t max_ram_size ); + [[eosio::action]] + void setramrate( uint16_t bytes_per_block ); - void voteproducer( const account_name voter, const account_name proxy, const std::vector& producers ); + [[eosio::action]] + void voteproducer( const name voter, const name proxy, const std::vector& producers ); - void regproxy( const account_name proxy, bool isproxy ); + [[eosio::action]] + void regproxy( const name proxy, bool isproxy ); + [[eosio::action]] void setparams( const eosio::blockchain_parameters& params ); + // *bos* + [[eosio::action]] + void namelist(std::string list, std::string action, const std::vector& names ); + + // *bos* + [[eosio::action]] + void setguaminres(uint32_t ram, uint32_t cpu, uint32_t net); + // functions defined in producer_pay.cpp - void claimrewards( const account_name& owner ); + [[eosio::action]] + void claimrewards( const name owner ); - void setpriv( account_name account, uint8_t ispriv ); + [[eosio::action]] + void setpriv( name account, uint8_t is_priv ); - void rmvproducer( account_name producer ); + [[eosio::action]] + void rmvproducer( name producer ); - void bidname( account_name bidder, account_name newname, asset bid ); - private: - void update_elected_producers( block_timestamp timestamp ); + [[eosio::action]] + void updtrevision( uint8_t revision ); + + [[eosio::action]] + void bidname( name bidder, name newname, asset bid ); + + [[eosio::action]] + void bidrefund( name bidder, name newname ); + //functions defined in upgrade.cpp + [[eosio::action]] + void setupgrade( const eosio::upgrade_parameters& params); + + private: // Implementation details: - //defind in delegate_bandwidth.cpp - void changebw( account_name from, account_name receiver, - asset stake_net_quantity, asset stake_cpu_quantity, bool transfer ); + static symbol get_core_symbol( const rammarket& rm ) { + auto itr = rm.find(ramcore_symbol.raw()); + eosio_assert(itr != rm.end(), "system contract must first be initialized"); + return itr->quote.balance.symbol; + } - //defined in voting.hpp + //defined in eosio.system.cpp static eosio_global_state get_default_parameters(); + static time_point current_time_point(); + static block_timestamp current_block_time(); + + symbol core_symbol()const; - void update_votes( const account_name voter, const account_name proxy, const std::vector& producers, bool voting ); + void update_ram_supply(); + + //defined in delegate_bandwidth.cpp + void changebw( name from, name receiver, + asset stake_net_quantity, asset stake_cpu_quantity, bool transfer ); + + //defined in voting.hpp + void update_elected_producers( block_timestamp timestamp ); + void update_votes( const name voter, const name proxy, const std::vector& producers, bool voting ); // defined in voting.cpp void propagate_weight_change( const voter_info& voter ); + + double update_producer_votepay_share( const producers_table2::const_iterator& prod_itr, + time_point ct, + double shares_rate, bool reset_to_zero = false ); + double update_total_votepay_share( time_point ct, + double additional_shares_delta = 0.0, double shares_rate_delta = 0.0 ); }; } /// eosiosystem diff --git a/contracts/eosio.system/exchange_state.cpp b/contracts/eosio.system/exchange_state.cpp index 621d3e714b3..28b43de7c7f 100644 --- a/contracts/eosio.system/exchange_state.cpp +++ b/contracts/eosio.system/exchange_state.cpp @@ -5,12 +5,11 @@ namespace eosiosystem { real_type R(supply.amount); real_type C(c.balance.amount+in.amount); - real_type F(c.weight/1000.0); + real_type F(c.weight); real_type T(in.amount); real_type ONE(1.0); real_type E = -R * (ONE - std::pow( ONE + T / C, F) ); - //print( "E: ", E, "\n"); int64_t issued = int64_t(E); supply.amount += issued; @@ -24,7 +23,7 @@ namespace eosiosystem { real_type R(supply.amount - in.amount); real_type C(c.balance.amount); - real_type F(1000.0/c.weight); + real_type F(1.0/c.weight); real_type E(in.amount); real_type ONE(1.0); @@ -36,7 +35,6 @@ namespace eosiosystem { // real_type T = C * std::expm1( F * std::log1p(E/R) ); real_type T = C * (std::pow( ONE + E/R, F) - ONE); - //print( "T: ", T, "\n"); int64_t out = int64_t(T); supply.amount -= in.amount; @@ -45,7 +43,7 @@ namespace eosiosystem { return asset( out, c.balance.symbol ); } - asset exchange_state::convert( asset from, symbol_type to ) { + asset exchange_state::convert( asset from, const symbol& to ) { auto sell_symbol = from.symbol; auto ex_symbol = supply.symbol; auto base_symbol = base.balance.symbol; diff --git a/contracts/eosio.system/exchange_state.hpp b/contracts/eosio.system/exchange_state.hpp index 3705a9b8b98..aba1eefeeb6 100644 --- a/contracts/eosio.system/exchange_state.hpp +++ b/contracts/eosio.system/exchange_state.hpp @@ -4,7 +4,7 @@ namespace eosiosystem { using eosio::asset; - using eosio::symbol_type; + using eosio::symbol; typedef double real_type; @@ -13,7 +13,7 @@ namespace eosiosystem { * bancor exchange is entirely contained within this struct. There are no external * side effects associated with using this API. */ - struct exchange_state { + struct [[eosio::table, eosio::contract("eosio.system")]] exchange_state { asset supply; struct connector { @@ -26,15 +26,15 @@ namespace eosiosystem { connector base; connector quote; - uint64_t primary_key()const { return supply.symbol; } + uint64_t primary_key()const { return supply.symbol.raw(); } - asset convert_to_exchange( connector& c, asset in ); + asset convert_to_exchange( connector& c, asset in ); asset convert_from_exchange( connector& c, asset in ); - asset convert( asset from, symbol_type to ); + asset convert( asset from, const symbol& to ); EOSLIB_SERIALIZE( exchange_state, (supply)(base)(quote) ) }; - typedef eosio::multi_index rammarket; + typedef eosio::multi_index< "rammarket"_n, exchange_state > rammarket; } /// namespace eosiosystem diff --git a/contracts/eosio.system/native.hpp b/contracts/eosio.system/native.hpp index e2bcb319575..61a23eee0ce 100644 --- a/contracts/eosio.system/native.hpp +++ b/contracts/eosio.system/native.hpp @@ -1,59 +1,66 @@ /** * @file - * @copyright defined in eos/LICENSE + * @copyright defined in eos/LICENSE.txt */ #pragma once #include #include -#include #include #include -#include #include #include +#include namespace eosiosystem { + using eosio::name; using eosio::permission_level; using eosio::public_key; - - typedef std::vector bytes; + using eosio::ignore; struct permission_level_weight { permission_level permission; - weight_type weight; + uint16_t weight; // explicit serialization macro is not necessary, used here only to improve compilation time EOSLIB_SERIALIZE( permission_level_weight, (permission)(weight) ) }; struct key_weight { - public_key key; - weight_type weight; + eosio::public_key key; + uint16_t weight; // explicit serialization macro is not necessary, used here only to improve compilation time EOSLIB_SERIALIZE( key_weight, (key)(weight) ) }; + struct wait_weight { + uint32_t wait_sec; + uint16_t weight; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( wait_weight, (wait_sec)(weight) ) + }; + struct authority { - uint32_t threshold; - uint32_t delay_sec; + uint32_t threshold = 0; std::vector keys; std::vector accounts; + std::vector waits; // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( authority, (threshold)(delay_sec)(keys)(accounts) ) + EOSLIB_SERIALIZE( authority, (threshold)(keys)(accounts)(waits) ) }; struct block_header { uint32_t timestamp; - account_name producer; + name producer; uint16_t confirmed = 0; - block_id_type previous; - checksum256 transaction_mroot; - checksum256 action_mroot; + capi_checksum256 previous; + capi_checksum256 transaction_mroot; + capi_checksum256 action_mroot; uint32_t schedule_version = 0; - eosio::optional new_producers; + std::optional new_producers; // explicit serialization macro is not necessary, used here only to improve compilation time EOSLIB_SERIALIZE(block_header, (timestamp)(producer)(confirmed)(previous)(transaction_mroot)(action_mroot) @@ -61,10 +68,18 @@ namespace eosiosystem { }; + struct [[eosio::table("abihash"), eosio::contract("eosio.system")]] abi_hash { + name owner; + capi_checksum256 hash; + uint64_t primary_key()const { return owner.value; } + + EOSLIB_SERIALIZE( abi_hash, (owner)(hash) ) + }; + /* * Method parameters commented out to prevent generation of code that parses input data. */ - class native : public eosio::contract { + class [[eosio::contract("eosio.system")]] native : public eosio::contract { public: using eosio::contract::contract; @@ -81,32 +96,44 @@ namespace eosiosystem { * therefore, this method will execute an inline buyram from receiver for newacnt in * an amount equal to the current new account creation fee. */ - void newaccount( account_name creator, - account_name newact - /* no need to parse authorites - const authority& owner, - const authority& active*/ ); + [[eosio::action]] + void newaccount( name creator, + name newact, + ignore owner, + ignore active); + + [[eosio::action]] + void updateauth( ignore account, + ignore permission, + ignore parent, + ignore auth ) {} - void updateauth( /*account_name account, - permission_name permission, - permission_name parent, - const authority& data*/ ) {} + [[eosio::action]] + void deleteauth( ignore account, + ignore permission ) {} - void deleteauth( /*account_name account, permission_name permission*/ ) {} + [[eosio::action]] + void linkauth( ignore account, + ignore code, + ignore type, + ignore requirement ) {} - void linkauth( /*account_name account, - account_name code, - action_name type, - permission_name requirement*/ ) {} + [[eosio::action]] + void unlinkauth( ignore account, + ignore code, + ignore type ) {} - void unlinkauth( /*account_name account, - account_name code, - action_name type*/ ) {} + [[eosio::action]] + void canceldelay( ignore canceling_auth, ignore trx_id ) {} - void canceldelay( /*permission_level canceling_auth, transaction_id_type trx_id*/ ) {} + [[eosio::action]] + void onerror( ignore sender_id, ignore> sent_trx ) {} - void onerror( /*const bytes&*/ ) {} + [[eosio::action]] + void setabi( name account, const std::vector& abi ); + [[eosio::action]] + void setcode( name account, uint8_t vmtype, uint8_t vmversion, const std::vector& code ) {} }; } diff --git a/contracts/eosio.system/producer_pay.cpp b/contracts/eosio.system/producer_pay.cpp index 1d0af68d432..7d2b89f188d 100644 --- a/contracts/eosio.system/producer_pay.cpp +++ b/contracts/eosio.system/producer_pay.cpp @@ -1,139 +1,278 @@ -#include "eosio.system.hpp" +#include #include +#include namespace eosiosystem { const int64_t min_pervote_daily_pay = 100'0000; - const int64_t min_activated_stake = 150'000'000'0000; - const double continuous_rate = 0.04879; // 5% annual rate + const int64_t min_activated_stake = 10'000'0000; + // const double continuous_rate = 0.04879; // 5% annual rate + const double continuous_rate = 0.0198; // 2% annual rate const double perblock_rate = 0.0025; // 0.25% const double standby_rate = 0.0075; // 0.75% const uint32_t blocks_per_year = 52*7*24*2*3600; // half seconds per year const uint32_t seconds_per_year = 52*7*24*3600; const uint32_t blocks_per_day = 2 * 24 * 3600; const uint32_t blocks_per_hour = 2 * 3600; - const uint64_t useconds_per_day = 24 * 3600 * uint64_t(1000000); - const uint64_t useconds_per_year = seconds_per_year*1000000ll; + const int64_t useconds_per_day = 24 * 3600 * int64_t(1000000); + const int64_t useconds_per_year = seconds_per_year*1000000ll; - void system_contract::onblock( block_timestamp timestamp, account_name producer ) { + void system_contract::onblock( ignore ) { using namespace eosio; - require_auth(N(eosio)); + require_auth(_self); + + block_timestamp timestamp; + name producer; + _ds >> timestamp >> producer; + + // _gstate2.last_block_num is not used anywhere in the system contract code anymore. + // Although this field is deprecated, we will continue updating it for now until the last_block_num field + // is eventually completely removed, at which point this line can be removed. + _gstate2.last_block_num = timestamp; + + static const int64_t min_activated_time = 1547816400000000; /// 2019-01-18 21:00:00 UTC+8 + const static time_point at{ microseconds{ static_cast( min_activated_time) } }; + + if (current_time_point() >= at&& _gstate.thresh_activated_stake_time == time_point()) + { + _gstate.thresh_activated_stake_time = current_time_point(); + } /** until activated stake crosses this threshold no new rewards are paid */ - if( _gstate.total_activated_stake < min_activated_stake ) + // if( _gstate.total_activated_stake < min_activated_stake ) + if(_gstate.thresh_activated_stake_time == time_point()) return; - if( _gstate.last_pervote_bucket_fill == 0 ) /// start the presses - _gstate.last_pervote_bucket_fill = current_time(); + if( _gstate.last_pervote_bucket_fill == time_point() ) /// start the presses + _gstate.last_pervote_bucket_fill = current_time_point(); /** * At startup the initial producer may not be one that is registered / elected * and therefore there may be no producer object for them. */ - auto prod = _producers.find(producer); + auto prod = _producers.find( producer.value ); if ( prod != _producers.end() ) { _gstate.total_unpaid_blocks++; - _producers.modify( prod, 0, [&](auto& p ) { + _producers.modify( prod, same_payer, [&](auto& p ) { p.unpaid_blocks++; }); } + auto modifybid = [&](auto &ns) { + name_bid_table bids(_self, _self.value); + for (auto &n : ns) + { + auto highest = bids.find(n.value); + if(highest != bids.end()) + { + // print( highest->last_bid_time.sec_since_epoch(), " dealed high_bid: ", highest->high_bid, " newname: ", name{highest->newname}, "\n" ); + bids.modify(highest, same_payer, [&](auto &b) { + b.high_bid = -b.high_bid; + }); + } + } + }; + + auto checkbidname = [&](auto &highest, auto &idx) { + if (highest == idx.end()) + { + return; + } + std::vector names; + static const int16_t COUNT10 = 10; + uint16_t deal_count = 0; + + if (highest->newname.length() >= BASE_LENGTH) + { + deal_count++; + } + + names.push_back(highest->newname); + // print( highest->last_bid_time.sec_since_epoch(), " deal high_bid: ", highest->high_bid, " newname: ", name{highest->newname}, "\n" ); + for (int16_t i = 0; ++highest != idx.end() && i < COUNT10; i++) + { + // print( highest->last_bid_time.sec_since_epoch(), " high_bid: ", highest->high_bid, " newname: ", name{highest->newname}, "\n" ); + if (highest->high_bid > 0 && + (current_time_point() - highest->last_bid_time) > microseconds(useconds_per_day) && + highest->newname.length() >= BASE_LENGTH) + { + names.push_back(highest->newname); + deal_count++; + // print( highest->last_bid_time.sec_since_epoch(), " deal high_bid: ", highest->high_bid, " newname: ", name{highest->newname}, "\n" ); + } + + if (COUNT10 == deal_count) + { + break; + } + } + + modifybid(names); + }; /// only update block producers once every minute, block_timestamp is in half seconds - if( timestamp.slot - _gstate.last_producer_schedule_update.slot > 120 ) { - update_elected_producers( timestamp ); - - if( (timestamp.slot - _gstate.last_name_close.slot) > blocks_per_day ) { - name_bid_table bids(_self,_self); - auto idx = bids.get_index(); - auto highest = idx.begin(); - if( highest != idx.end() && + if (timestamp.slot - _gstate.last_producer_schedule_update.slot > 120) { + update_elected_producers(timestamp); + + if ((timestamp.slot - _gstate.last_name_close.slot) > blocks_per_day){ + name_bid_table bids(_self, _self.value); + auto idx = bids.get_index<"highbid"_n>(); + auto highest = idx.lower_bound(std::numeric_limits::max() / 2); + if (highest != idx.end() && highest->high_bid > 0 && - highest->last_bid_time < (current_time() - useconds_per_day) && - _gstate.thresh_activated_stake_time > 0 && - (current_time() - _gstate.thresh_activated_stake_time) > 14 * useconds_per_day ) { - _gstate.last_name_close = timestamp; - idx.modify( highest, 0, [&]( auto& b ){ - b.high_bid = -b.high_bid; - }); + (current_time_point() - highest->last_bid_time) > microseconds(useconds_per_day) && + _gstate.thresh_activated_stake_time > time_point() && + (current_time_point() - _gstate.thresh_activated_stake_time) > microseconds(14*useconds_per_day)){ + _gstate.last_name_close = timestamp; + + checkbidname(highest, idx); } } } } using namespace eosio; - void system_contract::claimrewards( const account_name& owner ) { - require_auth(owner); + void system_contract::claimrewards( const name owner ) { + require_auth( owner ); - const auto& prod = _producers.get( owner ); + const auto& prod = _producers.get( owner.value ); eosio_assert( prod.active(), "producer does not have an active key" ); - eosio_assert( _gstate.total_activated_stake >= min_activated_stake, - "cannot claim rewards until the chain is activated (at least 15% of all tokens participate in voting)" ); + // eosio_assert( _gstate.total_activated_stake >= min_activated_stake, + // "cannot claim rewards until the chain is activated (at least 15% of all tokens participate in voting)" ); + eosio_assert( _gstate.thresh_activated_stake_time != time_point(), + "cannot claim rewards until the chain is activated " ); - auto ct = current_time(); + const auto ct = current_time_point(); - eosio_assert( ct - prod.last_claim_time > useconds_per_day, "already claimed rewards within past day" ); + eosio_assert( ct - prod.last_claim_time > microseconds(useconds_per_day), "already claimed rewards within past day" ); - const asset token_supply = token( N(eosio.token)).get_supply(symbol_type(system_token_symbol).name() ); - const auto usecs_since_last_fill = ct - _gstate.last_pervote_bucket_fill; + const asset token_supply = eosio::token::get_supply(token_account, core_symbol().code() ); + const auto usecs_since_last_fill = (ct - _gstate.last_pervote_bucket_fill).count(); - if( usecs_since_last_fill > 0 && _gstate.last_pervote_bucket_fill > 0 ) { + if( usecs_since_last_fill > 0 && _gstate.last_pervote_bucket_fill > time_point() ) { auto new_tokens = static_cast( (continuous_rate * double(token_supply.amount) * double(usecs_since_last_fill)) / double(useconds_per_year) ); - auto to_producers = new_tokens / 5; - auto to_savings = new_tokens - to_producers; - auto to_per_block_pay = to_producers / 4; - auto to_per_vote_pay = to_producers - to_per_block_pay; + // auto to_producers = new_tokens / 5; + // auto to_savings = new_tokens - to_producers; + auto to_producers = new_tokens / 2; + auto to_savings = new_tokens - to_producers; + auto to_per_block_pay = to_producers / 4; + auto to_per_vote_pay = to_producers - to_per_block_pay; + auto to_gov_fund = to_savings / 5; + auto to_dev_fund = to_savings - to_gov_fund; - INLINE_ACTION_SENDER(eosio::token, issue)( N(eosio.token), {{N(eosio),N(active)}}, - {N(eosio), asset(new_tokens), std::string("issue tokens for producer pay and savings")} ); + INLINE_ACTION_SENDER(eosio::token, issue)( + token_account, { {_self, active_permission} }, + { _self, asset(new_tokens, core_symbol()), std::string("issue tokens for producer pay and savings") } + ); - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, - { N(eosio), N(eosio.saving), asset(to_savings), "unallocated inflation" } ); + // INLINE_ACTION_SENDER(eosio::token, transfer)( + // token_account, { {_self, active_permission} }, + // { _self, saving_account, asset(to_savings, core_symbol()), "unallocated inflation" } + // ); - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, - { N(eosio), N(eosio.bpay), asset(to_per_block_pay), "fund per-block bucket" } ); + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {_self, active_permission} }, + { _self, dev_account, asset(to_dev_fund, core_symbol()), "unallocated inflation" } + ); - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, - { N(eosio), N(eosio.vpay), asset(to_per_vote_pay), "fund per-vote bucket" } ); + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {_self, active_permission} }, + { _self, gov_account, asset(to_gov_fund, core_symbol()), "unallocated inflation" } + ); - _gstate.pervote_bucket += to_per_vote_pay; - _gstate.perblock_bucket += to_per_block_pay; + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {_self, active_permission} }, + { _self, bpay_account, asset(to_per_block_pay, core_symbol()), "fund per-block bucket" } + ); + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {_self, active_permission} }, + { _self, vpay_account, asset(to_per_vote_pay, core_symbol()), "fund per-vote bucket" } + ); + + _gstate.pervote_bucket += to_per_vote_pay; + _gstate.perblock_bucket += to_per_block_pay; _gstate.last_pervote_bucket_fill = ct; } + auto prod2 = _producers2.find( owner.value ); + + /// New metric to be used in pervote pay calculation. Instead of vote weight ratio, we combine vote weight and + /// time duration the vote weight has been held into one metric. + const auto last_claim_plus_3days = prod.last_claim_time + microseconds(3 * useconds_per_day); + + bool crossed_threshold = (last_claim_plus_3days <= ct); + bool updated_after_threshold = true; + if ( prod2 != _producers2.end() ) { + updated_after_threshold = (last_claim_plus_3days <= prod2->last_votepay_share_update); + } else { + prod2 = _producers2.emplace( owner, [&]( producer_info2& info ) { + info.owner = owner; + info.last_votepay_share_update = ct; + }); + } + + // Note: updated_after_threshold implies cross_threshold (except if claiming rewards when the producers2 table row did not exist). + // The exception leads to updated_after_threshold to be treated as true regardless of whether the threshold was crossed. + // This is okay because in this case the producer will not get paid anything either way. + // In fact it is desired behavior because the producers votes need to be counted in the global total_producer_votepay_share for the first time. + int64_t producer_per_block_pay = 0; if( _gstate.total_unpaid_blocks > 0 ) { producer_per_block_pay = (_gstate.perblock_bucket * prod.unpaid_blocks) / _gstate.total_unpaid_blocks; } + + double new_votepay_share = update_producer_votepay_share( prod2, + ct, + updated_after_threshold ? 0.0 : prod.total_votes, + true // reset votepay_share to zero after updating + ); + int64_t producer_per_vote_pay = 0; - if( _gstate.total_producer_vote_weight > 0 ) { - producer_per_vote_pay = int64_t((_gstate.pervote_bucket * prod.total_votes ) / _gstate.total_producer_vote_weight); + if( _gstate2.revision > 0 ) { + double total_votepay_share = update_total_votepay_share( ct ); + if( total_votepay_share > 0 && !crossed_threshold ) { + producer_per_vote_pay = int64_t((new_votepay_share * _gstate.pervote_bucket) / total_votepay_share); + if( producer_per_vote_pay > _gstate.pervote_bucket ) + producer_per_vote_pay = _gstate.pervote_bucket; + } + } else { + if( _gstate.total_producer_vote_weight > 0 ) { + producer_per_vote_pay = int64_t((_gstate.pervote_bucket * prod.total_votes) / _gstate.total_producer_vote_weight); + } } + if( producer_per_vote_pay < min_pervote_daily_pay ) { producer_per_vote_pay = 0; } + _gstate.pervote_bucket -= producer_per_vote_pay; _gstate.perblock_bucket -= producer_per_block_pay; _gstate.total_unpaid_blocks -= prod.unpaid_blocks; - _producers.modify( prod, 0, [&](auto& p) { - p.last_claim_time = ct; - p.unpaid_blocks = 0; + update_total_votepay_share( ct, -new_votepay_share, (updated_after_threshold ? prod.total_votes : 0.0) ); + + _producers.modify( prod, same_payer, [&](auto& p) { + p.last_claim_time = ct; + p.unpaid_blocks = 0; }); if( producer_per_block_pay > 0 ) { - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.bpay),N(active)}, - { N(eosio.bpay), owner, asset(producer_per_block_pay), std::string("producer block pay") } ); + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {bpay_account, active_permission}, {owner, active_permission} }, + { bpay_account, owner, asset(producer_per_block_pay, core_symbol()), std::string("producer block pay") } + ); } if( producer_per_vote_pay > 0 ) { - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.vpay),N(active)}, - { N(eosio.vpay), owner, asset(producer_per_vote_pay), std::string("producer vote pay") } ); + INLINE_ACTION_SENDER(eosio::token, transfer)( + token_account, { {vpay_account, active_permission}, {owner, active_permission} }, + { vpay_account, owner, asset(producer_per_vote_pay, core_symbol()), std::string("producer vote pay") } + ); } } diff --git a/contracts/eosio.system/upgrade.cpp b/contracts/eosio.system/upgrade.cpp new file mode 100644 index 00000000000..32e99d8c287 --- /dev/null +++ b/contracts/eosio.system/upgrade.cpp @@ -0,0 +1,14 @@ +#include + +namespace eosiosystem { + + void system_contract::setupgrade( const eosio::upgrade_parameters& params) { + require_auth( _self ); + + (eosio::upgrade_parameters&)(_ustate) = params; + set_upgrade_parameters( params ); + + _ustate.current_version += 1; + _ustate.target_block_num = params.target_block_num; + } +} diff --git a/contracts/eosio.system/voting.cpp b/contracts/eosio.system/voting.cpp index 8076f79886d..8213d7044bf 100644 --- a/contracts/eosio.system/voting.cpp +++ b/contracts/eosio.system/voting.cpp @@ -1,8 +1,8 @@ /** * @file - * @copyright defined in eos/LICENSE + * @copyright defined in eos/LICENSE.txt */ -#include "eosio.system.hpp" +#include #include #include @@ -21,7 +21,6 @@ namespace eosiosystem { using eosio::indexed_by; using eosio::const_mem_fun; - using eosio::bytes; using eosio::print; using eosio::singleton; using eosio::transaction; @@ -34,46 +33,64 @@ namespace eosiosystem { * @pre authority of producer to register * */ - void system_contract::regproducer( const account_name producer, const eosio::public_key& producer_key, const std::string& url, uint16_t location ) { + void system_contract::regproducer( const name producer, const eosio::public_key& producer_key, const std::string& url, uint16_t location ) { eosio_assert( url.size() < 512, "url too long" ); eosio_assert( producer_key != eosio::public_key(), "public key should not be the default value" ); require_auth( producer ); - auto prod = _producers.find( producer ); + auto prod = _producers.find( producer.value ); + const auto ct = current_time_point(); if ( prod != _producers.end() ) { _producers.modify( prod, producer, [&]( producer_info& info ){ - info.producer_key = producer_key; - info.is_active = true; - info.url = url; - info.location = location; + info.producer_key = producer_key; + info.is_active = true; + info.url = url; + info.location = location; + if ( info.last_claim_time == time_point() ) + info.last_claim_time = ct; + }); + + auto prod2 = _producers2.find( producer.value ); + if ( prod2 == _producers2.end() ) { + _producers2.emplace( producer, [&]( producer_info2& info ){ + info.owner = producer; + info.last_votepay_share_update = ct; }); + update_total_votepay_share( ct, 0.0, prod->total_votes ); + // When introducing the producer2 table row for the first time, the producer's votes must also be accounted for in the global total_producer_votepay_share at the same time. + } } else { _producers.emplace( producer, [&]( producer_info& info ){ - info.owner = producer; - info.total_votes = 0; - info.producer_key = producer_key; - info.is_active = true; - info.url = url; - info.location = location; + info.owner = producer; + info.total_votes = 0; + info.producer_key = producer_key; + info.is_active = true; + info.url = url; + info.location = location; + info.last_claim_time = ct; + }); + _producers2.emplace( producer, [&]( producer_info2& info ){ + info.owner = producer; + info.last_votepay_share_update = ct; }); } + } - void system_contract::unregprod( const account_name producer ) { + void system_contract::unregprod( const name producer ) { require_auth( producer ); - const auto& prod = _producers.get( producer, "producer not found" ); - - _producers.modify( prod, 0, [&]( producer_info& info ){ - info.deactivate(); + const auto& prod = _producers.get( producer.value, "producer not found" ); + _producers.modify( prod, same_payer, [&]( producer_info& info ){ + info.deactivate(); }); } void system_contract::update_elected_producers( block_timestamp block_time ) { _gstate.last_producer_schedule_update = block_time; - auto idx = _producers.get_index(); + auto idx = _producers.get_index<"prototalvote"_n>(); std::vector< std::pair > top_producers; top_producers.reserve(21); @@ -93,8 +110,7 @@ namespace eosiosystem { return a.second ==b.second?a.first producers; @@ -102,7 +118,7 @@ namespace eosiosystem { for( const auto& item : top_producers ) producers.push_back(item.first); - bytes packed_schedule = pack(producers); + auto packed_schedule = pack(producers); if( set_proposed_producers( packed_schedule.data(), packed_schedule.size() ) >= 0 ) { _gstate.last_producer_schedule_size = static_cast( top_producers.size() ); @@ -114,6 +130,58 @@ namespace eosiosystem { double weight = int64_t( (now() - (block_timestamp::block_timestamp_epoch / 1000)) / (seconds_per_day * 7) ) / double( 52 ); return double(staked) * std::pow( 2, weight ); } + + double system_contract::update_total_votepay_share( time_point ct, + double additional_shares_delta, + double shares_rate_delta ) + { + double delta_total_votepay_share = 0.0; + if( ct > _gstate3.last_vpay_state_update ) { + delta_total_votepay_share = _gstate3.total_vpay_share_change_rate + * double( (ct - _gstate3.last_vpay_state_update).count() / 1E6 ); + } + + delta_total_votepay_share += additional_shares_delta; + if( delta_total_votepay_share < 0 && _gstate2.total_producer_votepay_share < -delta_total_votepay_share ) { + _gstate2.total_producer_votepay_share = 0.0; + } else { + _gstate2.total_producer_votepay_share += delta_total_votepay_share; + } + + if( shares_rate_delta < 0 && _gstate3.total_vpay_share_change_rate < -shares_rate_delta ) { + _gstate3.total_vpay_share_change_rate = 0.0; + } else { + _gstate3.total_vpay_share_change_rate += shares_rate_delta; + } + + _gstate3.last_vpay_state_update = ct; + + return _gstate2.total_producer_votepay_share; + } + + double system_contract::update_producer_votepay_share( const producers_table2::const_iterator& prod_itr, + time_point ct, + double shares_rate, + bool reset_to_zero ) + { + double delta_votepay_share = 0.0; + if( shares_rate > 0.0 && ct > prod_itr->last_votepay_share_update ) { + delta_votepay_share = shares_rate * double( (ct - prod_itr->last_votepay_share_update).count() / 1E6 ); // cannot be negative + } + + double new_votepay_share = prod_itr->votepay_share + delta_votepay_share; + _producers2.modify( prod_itr, same_payer, [&](auto& p) { + if( reset_to_zero ) + p.votepay_share = 0.0; + else + p.votepay_share = new_votepay_share; + + p.last_votepay_share_update = ct; + } ); + + return new_votepay_share; + } + /** * @pre producers must be sorted from lowest to highest and must be registered and active * @pre if proxy is set then no producers can be voted for @@ -130,17 +198,16 @@ namespace eosiosystem { * * If voting for a proxy, the producer votes will not change until the proxy updates their own vote. */ - void system_contract::voteproducer( const account_name voter_name, const account_name proxy, const std::vector& producers ) { + void system_contract::voteproducer( const name voter_name, const name proxy, const std::vector& producers ) { require_auth( voter_name ); update_votes( voter_name, proxy, producers, true ); } - void system_contract::update_votes( const account_name voter_name, const account_name proxy, const std::vector& producers, bool voting ) { + void system_contract::update_votes( const name voter_name, const name proxy, const std::vector& producers, bool voting ) { //validate input if ( proxy ) { eosio_assert( producers.size() == 0, "cannot vote for producers and proxy at same time" ); eosio_assert( voter_name != proxy, "cannot proxy to self" ); - require_recipient( proxy ); } else { eosio_assert( producers.size() <= 30, "attempt to vote for too many producers" ); for( size_t i = 1; i < producers.size(); ++i ) { @@ -148,7 +215,7 @@ namespace eosiosystem { } } - auto voter = _voters.find(voter_name); + auto voter = _voters.find( voter_name.value ); eosio_assert( voter != _voters.end(), "user must stake before they can vote" ); /// staking creates voter object eosio_assert( !proxy || !voter->is_proxy, "account registered as a proxy is not allowed to use a proxy" ); @@ -159,9 +226,10 @@ namespace eosiosystem { */ if( voter->last_vote_weight <= 0.0 ) { _gstate.total_activated_stake += voter->staked; - if( _gstate.total_activated_stake >= min_activated_stake && _gstate.thresh_activated_stake_time == 0 ) { - _gstate.thresh_activated_stake_time = current_time(); - } + /// modified + // if( _gstate.total_activated_stake >= min_activated_stake && _gstate.thresh_activated_stake_time == time_point() ) { + // _gstate.thresh_activated_stake_time = current_time_point(); + // } } auto new_vote_weight = stake2vote( voter->staked ); @@ -169,12 +237,12 @@ namespace eosiosystem { new_vote_weight += voter->proxied_vote_weight; } - boost::container::flat_map > producer_deltas; + boost::container::flat_map > producer_deltas; if ( voter->last_vote_weight > 0 ) { if( voter->proxy ) { - auto old_proxy = _voters.find( voter->proxy ); + auto old_proxy = _voters.find( voter->proxy.value ); eosio_assert( old_proxy != _voters.end(), "old proxy not found" ); //data corruption - _voters.modify( old_proxy, 0, [&]( auto& vp ) { + _voters.modify( old_proxy, same_payer, [&]( auto& vp ) { vp.proxied_vote_weight -= voter->last_vote_weight; }); propagate_weight_change( *old_proxy ); @@ -188,11 +256,11 @@ namespace eosiosystem { } if( proxy ) { - auto new_proxy = _voters.find( proxy ); + auto new_proxy = _voters.find( proxy.value ); eosio_assert( new_proxy != _voters.end(), "invalid proxy specified" ); //if ( !voting ) { data corruption } else { wrong vote } eosio_assert( !voting || new_proxy->is_proxy, "proxy not found" ); if ( new_vote_weight >= 0 ) { - _voters.modify( new_proxy, 0, [&]( auto& vp ) { + _voters.modify( new_proxy, same_payer, [&]( auto& vp ) { vp.proxied_vote_weight += new_vote_weight; }); propagate_weight_change( *new_proxy ); @@ -207,11 +275,15 @@ namespace eosiosystem { } } + const auto ct = current_time_point(); + double delta_change_rate = 0.0; + double total_inactive_vpay_share = 0.0; for( const auto& pd : producer_deltas ) { - auto pitr = _producers.find( pd.first ); + auto pitr = _producers.find( pd.first.value ); if( pitr != _producers.end() ) { eosio_assert( !voting || pitr->active() || !pd.second.second /* not from new set */, "producer is not currently registered" ); - _producers.modify( pitr, 0, [&]( auto& p ) { + double init_total_votes = pitr->total_votes; + _producers.modify( pitr, same_payer, [&]( auto& p ) { p.total_votes += pd.second.first; if ( p.total_votes < 0 ) { // floating point arithmetics can give small negative numbers p.total_votes = 0; @@ -219,12 +291,34 @@ namespace eosiosystem { _gstate.total_producer_vote_weight += pd.second.first; //eosio_assert( p.total_votes >= 0, "something bad happened" ); }); + auto prod2 = _producers2.find( pd.first.value ); + if( prod2 != _producers2.end() ) { + const auto last_claim_plus_3days = pitr->last_claim_time + microseconds(3 * useconds_per_day); + bool crossed_threshold = (last_claim_plus_3days <= ct); + bool updated_after_threshold = (last_claim_plus_3days <= prod2->last_votepay_share_update); + // Note: updated_after_threshold implies cross_threshold + + double new_votepay_share = update_producer_votepay_share( prod2, + ct, + updated_after_threshold ? 0.0 : init_total_votes, + crossed_threshold && !updated_after_threshold // only reset votepay_share once after threshold + ); + + if( !crossed_threshold ) { + delta_change_rate += pd.second.first; + } else if( !updated_after_threshold ) { + total_inactive_vpay_share += new_votepay_share; + delta_change_rate -= init_total_votes; + } + } } else { eosio_assert( !pd.second.second /* not from new set */, "producer is not registered" ); //data corruption } } - _voters.modify( voter, 0, [&]( auto& av ) { + update_total_votepay_share( ct, -total_inactive_vpay_share, delta_change_rate ); + + _voters.modify( voter, same_payer, [&]( auto& av ) { av.last_vote_weight = new_vote_weight; av.producers = producers; av.proxy = proxy; @@ -240,14 +334,14 @@ namespace eosiosystem { * @pre proxy must have something staked (existing row in voters table) * @pre new state must be different than current state */ - void system_contract::regproxy( const account_name proxy, bool isproxy ) { + void system_contract::regproxy( const name proxy, bool isproxy ) { require_auth( proxy ); - auto pitr = _voters.find(proxy); + auto pitr = _voters.find( proxy.value ); if ( pitr != _voters.end() ) { eosio_assert( isproxy != pitr->is_proxy, "action has no effect" ); eosio_assert( !isproxy || !pitr->proxy, "account that uses a proxy is not allowed to become a proxy" ); - _voters.modify( pitr, 0, [&]( auto& p ) { + _voters.modify( pitr, same_payer, [&]( auto& p ) { p.is_proxy = isproxy; }); propagate_weight_change( *pitr ); @@ -260,7 +354,7 @@ namespace eosiosystem { } void system_contract::propagate_weight_change( const voter_info& voter ) { - eosio_assert( voter.proxy == 0 || !voter.is_proxy, "account registered as a proxy is not allowed to use a proxy" ); + eosio_assert( !voter.proxy || !voter.is_proxy, "account registered as a proxy is not allowed to use a proxy" ); double new_weight = stake2vote( voter.staked ); if ( voter.is_proxy ) { new_weight += voter.proxied_vote_weight; @@ -269,24 +363,50 @@ namespace eosiosystem { /// don't propagate small changes (1 ~= epsilon) if ( fabs( new_weight - voter.last_vote_weight ) > 1 ) { if ( voter.proxy ) { - auto& proxy = _voters.get( voter.proxy, "proxy not found" ); //data corruption - _voters.modify( proxy, 0, [&]( auto& p ) { + auto& proxy = _voters.get( voter.proxy.value, "proxy not found" ); //data corruption + _voters.modify( proxy, same_payer, [&]( auto& p ) { p.proxied_vote_weight += new_weight - voter.last_vote_weight; } ); propagate_weight_change( proxy ); } else { auto delta = new_weight - voter.last_vote_weight; + const auto ct = current_time_point(); + double delta_change_rate = 0; + double total_inactive_vpay_share = 0; for ( auto acnt : voter.producers ) { - auto& pitr = _producers.get( acnt, "producer not found" ); //data corruption - _producers.modify( pitr, 0, [&]( auto& p ) { - p.total_votes += delta; - _gstate.total_producer_vote_weight += delta; + auto& prod = _producers.get( acnt.value, "producer not found" ); //data corruption + const double init_total_votes = prod.total_votes; + _producers.modify( prod, same_payer, [&]( auto& p ) { + p.total_votes += delta; + _gstate.total_producer_vote_weight += delta; }); + auto prod2 = _producers2.find( acnt.value ); + if ( prod2 != _producers2.end() ) { + const auto last_claim_plus_3days = prod.last_claim_time + microseconds(3 * useconds_per_day); + bool crossed_threshold = (last_claim_plus_3days <= ct); + bool updated_after_threshold = (last_claim_plus_3days <= prod2->last_votepay_share_update); + // Note: updated_after_threshold implies cross_threshold + + double new_votepay_share = update_producer_votepay_share( prod2, + ct, + updated_after_threshold ? 0.0 : init_total_votes, + crossed_threshold && !updated_after_threshold // only reset votepay_share once after threshold + ); + + if( !crossed_threshold ) { + delta_change_rate += delta; + } else if( !updated_after_threshold ) { + total_inactive_vpay_share += new_votepay_share; + delta_change_rate -= init_total_votes; + } + } } + + update_total_votepay_share( ct, -total_inactive_vpay_share, delta_change_rate ); } } - _voters.modify( voter, 0, [&]( auto& v ) { + _voters.modify( voter, same_payer, [&]( auto& v ) { v.last_vote_weight = new_weight; } ); diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 1310f589d65..d77d5b4d7e8 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1260,7 +1260,8 @@ struct controller_impl { try { const auto& upo = db.get().upgrade_target_block_num; - upgrading = (head->block_num + 1 >= upo) && head->dpos_irreversible_blocknum <= upo + 12; + upgrading = (head->block_num + 1 >= upo) + && std::max(head->dpos_irreversible_blocknum, head->bft_irreversible_blocknum) <= upo + 12; } catch( const boost::exception& e) { db.create([](auto&){}); } diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 7e105386011..9399302f246 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -57,7 +57,7 @@ target_link_libraries( ${NODE_EXECUTABLE_NAME} PRIVATE -Wl,${whole_archive_flag} chain_api_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} net_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} net_api_plugin -Wl,${no_whole_archive_flag} - PRIVATE -Wl,${whole_archive_flag} pbft_plugin -Wl,${no_whole_archive_flag} +# PRIVATE -Wl,${whole_archive_flag} pbft_plugin -Wl,${no_whole_archive_flag} # PRIVATE -Wl,${whole_archive_flag} faucet_testnet_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} txn_test_gen_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} db_size_api_plugin -Wl,${no_whole_archive_flag} @@ -65,7 +65,7 @@ target_link_libraries( ${NODE_EXECUTABLE_NAME} PRIVATE -Wl,${whole_archive_flag} test_control_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} test_control_api_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${build_id_flag} - PRIVATE chain_plugin http_plugin producer_plugin http_client_plugin + PRIVATE chain_plugin http_plugin producer_plugin http_client_plugin pbft_plugin PRIVATE eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) if(BUILD_MONGO_DB_PLUGIN) diff --git a/programs/nodeos/main.cpp b/programs/nodeos/main.cpp index 52eb9a0e9ab..a21babac350 100644 --- a/programs/nodeos/main.cpp +++ b/programs/nodeos/main.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -101,7 +102,7 @@ int main(int argc, char** argv) .default_unix_socket_path = "", .default_http_port = 8888 }); - if(!app().initialize(argc, argv)) + if(!app().initialize(argc, argv)) return INITIALIZE_FAIL; initialize_logging(); ilog("nodeos version ${ver}", ("ver", app().version_string())); From dab790e8f4ee5eb08debdce752244fc58e881eae Mon Sep 17 00:00:00 2001 From: oldcold Date: Fri, 5 Apr 2019 14:56:54 +0800 Subject: [PATCH 17/59] revert eosio.system contract; upgrade version can be found from a forked repo. --- contracts/eosio.system/CMakeLists.txt | 9 +- contracts/eosio.system/delegate_bandwidth.cpp | 315 +-- contracts/eosio.system/eosio.system.abi | 2056 +++++------------ contracts/eosio.system/eosio.system.cpp | 509 +--- contracts/eosio.system/eosio.system.hpp | 333 +-- contracts/eosio.system/eosio.system.wasm | Bin 174107 -> 0 bytes contracts/eosio.system/exchange_state.cpp | 8 +- contracts/eosio.system/exchange_state.hpp | 10 +- contracts/eosio.system/native.hpp | 99 +- contracts/eosio.system/producer_pay.cpp | 259 +-- contracts/eosio.system/upgrade.cpp | 14 - contracts/eosio.system/voting.cpp | 220 +- 12 files changed, 1001 insertions(+), 2831 deletions(-) delete mode 100755 contracts/eosio.system/eosio.system.wasm delete mode 100644 contracts/eosio.system/upgrade.cpp diff --git a/contracts/eosio.system/CMakeLists.txt b/contracts/eosio.system/CMakeLists.txt index 27e63078a71..eb4ff749f77 100644 --- a/contracts/eosio.system/CMakeLists.txt +++ b/contracts/eosio.system/CMakeLists.txt @@ -2,8 +2,7 @@ file(GLOB ABI_FILES "*.abi") configure_file("${ABI_FILES}" "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY) add_wast_executable(TARGET eosio.system - INCLUDE_FOLDERS "${STANDARD_INCLUDE_FOLDERS}" - LIBRARIES libc++ libc eosiolib eosio.token - DESTINATION_FOLDER ${CMAKE_CURRENT_BINARY_DIR} - ) - + INCLUDE_FOLDERS ${STANDARD_INCLUDE_FOLDERS} + LIBRARIES libc++ libc eosiolib eosio.token + DESTINATION_FOLDER ${CMAKE_CURRENT_BINARY_DIR} +) diff --git a/contracts/eosio.system/delegate_bandwidth.cpp b/contracts/eosio.system/delegate_bandwidth.cpp index 4570df9b76c..a5e9ad14efe 100644 --- a/contracts/eosio.system/delegate_bandwidth.cpp +++ b/contracts/eosio.system/delegate_bandwidth.cpp @@ -1,8 +1,8 @@ /** * @file - * @copyright defined in eos/LICENSE.txt + * @copyright defined in eos/LICENSE */ -#include +#include "eosio.system.hpp" #include #include @@ -22,22 +22,22 @@ namespace eosiosystem { using eosio::asset; using eosio::indexed_by; using eosio::const_mem_fun; + using eosio::bytes; using eosio::print; using eosio::permission_level; - using eosio::time_point_sec; using std::map; using std::pair; - static constexpr uint32_t refund_delay_sec = 3*24*3600; - static constexpr int64_t ram_gift_bytes = 1400; + static constexpr time refund_delay = 3*24*3600; + static constexpr time refund_expiration_time = 3600; - struct [[eosio::table, eosio::contract("eosio.system")]] user_resources { - name owner; + struct user_resources { + account_name owner; asset net_weight; asset cpu_weight; int64_t ram_bytes = 0; - uint64_t primary_key()const { return owner.value; } + uint64_t primary_key()const { return owner; } // explicit serialization macro is not necessary, used here only to improve compilation time EOSLIB_SERIALIZE( user_resources, (owner)(net_weight)(cpu_weight)(ram_bytes) ) @@ -47,26 +47,26 @@ namespace eosiosystem { /** * Every user 'from' has a scope/table that uses every receipient 'to' as the primary key. */ - struct [[eosio::table, eosio::contract("eosio.system")]] delegated_bandwidth { - name from; - name to; + struct delegated_bandwidth { + account_name from; + account_name to; asset net_weight; asset cpu_weight; - uint64_t primary_key()const { return to.value; } + uint64_t primary_key()const { return to; } // explicit serialization macro is not necessary, used here only to improve compilation time EOSLIB_SERIALIZE( delegated_bandwidth, (from)(to)(net_weight)(cpu_weight) ) }; - struct [[eosio::table, eosio::contract("eosio.system")]] refund_request { - name owner; - time_point_sec request_time; - eosio::asset net_amount; - eosio::asset cpu_amount; + struct refund_request { + account_name owner; + time request_time; + eosio::asset net_amount; + eosio::asset cpu_amount; - uint64_t primary_key()const { return owner.value; } + uint64_t primary_key()const { return owner; } // explicit serialization macro is not necessary, used here only to improve compilation time EOSLIB_SERIALIZE( refund_request, (owner)(request_time)(net_amount)(cpu_amount) ) @@ -76,20 +76,19 @@ namespace eosiosystem { * These tables are designed to be constructed in the scope of the relevant user, this * facilitates simpler API for per-user queries */ - typedef eosio::multi_index< "userres"_n, user_resources > user_resources_table; - typedef eosio::multi_index< "delband"_n, delegated_bandwidth > del_bandwidth_table; - typedef eosio::multi_index< "refunds"_n, refund_request > refunds_table; + typedef eosio::multi_index< N(userres), user_resources> user_resources_table; + typedef eosio::multi_index< N(delband), delegated_bandwidth> del_bandwidth_table; + typedef eosio::multi_index< N(refunds), refund_request> refunds_table; /** * This action will buy an exact amount of ram and bill the payer the current market price. */ - void system_contract::buyrambytes( name payer, name receiver, uint32_t bytes ) { - - auto itr = _rammarket.find(ramcore_symbol.raw()); + void system_contract::buyrambytes( account_name payer, account_name receiver, uint32_t bytes ) { + auto itr = _rammarket.find(S(4,RAMCORE)); auto tmp = *itr; - auto eosout = tmp.convert( asset(bytes, ram_symbol), core_symbol() ); + auto eosout = tmp.convert( asset(bytes,S(0,RAM)), CORE_SYMBOL ); buyram( payer, receiver, eosout ); } @@ -103,12 +102,9 @@ namespace eosiosystem { * RAM is a scarce resource whose supply is defined by global properties max_ram_size. RAM is * priced using the bancor algorithm such that price-per-byte with a constant reserve ratio of 100:1. */ - void system_contract::buyram( name payer, name receiver, asset quant ) + void system_contract::buyram( account_name payer, account_name receiver, asset quant ) { require_auth( payer ); - update_ram_supply(); - - eosio_assert( quant.symbol == core_symbol(), "must buy ram with core token" ); eosio_assert( quant.amount > 0, "must purchase a positive amount" ); auto fee = quant; @@ -121,23 +117,19 @@ namespace eosiosystem { // quant_after_fee.amount should be > 0 if quant.amount > 1. // If quant.amount == 1, then quant_after_fee.amount == 0 and the next inline transfer will fail causing the buyram action to fail. - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {payer, active_permission}, {ram_account, active_permission} }, - { payer, ram_account, quant_after_fee, std::string("buy ram") } - ); + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)}, + { payer, N(eosio.ram), quant_after_fee, std::string("buy ram") } ); if( fee.amount > 0 ) { - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {payer, active_permission} }, - { payer, ramfee_account, fee, std::string("ram fee") } - ); + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)}, + { payer, N(eosio.ramfee), fee, std::string("ram fee") } ); } int64_t bytes_out; - const auto& market = _rammarket.get(ramcore_symbol.raw(), "ram market does not exist"); - _rammarket.modify( market, same_payer, [&]( auto& es ) { - bytes_out = es.convert( quant_after_fee, ram_symbol ).amount; + const auto& market = _rammarket.get(S(4,RAMCORE), "ram market does not exist"); + _rammarket.modify( market, 0, [&]( auto& es ) { + bytes_out = es.convert( quant_after_fee, S(0,RAM) ).amount; }); eosio_assert( bytes_out > 0, "must reserve a positive amount" ); @@ -145,13 +137,11 @@ namespace eosiosystem { _gstate.total_ram_bytes_reserved += uint64_t(bytes_out); _gstate.total_ram_stake += quant_after_fee.amount; - user_resources_table userres( _self, receiver.value ); - auto res_itr = userres.find( receiver.value ); + user_resources_table userres( _self, receiver ); + auto res_itr = userres.find( receiver ); if( res_itr == userres.end() ) { res_itr = userres.emplace( receiver, [&]( auto& res ) { res.owner = receiver; - res.net_weight = asset( 0, core_symbol() ); - res.cpu_weight = asset( 0, core_symbol() ); res.ram_bytes = bytes_out; }); } else { @@ -159,37 +149,30 @@ namespace eosiosystem { res.ram_bytes += bytes_out; }); } - - auto voter_itr = _voters.find( res_itr->owner.value ); - if( voter_itr == _voters.end() || !has_field( voter_itr->flags1, voter_info::flags1_fields::ram_managed ) ) { - int64_t ram_bytes, net, cpu; - get_resource_limits( res_itr->owner.value, &ram_bytes, &net, &cpu ); - set_resource_limits( res_itr->owner.value, res_itr->ram_bytes + ram_gift_bytes, net, cpu ); - } + set_resource_limits( res_itr->owner, res_itr->ram_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); } - /** + + /** * The system contract now buys and sells RAM allocations at prevailing market prices. * This may result in traders buying RAM today in anticipation of potential shortages * tomorrow. Overall this will result in the market balancing the supply and demand * for RAM over time. */ - void system_contract::sellram( name account, int64_t bytes ) { + void system_contract::sellram( account_name account, int64_t bytes ) { require_auth( account ); - update_ram_supply(); - eosio_assert( bytes > 0, "cannot sell negative byte" ); - user_resources_table userres( _self, account.value ); - auto res_itr = userres.find( account.value ); + user_resources_table userres( _self, account ); + auto res_itr = userres.find( account ); eosio_assert( res_itr != userres.end(), "no resource row" ); eosio_assert( res_itr->ram_bytes >= bytes, "insufficient quota" ); asset tokens_out; - auto itr = _rammarket.find(ramcore_symbol.raw()); - _rammarket.modify( itr, same_payer, [&]( auto& es ) { + auto itr = _rammarket.find(S(4,RAMCORE)); + _rammarket.modify( itr, 0, [&]( auto& es ) { /// the cast to int64_t of bytes is safe because we certify bytes is <= quota which is limited by prior purchases - tokens_out = es.convert( asset(bytes, ram_symbol), core_symbol()); + tokens_out = es.convert( asset(bytes,S(0,RAM)), CORE_SYMBOL); }); eosio_assert( tokens_out.amount > 1, "token amount received from selling ram is too low" ); @@ -203,55 +186,46 @@ namespace eosiosystem { userres.modify( res_itr, account, [&]( auto& res ) { res.ram_bytes -= bytes; }); + set_resource_limits( res_itr->owner, res_itr->ram_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); - auto voter_itr = _voters.find( res_itr->owner.value ); - if( voter_itr == _voters.end() || !has_field( voter_itr->flags1, voter_info::flags1_fields::ram_managed ) ) { - int64_t ram_bytes, net, cpu; - get_resource_limits( res_itr->owner.value, &ram_bytes, &net, &cpu ); - set_resource_limits( res_itr->owner.value, res_itr->ram_bytes + ram_gift_bytes, net, cpu ); - } - - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {ram_account, active_permission}, {account, active_permission} }, - { ram_account, account, asset(tokens_out), std::string("sell ram") } - ); + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.ram),N(active)}, + { N(eosio.ram), account, asset(tokens_out), std::string("sell ram") } ); auto fee = ( tokens_out.amount + 199 ) / 200; /// .5% fee (round up) // since tokens_out.amount was asserted to be at least 2 earlier, fee.amount < tokens_out.amount + if( fee > 0 ) { - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {account, active_permission} }, - { account, ramfee_account, asset(fee, core_symbol()), std::string("sell ram fee") } - ); + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {account,N(active)}, + { account, N(eosio.ramfee), asset(fee), std::string("sell ram fee") } ); } } - void validate_bos_vesting( int64_t stake ) { - const int64_t base_time = 1546272000; /// 2019-01-01 00:00:00 - const int64_t max_claimable = 200'000'000'0000ll; - const int64_t claimable = int64_t(max_claimable * double(now()-base_time) / (4*seconds_per_year) ); + void validate_b1_vesting( int64_t stake ) { + const int64_t base_time = 1527811200; /// 2018-06-01 + const int64_t max_claimable = 100'000'000'0000ll; + const int64_t claimable = int64_t(max_claimable * double(now()-base_time) / (10*seconds_per_year) ); - eosio_assert( max_claimable - claimable <= stake, "bos can only claim their tokens over 4 years" ); + eosio_assert( max_claimable - claimable <= stake, "bos can only claim their tokens over 10 years" ); } - void system_contract::changebw( name from, name receiver, + void system_contract::changebw( account_name from, account_name receiver, const asset stake_net_delta, const asset stake_cpu_delta, bool transfer ) { require_auth( from ); - eosio_assert( stake_net_delta.amount != 0 || stake_cpu_delta.amount != 0, "should stake non-zero amount" ); + eosio_assert( stake_net_delta != asset(0) || stake_cpu_delta != asset(0), "should stake non-zero amount" ); eosio_assert( std::abs( (stake_net_delta + stake_cpu_delta).amount ) >= std::max( std::abs( stake_net_delta.amount ), std::abs( stake_cpu_delta.amount ) ), "net and cpu deltas cannot be opposite signs" ); - name source_stake_from = from; + account_name source_stake_from = from; if ( transfer ) { from = receiver; } // update stake delegated from "from" to "receiver" { - del_bandwidth_table del_tbl( _self, from.value ); - auto itr = del_tbl.find( receiver.value ); + del_bandwidth_table del_tbl( _self, from); + auto itr = del_tbl.find( receiver ); if( itr == del_tbl.end() ) { itr = del_tbl.emplace( from, [&]( auto& dbo ){ dbo.from = from; @@ -261,22 +235,22 @@ namespace eosiosystem { }); } else { - del_tbl.modify( itr, same_payer, [&]( auto& dbo ){ + del_tbl.modify( itr, 0, [&]( auto& dbo ){ dbo.net_weight += stake_net_delta; dbo.cpu_weight += stake_cpu_delta; }); } - eosio_assert( 0 <= itr->net_weight.amount, "insufficient staked net bandwidth" ); - eosio_assert( 0 <= itr->cpu_weight.amount, "insufficient staked cpu bandwidth" ); - if ( itr->net_weight.amount == 0 && itr->cpu_weight.amount == 0 ) { + eosio_assert( asset(0) <= itr->net_weight, "insufficient staked net bandwidth" ); + eosio_assert( asset(0) <= itr->cpu_weight, "insufficient staked cpu bandwidth" ); + if ( itr->net_weight == asset(0) && itr->cpu_weight == asset(0) ) { del_tbl.erase( itr ); } } // itr can be invalid, should go out of scope // update totals of "receiver" { - user_resources_table totals_tbl( _self, receiver.value ); - auto tot_itr = totals_tbl.find( receiver.value ); + user_resources_table totals_tbl( _self, receiver ); + auto tot_itr = totals_tbl.find( receiver ); if( tot_itr == totals_tbl.end() ) { tot_itr = totals_tbl.emplace( from, [&]( auto& tot ) { tot.owner = receiver; @@ -284,46 +258,25 @@ namespace eosiosystem { tot.cpu_weight = stake_cpu_delta; }); } else { - totals_tbl.modify( tot_itr, from == receiver ? from : same_payer, [&]( auto& tot ) { + totals_tbl.modify( tot_itr, from == receiver ? from : 0, [&]( auto& tot ) { tot.net_weight += stake_net_delta; tot.cpu_weight += stake_cpu_delta; }); } - eosio_assert( 0 <= tot_itr->net_weight.amount, "insufficient staked total net bandwidth" ); - eosio_assert( 0 <= tot_itr->cpu_weight.amount, "insufficient staked total cpu bandwidth" ); - - { - bool ram_managed = false; - bool net_managed = false; - bool cpu_managed = false; - - auto voter_itr = _voters.find( receiver.value ); - if( voter_itr != _voters.end() ) { - ram_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::ram_managed ); - net_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::net_managed ); - cpu_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::cpu_managed ); - } - - if( !(net_managed && cpu_managed) ) { - int64_t ram_bytes, net, cpu; - get_resource_limits( receiver.value, &ram_bytes, &net, &cpu ); - - set_resource_limits( receiver.value, - ram_managed ? ram_bytes : std::max( tot_itr->ram_bytes + ram_gift_bytes, ram_bytes ), - net_managed ? net : tot_itr->net_weight.amount, - cpu_managed ? cpu : tot_itr->cpu_weight.amount ); - } - } + eosio_assert( asset(0) <= tot_itr->net_weight, "insufficient staked total net bandwidth" ); + eosio_assert( asset(0) <= tot_itr->cpu_weight, "insufficient staked total cpu bandwidth" ); + + set_resource_limits( receiver, tot_itr->ram_bytes, tot_itr->net_weight.amount, tot_itr->cpu_weight.amount ); - if ( tot_itr->net_weight.amount == 0 && tot_itr->cpu_weight.amount == 0 && tot_itr->ram_bytes == 0 ) { + if ( tot_itr->net_weight == asset(0) && tot_itr->cpu_weight == asset(0) && tot_itr->ram_bytes == 0 ) { totals_tbl.erase( tot_itr ); } } // tot_itr can be invalid, should go out of scope // create refund or update from existing refund - if ( stake_account != source_stake_from ) { //for eosio both transfer and refund make no sense - refunds_table refunds_tbl( _self, from.value ); - auto req = refunds_tbl.find( from.value ); + if ( N(eosio.stake) != source_stake_from ) { //for eosio both transfer and refund make no sense + refunds_table refunds_tbl( _self, from ); + auto req = refunds_tbl.find( from ); //create/update/delete refund auto net_balance = stake_net_delta; @@ -338,51 +291,48 @@ namespace eosiosystem { if( is_delegating_to_self || is_undelegating ) { if ( req != refunds_tbl.end() ) { //need to update refund - refunds_tbl.modify( req, same_payer, [&]( refund_request& r ) { - if ( net_balance.amount < 0 || cpu_balance.amount < 0 ) { - r.request_time = current_time_point(); + refunds_tbl.modify( req, 0, [&]( refund_request& r ) { + if ( net_balance < asset(0) || cpu_balance < asset(0) ) { + r.request_time = now(); } r.net_amount -= net_balance; - if ( r.net_amount.amount < 0 ) { + if ( r.net_amount < asset(0) ) { net_balance = -r.net_amount; - r.net_amount.amount = 0; + r.net_amount = asset(0); } else { - net_balance.amount = 0; + net_balance = asset(0); } r.cpu_amount -= cpu_balance; - if ( r.cpu_amount.amount < 0 ){ + if ( r.cpu_amount < asset(0) ){ cpu_balance = -r.cpu_amount; - r.cpu_amount.amount = 0; + r.cpu_amount = asset(0); } else { - cpu_balance.amount = 0; + cpu_balance = asset(0); } }); - eosio_assert( 0 <= req->net_amount.amount, "negative net refund amount" ); //should never happen - eosio_assert( 0 <= req->cpu_amount.amount, "negative cpu refund amount" ); //should never happen + eosio_assert( asset(0) <= req->net_amount, "negative net refund amount" ); //should never happen + eosio_assert( asset(0) <= req->cpu_amount, "negative cpu refund amount" ); //should never happen - if ( req->net_amount.amount == 0 && req->cpu_amount.amount == 0 ) { + if ( req->net_amount == asset(0) && req->cpu_amount == asset(0) ) { refunds_tbl.erase( req ); need_deferred_trx = false; } else { need_deferred_trx = true; } - } else if ( net_balance.amount < 0 || cpu_balance.amount < 0 ) { //need to create refund + + } else if ( net_balance < asset(0) || cpu_balance < asset(0) ) { //need to create refund refunds_tbl.emplace( from, [&]( refund_request& r ) { r.owner = from; - if ( net_balance.amount < 0 ) { + if ( net_balance < asset(0) ) { r.net_amount = -net_balance; - net_balance.amount = 0; - } else { - r.net_amount = asset( 0, core_symbol() ); - } - if ( cpu_balance.amount < 0 ) { + net_balance = asset(0); + } // else r.net_amount = 0 by default constructor + if ( cpu_balance < asset(0) ) { r.cpu_amount = -cpu_balance; - cpu_balance.amount = 0; - } else { - r.cpu_amount = asset( 0, core_symbol() ); - } - r.request_time = current_time_point(); + cpu_balance = asset(0); + } // else r.cpu_amount = 0 by default constructor + r.request_time = now(); }); need_deferred_trx = true; } // else stake increase requested with no existing row in refunds_tbl -> nothing to do with refunds_tbl @@ -390,44 +340,38 @@ namespace eosiosystem { if ( need_deferred_trx ) { eosio::transaction out; - out.actions.emplace_back( permission_level{from, active_permission}, - _self, "refund"_n, - from - ); - out.delay_sec = refund_delay_sec; - cancel_deferred( from.value ); // TODO: Remove this line when replacing deferred trxs is fixed - out.send( from.value, from, true ); + out.actions.emplace_back( permission_level{ from, N(active) }, _self, N(refund), from ); + out.delay_sec = refund_delay; + cancel_deferred( from ); // TODO: Remove this line when replacing deferred trxs is fixed + out.send( from, from, true ); } else { - cancel_deferred( from.value ); + cancel_deferred( from ); } auto transfer_amount = net_balance + cpu_balance; - if ( 0 < transfer_amount.amount ) { - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {source_stake_from, active_permission} }, - { source_stake_from, stake_account, asset(transfer_amount), std::string("stake bandwidth") } - ); + if ( asset(0) < transfer_amount ) { + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {source_stake_from, N(active)}, + { source_stake_from, N(eosio.stake), asset(transfer_amount), std::string("stake bandwidth") } ); } } // update voting power { asset total_update = stake_net_delta + stake_cpu_delta; - auto from_voter = _voters.find( from.value ); + auto from_voter = _voters.find(from); if( from_voter == _voters.end() ) { from_voter = _voters.emplace( from, [&]( auto& v ) { v.owner = from; v.staked = total_update.amount; }); } else { - _voters.modify( from_voter, same_payer, [&]( auto& v ) { + _voters.modify( from_voter, 0, [&]( auto& v ) { v.staked += total_update.amount; }); } eosio_assert( 0 <= from_voter->staked, "stake for voting cannot be negative"); - - if( from == "bos"_n ) { - validate_bos_vesting( from_voter->staked ); + if( from == N(b1) ) { + validate_b1_vesting( from_voter->staked ); } if( from_voter->producers.size() || from_voter->proxy ) { @@ -436,49 +380,44 @@ namespace eosiosystem { } } - void system_contract::delegatebw( name from, name receiver, + void system_contract::delegatebw( account_name from, account_name receiver, asset stake_net_quantity, asset stake_cpu_quantity, bool transfer ) { - asset zero_asset( 0, core_symbol() ); - eosio_assert( stake_cpu_quantity >= zero_asset, "must stake a positive amount" ); - eosio_assert( stake_net_quantity >= zero_asset, "must stake a positive amount" ); - eosio_assert( stake_net_quantity.amount + stake_cpu_quantity.amount > 0, "must stake a positive amount" ); + eosio_assert( stake_cpu_quantity >= asset(0), "must stake a positive amount" ); + eosio_assert( stake_net_quantity >= asset(0), "must stake a positive amount" ); + eosio_assert( stake_net_quantity + stake_cpu_quantity > asset(0), "must stake a positive amount" ); eosio_assert( !transfer || from != receiver, "cannot use transfer flag if delegating to self" ); changebw( from, receiver, stake_net_quantity, stake_cpu_quantity, transfer); } // delegatebw - void system_contract::undelegatebw( name from, name receiver, + void system_contract::undelegatebw( account_name from, account_name receiver, asset unstake_net_quantity, asset unstake_cpu_quantity ) { - asset zero_asset( 0, core_symbol() ); - eosio_assert( unstake_cpu_quantity >= zero_asset, "must unstake a positive amount" ); - eosio_assert( unstake_net_quantity >= zero_asset, "must unstake a positive amount" ); - eosio_assert( unstake_cpu_quantity.amount + unstake_net_quantity.amount > 0, "must unstake a positive amount" ); - // eosio_assert( _gstate.total_activated_stake >= min_activated_stake, - // "cannot undelegate bandwidth until the chain is activated (at least 15% of all tokens participate in voting)" ); - eosio_assert( _gstate.thresh_activated_stake_time != time_point(), - "cannot undelegate bandwidth until the chain is activated " ); - + eosio_assert( asset() <= unstake_cpu_quantity, "must unstake a positive amount" ); + eosio_assert( asset() <= unstake_net_quantity, "must unstake a positive amount" ); + eosio_assert( asset() < unstake_cpu_quantity + unstake_net_quantity, "must unstake a positive amount" ); + eosio_assert( _gstate.total_activated_stake >= min_activated_stake, + "cannot undelegate bandwidth until the chain is activated (at least 15% of all tokens participate in voting)" ); changebw( from, receiver, -unstake_net_quantity, -unstake_cpu_quantity, false); } // undelegatebw - - void system_contract::refund( const name owner ) { + + void system_contract::refund( const account_name owner ) { require_auth( owner ); - refunds_table refunds_tbl( _self, owner.value ); - auto req = refunds_tbl.find( owner.value ); + refunds_table refunds_tbl( _self, owner ); + auto req = refunds_tbl.find( owner ); eosio_assert( req != refunds_tbl.end(), "refund request not found" ); - eosio_assert( req->request_time + seconds(refund_delay_sec) <= current_time_point(), - "refund is not available yet" ); + eosio_assert( req->request_time + refund_delay <= now(), "refund is not available yet" ); + // Until now() becomes NOW, the fact that now() is the timestamp of the previous block could in theory + // allow people to get their tokens earlier than the 3 day delay if the unstake happened immediately after many + // consecutive missed blocks. - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {stake_account, active_permission}, {req->owner, active_permission} }, - { stake_account, req->owner, req->net_amount + req->cpu_amount, std::string("unstake") } - ); + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.stake),N(active)}, + { N(eosio.stake), req->owner, req->net_amount + req->cpu_amount, std::string("unstake") } ); refunds_tbl.erase( req ); } diff --git a/contracts/eosio.system/eosio.system.abi b/contracts/eosio.system/eosio.system.abi index 6da34ae4796..f2f9f394cf9 100644 --- a/contracts/eosio.system/eosio.system.abi +++ b/contracts/eosio.system/eosio.system.abi @@ -1,1482 +1,578 @@ { - "____comment": "This file was generated with eosio-abigen. DO NOT EDIT Thu Apr 4 16:08:35 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": "bid_refund", - "base": "", - "fields": [ - { - "name": "bidder", - "type": "name" - }, - { - "name": "amount", - "type": "asset" - } - ] - }, - { - "name": "bidname", - "base": "", - "fields": [ - { - "name": "bidder", - "type": "name" - }, - { - "name": "newname", - "type": "name" - }, - { - "name": "bid", - "type": "asset" - } - ] - }, - { - "name": "bidrefund", - "base": "", - "fields": [ - { - "name": "bidder", - "type": "name" - }, - { - "name": "newname", - "type": "name" - } - ] - }, - { - "name": "block_header", - "base": "", - "fields": [ - { - "name": "timestamp", - "type": "uint32" - }, - { - "name": "producer", - "type": "name" - }, - { - "name": "confirmed", - "type": "uint16" - }, - { - "name": "previous", - "type": "checksum256" - }, - { - "name": "transaction_mroot", - "type": "checksum256" - }, - { - "name": "action_mroot", - "type": "checksum256" - }, - { - "name": "schedule_version", - "type": "uint32" - }, - { - "name": "new_producers", - "type": "producer_schedule?" - } - ] - }, - { - "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": "buyram", - "base": "", - "fields": [ - { - "name": "payer", - "type": "name" - }, - { - "name": "receiver", - "type": "name" - }, - { - "name": "quant", - "type": "asset" - } - ] - }, - { - "name": "buyrambytes", - "base": "", - "fields": [ - { - "name": "payer", - "type": "name" - }, - { - "name": "receiver", - "type": "name" - }, - { - "name": "bytes", - "type": "uint32" - } - ] - }, - { - "name": "canceldelay", - "base": "", - "fields": [ - { - "name": "canceling_auth", - "type": "permission_level" - }, - { - "name": "trx_id", - "type": "checksum256" - } - ] - }, - { - "name": "claimrewards", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - } - ] - }, - { - "name": "connector", - "base": "", - "fields": [ - { - "name": "balance", - "type": "asset" - }, - { - "name": "weight", - "type": "float64" - } - ] - }, - { - "name": "delegatebw", - "base": "", - "fields": [ - { - "name": "from", - "type": "name" - }, - { - "name": "receiver", - "type": "name" - }, - { - "name": "stake_net_quantity", - "type": "asset" - }, - { - "name": "stake_cpu_quantity", - "type": "asset" - }, - { - "name": "transfer", - "type": "bool" - } - ] - }, - { - "name": "delegated_bandwidth", - "base": "", - "fields": [ - { - "name": "from", - "type": "name" - }, - { - "name": "to", - "type": "name" - }, - { - "name": "net_weight", - "type": "asset" - }, - { - "name": "cpu_weight", - "type": "asset" - } - ] - }, - { - "name": "deleteauth", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "permission", - "type": "name" - } - ] - }, - { - "name": "eosio_global_state", - "base": "blockchain_parameters", - "fields": [ - { - "name": "max_ram_size", - "type": "uint64" - }, - { - "name": "total_ram_bytes_reserved", - "type": "uint64" - }, - { - "name": "total_ram_stake", - "type": "int64" - }, - { - "name": "last_producer_schedule_update", - "type": "block_timestamp_type" - }, - { - "name": "last_pervote_bucket_fill", - "type": "time_point" - }, - { - "name": "pervote_bucket", - "type": "int64" - }, - { - "name": "perblock_bucket", - "type": "int64" - }, - { - "name": "total_unpaid_blocks", - "type": "uint32" - }, - { - "name": "total_activated_stake", - "type": "int64" - }, - { - "name": "thresh_activated_stake_time", - "type": "time_point" - }, - { - "name": "last_producer_schedule_size", - "type": "uint16" - }, - { - "name": "total_producer_vote_weight", - "type": "float64" - }, - { - "name": "last_name_close", - "type": "block_timestamp_type" - } - ] - }, - { - "name": "eosio_global_state2", - "base": "", - "fields": [ - { - "name": "new_ram_per_block", - "type": "uint16" - }, - { - "name": "last_ram_increase", - "type": "block_timestamp_type" - }, - { - "name": "last_block_num", - "type": "block_timestamp_type" - }, - { - "name": "total_producer_votepay_share", - "type": "float64" - }, - { - "name": "revision", - "type": "uint8" - } - ] - }, - { - "name": "eosio_global_state3", - "base": "", - "fields": [ - { - "name": "last_vpay_state_update", - "type": "time_point" - }, - { - "name": "total_vpay_share_change_rate", - "type": "float64" - } - ] - }, - { - "name": "eosio_guaranteed_min_res", - "base": "", - "fields": [ - { - "name": "ram", - "type": "uint32" - }, - { - "name": "cpu", - "type": "uint32" - }, - { - "name": "net", - "type": "uint32" - } - ] - }, - { - "name": "exchange_state", - "base": "", - "fields": [ - { - "name": "supply", - "type": "asset" - }, - { - "name": "base", - "type": "connector" - }, - { - "name": "quote", - "type": "connector" - } - ] - }, - { - "name": "init", - "base": "", - "fields": [ - { - "name": "version", - "type": "varuint32" - }, - { - "name": "core", - "type": "symbol" - } - ] - }, - { - "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": "name_bid", - "base": "", - "fields": [ - { - "name": "newname", - "type": "name" - }, - { - "name": "high_bidder", - "type": "name" - }, - { - "name": "high_bid", - "type": "int64" - }, - { - "name": "last_bid_time", - "type": "time_point" - } - ] - }, - { - "name": "namelist", - "base": "", - "fields": [ - { - "name": "list", - "type": "string" - }, - { - "name": "action", - "type": "string" - }, - { - "name": "names", - "type": "name[]" - } - ] - }, - { - "name": "newaccount", - "base": "", - "fields": [ - { - "name": "creator", - "type": "name" - }, - { - "name": "newact", - "type": "name" - }, - { - "name": "owner", - "type": "authority" - }, - { - "name": "active", - "type": "authority" - } - ] - }, - { - "name": "onblock", - "base": "", - "fields": [ - { - "name": "header", - "type": "block_header" - } - ] - }, - { - "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_info", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - }, - { - "name": "total_votes", - "type": "float64" - }, - { - "name": "producer_key", - "type": "public_key" - }, - { - "name": "is_active", - "type": "bool" - }, - { - "name": "url", - "type": "string" - }, - { - "name": "unpaid_blocks", - "type": "uint32" - }, - { - "name": "last_claim_time", - "type": "time_point" - }, - { - "name": "location", - "type": "uint16" - } - ] - }, - { - "name": "producer_info2", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - }, - { - "name": "votepay_share", - "type": "float64" - }, - { - "name": "last_votepay_share_update", - "type": "time_point" - } - ] - }, - { - "name": "producer_key", - "base": "", - "fields": [ - { - "name": "producer_name", - "type": "name" - }, - { - "name": "block_signing_key", - "type": "public_key" - } - ] - }, - { - "name": "producer_schedule", - "base": "", - "fields": [ - { - "name": "version", - "type": "uint32" - }, - { - "name": "producers", - "type": "producer_key[]" - } - ] - }, - { - "name": "refund", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - } - ] - }, - { - "name": "refund_request", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - }, - { - "name": "request_time", - "type": "time_point_sec" - }, - { - "name": "net_amount", - "type": "asset" - }, - { - "name": "cpu_amount", - "type": "asset" - } - ] - }, - { - "name": "regproducer", - "base": "", - "fields": [ - { - "name": "producer", - "type": "name" - }, - { - "name": "producer_key", - "type": "public_key" - }, - { - "name": "url", - "type": "string" - }, - { - "name": "location", - "type": "uint16" - } - ] - }, - { - "name": "regproxy", - "base": "", - "fields": [ - { - "name": "proxy", - "type": "name" - }, - { - "name": "isproxy", - "type": "bool" - } - ] - }, - { - "name": "rmvproducer", - "base": "", - "fields": [ - { - "name": "producer", - "type": "name" - } - ] - }, - { - "name": "sellram", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "bytes", - "type": "int64" - } - ] - }, - { - "name": "setabi", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "abi", - "type": "bytes" - } - ] - }, - { - "name": "setacctcpu", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "cpu_weight", - "type": "int64?" - } - ] - }, - { - "name": "setacctnet", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "net_weight", - "type": "int64?" - } - ] - }, - { - "name": "setacctram", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "ram_bytes", - "type": "int64?" - } - ] - }, - { - "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": "setguaminres", - "base": "", - "fields": [ - { - "name": "ram", - "type": "uint32" - }, - { - "name": "cpu", - "type": "uint32" - }, - { - "name": "net", - "type": "uint32" - } - ] - }, - { - "name": "setparams", - "base": "", - "fields": [ - { - "name": "params", - "type": "blockchain_parameters" - } - ] - }, - { - "name": "setpriv", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "is_priv", - "type": "uint8" - } - ] - }, - { - "name": "setram", - "base": "", - "fields": [ - { - "name": "max_ram_size", - "type": "uint64" - } - ] - }, - { - "name": "setramrate", - "base": "", - "fields": [ - { - "name": "bytes_per_block", - "type": "uint16" - } - ] - }, - { - "name": "setupgrade", - "base": "", - "fields": [ - { - "name": "params", - "type": "upgrade_parameters" - } - ] - }, - { - "name": "undelegatebw", - "base": "", - "fields": [ - { - "name": "from", - "type": "name" - }, - { - "name": "receiver", - "type": "name" - }, - { - "name": "unstake_net_quantity", - "type": "asset" - }, - { - "name": "unstake_cpu_quantity", - "type": "asset" - } - ] - }, - { - "name": "unlinkauth", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "code", - "type": "name" - }, - { - "name": "type", - "type": "name" - } - ] - }, - { - "name": "unregprod", - "base": "", - "fields": [ - { - "name": "producer", - "type": "name" - } - ] - }, - { - "name": "updateauth", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "permission", - "type": "name" - }, - { - "name": "parent", - "type": "name" - }, - { - "name": "auth", - "type": "authority" - } - ] - }, - { - "name": "updtrevision", - "base": "", - "fields": [ - { - "name": "revision", - "type": "uint8" - } - ] - }, - { - "name": "upgrade_parameters", - "base": "", - "fields": [ - { - "name": "target_block_num", - "type": "uint32" - } - ] - }, - { - "name": "upgrade_state", - "base": "upgrade_parameters", - "fields": [ - { - "name": "current_version", - "type": "uint16" - } - ] - }, - { - "name": "user_resources", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - }, - { - "name": "net_weight", - "type": "asset" - }, - { - "name": "cpu_weight", - "type": "asset" - }, - { - "name": "ram_bytes", - "type": "int64" - } - ] - }, - { - "name": "voteproducer", - "base": "", - "fields": [ - { - "name": "voter", - "type": "name" - }, - { - "name": "proxy", - "type": "name" - }, - { - "name": "producers", - "type": "name[]" - } - ] - }, - { - "name": "voter_info", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - }, - { - "name": "proxy", - "type": "name" - }, - { - "name": "producers", - "type": "name[]" - }, - { - "name": "staked", - "type": "int64" - }, - { - "name": "last_vote_weight", - "type": "float64" - }, - { - "name": "proxied_vote_weight", - "type": "float64" - }, - { - "name": "is_proxy", - "type": "bool" - }, - { - "name": "flags1", - "type": "uint32" - }, - { - "name": "reserved2", - "type": "uint32" - }, - { - "name": "reserved3", - "type": "asset" - } - ] - }, - { - "name": "wait_weight", - "base": "", - "fields": [ - { - "name": "wait_sec", - "type": "uint32" - }, - { - "name": "weight", - "type": "uint16" - } - ] - } - ], - "types": [], - "actions": [ - { - "name": "bidname", - "type": "bidname", - "ricardian_contract": "" - }, - { - "name": "bidrefund", - "type": "bidrefund", - "ricardian_contract": "" - }, - { - "name": "buyram", - "type": "buyram", - "ricardian_contract": "" - }, - { - "name": "buyrambytes", - "type": "buyrambytes", - "ricardian_contract": "" - }, - { - "name": "canceldelay", - "type": "canceldelay", - "ricardian_contract": "" - }, - { - "name": "claimrewards", - "type": "claimrewards", - "ricardian_contract": "" - }, - { - "name": "delegatebw", - "type": "delegatebw", - "ricardian_contract": "" - }, - { - "name": "deleteauth", - "type": "deleteauth", - "ricardian_contract": "" - }, - { - "name": "init", - "type": "init", - "ricardian_contract": "" - }, - { - "name": "linkauth", - "type": "linkauth", - "ricardian_contract": "" - }, - { - "name": "namelist", - "type": "namelist", - "ricardian_contract": "" - }, - { - "name": "newaccount", - "type": "newaccount", - "ricardian_contract": "" - }, - { - "name": "onblock", - "type": "onblock", - "ricardian_contract": "" - }, - { - "name": "onerror", - "type": "onerror", - "ricardian_contract": "" - }, - { - "name": "refund", - "type": "refund", - "ricardian_contract": "" - }, - { - "name": "regproducer", - "type": "regproducer", - "ricardian_contract": "" - }, - { - "name": "regproxy", - "type": "regproxy", - "ricardian_contract": "" - }, - { - "name": "rmvproducer", - "type": "rmvproducer", - "ricardian_contract": "" - }, - { - "name": "sellram", - "type": "sellram", - "ricardian_contract": "" - }, - { - "name": "setabi", - "type": "setabi", - "ricardian_contract": "" - }, - { - "name": "setacctcpu", - "type": "setacctcpu", - "ricardian_contract": "" - }, - { - "name": "setacctnet", - "type": "setacctnet", - "ricardian_contract": "" - }, - { - "name": "setacctram", - "type": "setacctram", - "ricardian_contract": "" - }, - { - "name": "setalimits", - "type": "setalimits", - "ricardian_contract": "" - }, - { - "name": "setcode", - "type": "setcode", - "ricardian_contract": "" - }, - { - "name": "setguaminres", - "type": "setguaminres", - "ricardian_contract": "" - }, - { - "name": "setparams", - "type": "setparams", - "ricardian_contract": "" - }, - { - "name": "setpriv", - "type": "setpriv", - "ricardian_contract": "" - }, - { - "name": "setram", - "type": "setram", - "ricardian_contract": "" - }, - { - "name": "setramrate", - "type": "setramrate", - "ricardian_contract": "" - }, - { - "name": "setupgrade", - "type": "setupgrade", - "ricardian_contract": "" - }, - { - "name": "undelegatebw", - "type": "undelegatebw", - "ricardian_contract": "" - }, - { - "name": "unlinkauth", - "type": "unlinkauth", - "ricardian_contract": "" - }, - { - "name": "unregprod", - "type": "unregprod", - "ricardian_contract": "" - }, - { - "name": "updateauth", - "type": "updateauth", - "ricardian_contract": "" - }, - { - "name": "updtrevision", - "type": "updtrevision", - "ricardian_contract": "" - }, - { - "name": "voteproducer", - "type": "voteproducer", - "ricardian_contract": "" - } - ], - "tables": [ - { - "name": "abihash", - "type": "abi_hash", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "bidrefunds", - "type": "bid_refund", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "delband", - "type": "delegated_bandwidth", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "global", - "type": "eosio_global_state", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "global2", - "type": "eosio_global_state2", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "global3", - "type": "eosio_global_state3", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "guaranminres", - "type": "eosio_guaranteed_min_res", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "namebids", - "type": "name_bid", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "producers", - "type": "producer_info", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "producers2", - "type": "producer_info2", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "rammarket", - "type": "exchange_state", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "refunds", - "type": "refund_request", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "upgrade", - "type": "upgrade_state", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "userres", - "type": "user_resources", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "voters", - "type": "voter_info", - "index_type": "i64", - "key_names": [], - "key_types": [] - } - ], - "ricardian_clauses": [], - "variants": [], - "abi_extensions": [] + "version": "eosio::abi/1.0", + "types": [{ + "new_type_name": "account_name", + "type": "name" + },{ + "new_type_name": "permission_name", + "type": "name" + },{ + "new_type_name": "action_name", + "type": "name" + },{ + "new_type_name": "transaction_id_type", + "type": "checksum256" + },{ + "new_type_name": "weight_type", + "type": "uint16" + }], + "____comment": "eosio.bios structs: set_account_limits, setpriv, set_global_limits, producer_key, set_producers, require_auth are provided so abi available for deserialization in future.", + "structs": [{ + "name": "permission_level", + "base": "", + "fields": [ + {"name":"actor", "type":"account_name"}, + {"name":"permission", "type":"permission_name"} + ] + },{ + "name": "key_weight", + "base": "", + "fields": [ + {"name":"key", "type":"public_key"}, + {"name":"weight", "type":"weight_type"} + ] + },{ + "name": "bidname", + "base": "", + "fields": [ + {"name":"bidder", "type":"account_name"}, + {"name":"newname", "type":"account_name"}, + {"name":"bid", "type":"asset"} + ] + },{ + "name": "permission_level_weight", + "base": "", + "fields": [ + {"name":"permission", "type":"permission_level"}, + {"name":"weight", "type":"weight_type"} + ] + },{ + "name": "wait_weight", + "base": "", + "fields": [ + {"name":"wait_sec", "type":"uint32"}, + {"name":"weight", "type":"weight_type"} + ] + },{ + "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": "newaccount", + "base": "", + "fields": [ + {"name":"creator", "type":"account_name"}, + {"name":"name", "type":"account_name"}, + {"name":"owner", "type":"authority"}, + {"name":"active", "type":"authority"} + ] + },{ + "name": "setcode", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"vmtype", "type":"uint8"}, + {"name":"vmversion", "type":"uint8"}, + {"name":"code", "type":"bytes"} + ] + },{ + "name": "setabi", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"abi", "type":"bytes"} + ] + },{ + "name": "updateauth", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"permission", "type":"permission_name"}, + {"name":"parent", "type":"permission_name"}, + {"name":"auth", "type":"authority"} + ] + },{ + "name": "deleteauth", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"permission", "type":"permission_name"} + ] + },{ + "name": "linkauth", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"code", "type":"account_name"}, + {"name":"type", "type":"action_name"}, + {"name":"requirement", "type":"permission_name"} + ] + },{ + "name": "unlinkauth", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"code", "type":"account_name"}, + {"name":"type", "type":"action_name"} + ] + },{ + "name": "canceldelay", + "base": "", + "fields": [ + {"name":"canceling_auth", "type":"permission_level"}, + {"name":"trx_id", "type":"transaction_id_type"} + ] + },{ + "name": "onerror", + "base": "", + "fields": [ + {"name":"sender_id", "type":"uint128"}, + {"name":"sent_trx", "type":"bytes"} + ] + },{ + "name": "buyrambytes", + "base": "", + "fields": [ + {"name":"payer", "type":"account_name"}, + {"name":"receiver", "type":"account_name"}, + {"name":"bytes", "type":"uint32"} + ] + },{ + "name": "sellram", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"bytes", "type":"uint64"} + ] + },{ + "name": "buyram", + "base": "", + "fields": [ + {"name":"payer", "type":"account_name"}, + {"name":"receiver", "type":"account_name"}, + {"name":"quant", "type":"asset"} + ] + },{ + "name": "delegatebw", + "base": "", + "fields": [ + {"name":"from", "type":"account_name"}, + {"name":"receiver", "type":"account_name"}, + {"name":"stake_net_quantity", "type":"asset"}, + {"name":"stake_cpu_quantity", "type":"asset"}, + {"name":"transfer", "type":"bool"} + ] + },{ + "name": "undelegatebw", + "base": "", + "fields": [ + {"name":"from", "type":"account_name"}, + {"name":"receiver", "type":"account_name"}, + {"name":"unstake_net_quantity", "type":"asset"}, + {"name":"unstake_cpu_quantity", "type":"asset"} + ] + },{ + "name": "refund", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"} + ] + },{ + "name": "delegated_bandwidth", + "base": "", + "fields": [ + {"name":"from", "type":"account_name"}, + {"name":"to", "type":"account_name"}, + {"name":"net_weight", "type":"asset"}, + {"name":"cpu_weight", "type":"asset"} + ] + },{ + "name": "user_resources", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"net_weight", "type":"asset"}, + {"name":"cpu_weight", "type":"asset"}, + {"name":"ram_bytes", "type":"uint64"} + ] + },{ + "name": "total_resources", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"net_weight", "type":"asset"}, + {"name":"cpu_weight", "type":"asset"}, + {"name":"ram_bytes", "type":"uint64"} + ] + },{ + "name": "refund_request", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"request_time", "type":"time_point_sec"}, + {"name":"net_amount", "type":"asset"}, + {"name":"cpu_amount", "type":"asset"} + ] + },{ + "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": "eosio_global_state", + "base": "blockchain_parameters", + "fields": [ + {"name":"max_ram_size", "type":"uint64"}, + {"name":"total_ram_bytes_reserved", "type":"uint64"}, + {"name":"total_ram_stake", "type":"int64"}, + {"name":"last_producer_schedule_update", "type":"block_timestamp_type"}, + {"name":"last_pervote_bucket_fill", "type":"uint64"}, + {"name":"pervote_bucket", "type":"int64"}, + {"name":"perblock_bucket", "type":"int64"}, + {"name":"total_unpaid_blocks", "type":"uint32"}, + {"name":"total_activated_stake", "type":"int64"}, + {"name":"thresh_activated_stake_time", "type":"uint64"}, + {"name":"last_producer_schedule_size", "type":"uint16"}, + {"name":"total_producer_vote_weight", "type":"float64"}, + {"name":"last_name_close", "type":"block_timestamp_type"} + ] + },{ + "name": "producer_info", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"total_votes", "type":"float64"}, + {"name":"producer_key", "type":"public_key"}, + {"name":"is_active", "type":"bool"}, + {"name":"url", "type":"string"}, + {"name":"unpaid_blocks", "type":"uint32"}, + {"name":"last_claim_time", "type":"uint64"}, + {"name":"location", "type":"uint16"} + ] + },{ + "name": "regproducer", + "base": "", + "fields": [ + {"name":"producer", "type":"account_name"}, + {"name":"producer_key", "type":"public_key"}, + {"name":"url", "type":"string"}, + {"name":"location", "type":"uint16"} + ] + },{ + "name": "unregprod", + "base": "", + "fields": [ + {"name":"producer", "type":"account_name"} + ] + },{ + "name": "setram", + "base": "", + "fields": [ + {"name":"max_ram_size", "type":"uint64"} + ] + },{ + "name": "regproxy", + "base": "", + "fields": [ + {"name":"proxy", "type":"account_name"}, + {"name":"isproxy", "type":"bool"} + ] + },{ + "name": "voteproducer", + "base": "", + "fields": [ + {"name":"voter", "type":"account_name"}, + {"name":"proxy", "type":"account_name"}, + {"name":"producers", "type":"account_name[]"} + ] + },{ + "name": "voter_info", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"proxy", "type":"account_name"}, + {"name":"producers", "type":"account_name[]"}, + {"name":"staked", "type":"int64"}, + {"name":"last_vote_weight", "type":"float64"}, + {"name":"proxied_vote_weight", "type":"float64"}, + {"name":"is_proxy", "type":"bool"} + ] + },{ + "name": "claimrewards", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"} + ] + },{ + "name": "setpriv", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"is_priv", "type":"int8"} + ] + },{ + "name": "rmvproducer", + "base": "", + "fields": [ + {"name":"producer", "type":"account_name"} + ] + },{ + "name": "set_account_limits", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"ram_bytes", "type":"int64"}, + {"name":"net_weight", "type":"int64"}, + {"name":"cpu_weight", "type":"int64"} + ] + },{ + "name": "set_global_limits", + "base": "", + "fields": [ + {"name":"cpu_usec_per_period", "type":"int64"} + ] + },{ + "name": "producer_key", + "base": "", + "fields": [ + {"name":"producer_name", "type":"account_name"}, + {"name":"block_signing_key", "type":"public_key"} + ] + },{ + "name": "set_producers", + "base": "", + "fields": [ + {"name":"schedule", "type":"producer_key[]"} + ] + },{ + "name": "require_auth", + "base": "", + "fields": [ + {"name":"from", "type":"account_name"} + ] + },{ + "name": "setparams", + "base": "", + "fields": [ + {"name":"params", "type":"blockchain_parameters"} + ] + },{ + "name": "connector", + "base": "", + "fields": [ + {"name":"balance", "type":"asset"}, + {"name":"weight", "type":"float64"} + ] + },{ + "name": "exchange_state", + "base": "", + "fields": [ + {"name":"supply", "type":"asset"}, + {"name":"base", "type":"connector"}, + {"name":"quote", "type":"connector"} + ] + }, { + "name": "namebid_info", + "base": "", + "fields": [ + {"name":"newname", "type":"account_name"}, + {"name":"high_bidder", "type":"account_name"}, + {"name":"high_bid", "type":"int64"}, + {"name":"last_bid_time", "type":"uint64"} + ] + } + ], + "actions": [{ + "name": "newaccount", + "type": "newaccount", + "ricardian_contract": "" + },{ + "name": "setcode", + "type": "setcode", + "ricardian_contract": "" + },{ + "name": "setabi", + "type": "setabi", + "ricardian_contract": "" + },{ + "name": "updateauth", + "type": "updateauth", + "ricardian_contract": "" + },{ + "name": "deleteauth", + "type": "deleteauth", + "ricardian_contract": "" + },{ + "name": "linkauth", + "type": "linkauth", + "ricardian_contract": "" + },{ + "name": "unlinkauth", + "type": "unlinkauth", + "ricardian_contract": "" + },{ + "name": "canceldelay", + "type": "canceldelay", + "ricardian_contract": "" + },{ + "name": "onerror", + "type": "onerror", + "ricardian_contract": "" + },{ + "name": "buyrambytes", + "type": "buyrambytes", + "ricardian_contract": "" + },{ + "name": "buyram", + "type": "buyram", + "ricardian_contract": "" + },{ + "name": "sellram", + "type": "sellram", + "ricardian_contract": "" + },{ + "name": "delegatebw", + "type": "delegatebw", + "ricardian_contract": "" + },{ + "name": "undelegatebw", + "type": "undelegatebw", + "ricardian_contract": "" + },{ + "name": "refund", + "type": "refund", + "ricardian_contract": "" + },{ + "name": "regproducer", + "type": "regproducer", + "ricardian_contract": "" + },{ + "name": "setram", + "type": "setram", + "ricardian_contract": "" + },{ + "name": "bidname", + "type": "bidname", + "ricardian_contract": "" + },{ + "name": "unregprod", + "type": "unregprod", + "ricardian_contract": "" + },{ + "name": "regproxy", + "type": "regproxy", + "ricardian_contract": "" + },{ + "name": "voteproducer", + "type": "voteproducer", + "ricardian_contract": "" + },{ + "name": "claimrewards", + "type": "claimrewards", + "ricardian_contract": "" + },{ + "name": "setpriv", + "type": "setpriv", + "ricardian_contract": "" + },{ + "name": "rmvproducer", + "type": "rmvproducer", + "ricardian_contract": "" + },{ + "name": "setalimits", + "type": "set_account_limits", + "ricardian_contract": "" + },{ + "name": "setglimits", + "type": "set_global_limits", + "ricardian_contract": "" + },{ + "name": "setprods", + "type": "set_producers", + "ricardian_contract": "" + },{ + "name": "reqauth", + "type": "require_auth", + "ricardian_contract": "" + },{ + "name": "setparams", + "type": "setparams", + "ricardian_contract": "" + }], + "tables": [{ + "name": "producers", + "type": "producer_info", + "index_type": "i64", + "key_names" : ["owner"], + "key_types" : ["uint64"] + },{ + "name": "global", + "type": "eosio_global_state", + "index_type": "i64", + "key_names" : [], + "key_types" : [] + },{ + "name": "voters", + "type": "voter_info", + "index_type": "i64", + "key_names" : ["owner"], + "key_types" : ["account_name"] + },{ + "name": "userres", + "type": "user_resources", + "index_type": "i64", + "key_names" : ["owner"], + "key_types" : ["uint64"] + },{ + "name": "delband", + "type": "delegated_bandwidth", + "index_type": "i64", + "key_names" : ["to"], + "key_types" : ["uint64"] + },{ + "name": "rammarket", + "type": "exchange_state", + "index_type": "i64", + "key_names" : ["supply"], + "key_types" : ["uint64"] + },{ + "name": "refunds", + "type": "refund_request", + "index_type": "i64", + "key_names" : ["owner"], + "key_types" : ["uint64"] + },{ + "name": "namebids", + "type": "namebid_info", + "index_type": "i64", + "key_names" : ["newname"], + "key_types" : ["account_name"] + } + ], + "ricardian_clauses": [], + "abi_extensions": [] } \ No newline at end of file diff --git a/contracts/eosio.system/eosio.system.cpp b/contracts/eosio.system/eosio.system.cpp index 2f030a99e9f..daf40efbb84 100644 --- a/contracts/eosio.system/eosio.system.cpp +++ b/contracts/eosio.system/eosio.system.cpp @@ -1,35 +1,41 @@ -#include +#include "eosio.system.hpp" #include -#include #include "producer_pay.cpp" #include "delegate_bandwidth.cpp" #include "voting.cpp" #include "exchange_state.cpp" -#include "upgrade.cpp" -#include + namespace eosiosystem { - system_contract::system_contract( name s, name code, datastream ds ) - :native(s,code,ds), - _voters(_self, _self.value), - _producers(_self, _self.value), - _producers2(_self, _self.value), - _global(_self, _self.value), - _global2(_self, _self.value), - _global3(_self, _self.value), - _guarantee(_self, _self.value), - _rammarket(_self, _self.value), - _upgrade(_self, _self.value) + system_contract::system_contract( account_name s ) + :native(s), + _voters(_self,_self), + _producers(_self,_self), + _global(_self,_self), + _rammarket(_self,_self) { - //print( "construct system\n" ); - _gstate = _global.exists() ? _global.get() : get_default_parameters(); - _gstate2 = _global2.exists() ? _global2.get() : eosio_global_state2{}; - _gstate3 = _global3.exists() ? _global3.get() : eosio_global_state3{}; - - _ustate = _upgrade.exists() ? _upgrade.get() : upgrade_state{}; + _gstate = _global.exists() ? _global.get() : get_default_parameters(); + + auto itr = _rammarket.find(S(4,RAMCORE)); + + if( itr == _rammarket.end() ) { + auto system_token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount; + if( system_token_supply > 0 ) { + itr = _rammarket.emplace( _self, [&]( auto& m ) { + m.supply.amount = 100000000000000ll; + m.supply.symbol = S(4,RAMCORE); + m.base.balance.amount = int64_t(_gstate.free_ram()); + m.base.balance.symbol = S(0,RAM); + m.quote.balance.amount = system_token_supply / 1000; + m.quote.balance.symbol = CORE_SYMBOL; + }); + } + } else { + //print( "ram market already created" ); + } } eosio_global_state system_contract::get_default_parameters() { @@ -38,27 +44,11 @@ namespace eosiosystem { return dp; } - time_point system_contract::current_time_point() { - const static time_point ct{ microseconds{ static_cast( current_time() ) } }; - return ct; - } - - block_timestamp system_contract::current_block_time() { - const static block_timestamp cbt{ current_time_point() }; - return cbt; - } - - symbol system_contract::core_symbol()const { - const static auto sym = get_core_symbol( _rammarket ); - return sym; - } system_contract::~system_contract() { + //print( "destruct system\n" ); _global.set( _gstate, _self ); - _global2.set( _gstate2, _self ); - _global3.set( _gstate3, _self ); - - _upgrade.set( _ustate, _self ); + //eosio_exit(0); } void system_contract::setram( uint64_t max_ram_size ) { @@ -69,391 +59,81 @@ namespace eosiosystem { eosio_assert( max_ram_size > _gstate.total_ram_bytes_reserved, "attempt to set max below reserved" ); auto delta = int64_t(max_ram_size) - int64_t(_gstate.max_ram_size); - auto itr = _rammarket.find(ramcore_symbol.raw()); + auto itr = _rammarket.find(S(4,RAMCORE)); /** - * Increase the amount of ram for sale based upon the change in max ram size. + * Increase or decrease the amount of ram for sale based upon the change in max + * ram size. */ - _rammarket.modify( itr, same_payer, [&]( auto& m ) { + _rammarket.modify( itr, 0, [&]( auto& m ) { m.base.balance.amount += delta; }); _gstate.max_ram_size = max_ram_size; - } - - void system_contract::update_ram_supply() { - auto cbt = current_block_time(); - - if( cbt <= _gstate2.last_ram_increase ) return; - - auto itr = _rammarket.find(ramcore_symbol.raw()); - auto new_ram = (cbt.slot - _gstate2.last_ram_increase.slot)*_gstate2.new_ram_per_block; - _gstate.max_ram_size += new_ram; - - /** - * Increase the amount of ram for sale based upon the change in max ram size. - */ - _rammarket.modify( itr, same_payer, [&]( auto& m ) { - m.base.balance.amount += new_ram; - }); - _gstate2.last_ram_increase = cbt; - } - - /** - * Sets the rate of increase of RAM in bytes per block. It is capped by the uint16_t to - * a maximum rate of 3 TB per year. - * - * If update_ram_supply hasn't been called for the most recent block, then new ram will - * be allocated at the old rate up to the present block before switching the rate. - */ - void system_contract::setramrate( uint16_t bytes_per_block ) { - require_auth( _self ); - - update_ram_supply(); - _gstate2.new_ram_per_block = bytes_per_block; + _global.set( _gstate, _self ); } void system_contract::setparams( const eosio::blockchain_parameters& params ) { - require_auth( _self ); + require_auth( N(eosio) ); (eosio::blockchain_parameters&)(_gstate) = params; eosio_assert( 3 <= _gstate.max_authority_depth, "max_authority_depth should be at least 3" ); set_blockchain_parameters( params ); } - // *bos begin* - void system_contract::namelist(std::string list, std::string action, const std::vector &names) - { - const int MAX_LIST_LENGTH = 30; - const int MAX_ACTION_LENGTH = 10; - enum class list_type:int64_t - { - actor_blacklist_type = 1, - contract_blacklist_type, - resource_greylist_type, - list_type_count - }; - enum class list_action_type:int64_t - { - insert_type = 1, - remove_type, - list_action_type_count - }; - - std::map list_type_string_to_enum = { - {"actor_blacklist", list_type::actor_blacklist_type}, - {"contract_blacklist", list_type::contract_blacklist_type}, - {"resource_greylist", list_type::resource_greylist_type}}; - - std::map list_action_type_string_to_enum = { - {"insert", list_action_type::insert_type}, - {"remove", list_action_type::remove_type}}; - - std::map::iterator itlt = list_type_string_to_enum.find(list); - std::map::iterator itlat = list_action_type_string_to_enum.find(action); - - require_auth(_self); - eosio_assert(3 <= _gstate.max_authority_depth, "max_authority_depth should be at least 3"); - eosio_assert(list.length() < MAX_LIST_LENGTH, "list string is greater than max length 30"); - eosio_assert(action.length() < MAX_ACTION_LENGTH, " action string is greater than max length 10"); - eosio_assert(itlt != list_type_string_to_enum.end(), " unknown list type string support 'actor_blacklist' ,'contract_blacklist', 'resource_greylist'"); - eosio_assert(itlat != list_action_type_string_to_enum.end(), " unknown list type string support 'insert' or 'remove'"); - - auto packed_names = pack(names); - - set_name_list_packed(static_cast(itlt->second), static_cast(itlat->second), packed_names.data(), packed_names.size()); - } - - void system_contract::setguaminres(uint32_t ram, uint32_t cpu, uint32_t net) - { - require_auth(_self); - - const static uint32_t MAX_BYTE = 100*1024; - const static uint32_t MAX_MICROSEC = 100*1000; - - const static uint32_t STEP_BYTE = 10*1024; - const static uint32_t STEP_MICROSEC = 10*1000; - eosio_assert(3 <= _gstate.max_authority_depth, "max_authority_depth should be at least 3"); - eosio_assert(ram <= MAX_BYTE && net <= MAX_BYTE, "the value of ram, cpu and net should not more then 100 kb"); - eosio_assert(cpu <= MAX_MICROSEC , "the value of cpu should not more then 100 ms"); - - eosio_guaranteed_min_res _gmr = _guarantee.exists() ? _guarantee.get() : eosio_guaranteed_min_res{}; - eosio_assert(ram >= _gmr.ram, "can not reduce ram guarantee "); - eosio_assert(ram <= _gmr.ram + STEP_BYTE, "minimum ram guarantee can not increace more then 10kb every time"); - eosio_assert(cpu <= _gmr.cpu + STEP_MICROSEC, "minimum cpu guarantee can not increace more then 10ms token weight every time"); - eosio_assert(net <= _gmr.net + STEP_BYTE, "minimum net guarantee can not increace more then 10kb token weight every time"); - - _gmr.ram = ram; - _gmr.cpu = cpu; - _gmr.net = net; - - _guarantee.set(_gmr, _self); - set_guaranteed_minimum_resources(ram, cpu, net); - } - // *bos end* - - void system_contract::setpriv( name account, uint8_t ispriv ) { - require_auth( _self ); - set_privileged( account.value, ispriv ); - } - - void system_contract::setalimits( name account, int64_t ram, int64_t net, int64_t cpu ) { - require_auth( _self ); - - user_resources_table userres( _self, account.value ); - auto ritr = userres.find( account.value ); - eosio_assert( ritr == userres.end(), "only supports unlimited accounts" ); - - auto vitr = _voters.find( account.value ); - if( vitr != _voters.end() ) { - bool ram_managed = has_field( vitr->flags1, voter_info::flags1_fields::ram_managed ); - bool net_managed = has_field( vitr->flags1, voter_info::flags1_fields::net_managed ); - bool cpu_managed = has_field( vitr->flags1, voter_info::flags1_fields::cpu_managed ); - eosio_assert( !(ram_managed || net_managed || cpu_managed), "cannot use setalimits on an account with managed resources" ); - } - - set_resource_limits( account.value, ram, net, cpu ); - } - - void system_contract::setacctram( name account, std::optional ram_bytes ) { - require_auth( _self ); - - int64_t current_ram, current_net, current_cpu; - get_resource_limits( account.value, ¤t_ram, ¤t_net, ¤t_cpu ); - - int64_t ram = 0; - - if( !ram_bytes ) { - auto vitr = _voters.find( account.value ); - eosio_assert( vitr != _voters.end() && has_field( vitr->flags1, voter_info::flags1_fields::ram_managed ), - "RAM of account is already unmanaged" ); - - user_resources_table userres( _self, account.value ); - auto ritr = userres.find( account.value ); - - ram = ram_gift_bytes; - if( ritr != userres.end() ) { - ram += ritr->ram_bytes; - } - - _voters.modify( vitr, same_payer, [&]( auto& v ) { - v.flags1 = set_field( v.flags1, voter_info::flags1_fields::ram_managed, false ); - }); - } else { - eosio_assert( *ram_bytes >= 0, "not allowed to set RAM limit to unlimited" ); - - auto vitr = _voters.find( account.value ); - if ( vitr != _voters.end() ) { - _voters.modify( vitr, same_payer, [&]( auto& v ) { - v.flags1 = set_field( v.flags1, voter_info::flags1_fields::ram_managed, true ); - }); - } else { - _voters.emplace( account, [&]( auto& v ) { - v.owner = account; - v.flags1 = set_field( v.flags1, voter_info::flags1_fields::ram_managed, true ); - }); - } - - ram = *ram_bytes; - } - - set_resource_limits( account.value, ram, current_net, current_cpu ); - } - - void system_contract::setacctnet( name account, std::optional net_weight ) { + void system_contract::setpriv( account_name account, uint8_t ispriv ) { require_auth( _self ); - - int64_t current_ram, current_net, current_cpu; - get_resource_limits( account.value, ¤t_ram, ¤t_net, ¤t_cpu ); - - int64_t net = 0; - - if( !net_weight ) { - auto vitr = _voters.find( account.value ); - eosio_assert( vitr != _voters.end() && has_field( vitr->flags1, voter_info::flags1_fields::net_managed ), - "Network bandwidth of account is already unmanaged" ); - - user_resources_table userres( _self, account.value ); - auto ritr = userres.find( account.value ); - - if( ritr != userres.end() ) { - net = ritr->net_weight.amount; - } - - _voters.modify( vitr, same_payer, [&]( auto& v ) { - v.flags1 = set_field( v.flags1, voter_info::flags1_fields::net_managed, false ); - }); - } else { - eosio_assert( *net_weight >= -1, "invalid value for net_weight" ); - - auto vitr = _voters.find( account.value ); - if ( vitr != _voters.end() ) { - _voters.modify( vitr, same_payer, [&]( auto& v ) { - v.flags1 = set_field( v.flags1, voter_info::flags1_fields::net_managed, true ); - }); - } else { - _voters.emplace( account, [&]( auto& v ) { - v.owner = account; - v.flags1 = set_field( v.flags1, voter_info::flags1_fields::net_managed, true ); - }); - } - - net = *net_weight; - } - - set_resource_limits( account.value, current_ram, net, current_cpu ); + set_privileged( account, ispriv ); } - void system_contract::setacctcpu( name account, std::optional cpu_weight ) { + void system_contract::rmvproducer( account_name producer ) { require_auth( _self ); - - int64_t current_ram, current_net, current_cpu; - get_resource_limits( account.value, ¤t_ram, ¤t_net, ¤t_cpu ); - - int64_t cpu = 0; - - if( !cpu_weight ) { - auto vitr = _voters.find( account.value ); - eosio_assert( vitr != _voters.end() && has_field( vitr->flags1, voter_info::flags1_fields::cpu_managed ), - "CPU bandwidth of account is already unmanaged" ); - - user_resources_table userres( _self, account.value ); - auto ritr = userres.find( account.value ); - - if( ritr != userres.end() ) { - cpu = ritr->cpu_weight.amount; - } - - _voters.modify( vitr, same_payer, [&]( auto& v ) { - v.flags1 = set_field( v.flags1, voter_info::flags1_fields::cpu_managed, false ); - }); - } else { - eosio_assert( *cpu_weight >= -1, "invalid value for cpu_weight" ); - - auto vitr = _voters.find( account.value ); - if ( vitr != _voters.end() ) { - _voters.modify( vitr, same_payer, [&]( auto& v ) { - v.flags1 = set_field( v.flags1, voter_info::flags1_fields::cpu_managed, true ); - }); - } else { - _voters.emplace( account, [&]( auto& v ) { - v.owner = account; - v.flags1 = set_field( v.flags1, voter_info::flags1_fields::cpu_managed, true ); - }); - } - - cpu = *cpu_weight; - } - - set_resource_limits( account.value, current_ram, current_net, cpu ); - } - - void system_contract::rmvproducer( name producer ) { - require_auth( _self ); - auto prod = _producers.find( producer.value ); + auto prod = _producers.find( producer ); eosio_assert( prod != _producers.end(), "producer not found" ); - _producers.modify( prod, same_payer, [&](auto& p) { + _producers.modify( prod, 0, [&](auto& p) { p.deactivate(); }); } - void system_contract::updtrevision( uint8_t revision ) { - require_auth( _self ); - eosio_assert( _gstate2.revision < 255, "can not increment revision" ); // prevent wrap around - eosio_assert( revision == _gstate2.revision + 1, "can only increment revision by one" ); - eosio_assert( revision <= 1, // set upper bound to greatest revision supported in the code - "specified revision is not yet supported by the code" ); - _gstate2.revision = revision; - } - - void system_contract::bidname( name bidder, name newname, asset bid ) { + void system_contract::bidname( account_name bidder, account_name newname, asset bid ) { require_auth( bidder ); - eosio_assert( newname.suffix() == newname, "you can only bid on top-level suffix" ); - - eosio_assert( (bool)newname, "the empty name is not a valid account name to bid on" ); - eosio_assert( (newname.value & 0xFull) == 0, "13 character names are not valid account names to bid on" ); - eosio_assert( (newname.value & 0x1F0ull) == 0, "accounts with 12 character names and no dots can be created without bidding required" ); + eosio_assert( eosio::name_suffix(newname) == newname, "you can only bid on top-level suffix" ); + eosio_assert( newname != 0, "the empty name is not a valid account name to bid on" ); + eosio_assert( (newname & 0xFull) == 0, "13 character names are not valid account names to bid on" ); + eosio_assert( (newname & 0x1F0ull) == 0, "accounts with 12 character names and no dots can be created without bidding required" ); eosio_assert( !is_account( newname ), "account already exists" ); - eosio_assert( bid.symbol == core_symbol(), "asset must be system token" ); + eosio_assert( bid.symbol == asset().symbol, "asset must be system token" ); eosio_assert( bid.amount > 0, "insufficient bid" ); - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {bidder, active_permission} }, - { bidder, names_account, bid, std::string("bid name ")+ newname.to_string() } - ); - - name_bid_table bids(_self, _self.value); - - if (newname.length() < BASE_LENGTH) - { - - auto idx = bids.get_index<"highbid"_n>(); - auto highest = idx.lower_bound(std::numeric_limits::max() / 2); - - if (highest != idx.end() && - highest->high_bid > 0) - { - std::string msg= "newname which length is less than 3 must increase bid by 10% than the highest bid in all bid :current value:"+std::to_string(highest->high_bid ); - eosio_assert(bid.amount - highest->high_bid > (highest->high_bid / 10),msg.c_str()); - } - } + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {bidder,N(active)}, + { bidder, N(eosio.names), bid, std::string("bid name ")+(name{newname}).to_string() } ); + name_bid_table bids(_self,_self); print( name{bidder}, " bid ", bid, " on ", name{newname}, "\n" ); - auto current = bids.find( newname.value ); + auto current = bids.find( newname ); if( current == bids.end() ) { bids.emplace( bidder, [&]( auto& b ) { b.newname = newname; b.high_bidder = bidder; b.high_bid = bid.amount; - b.last_bid_time = current_time_point(); + b.last_bid_time = current_time(); }); } else { eosio_assert( current->high_bid > 0, "this auction has already closed" ); eosio_assert( bid.amount - current->high_bid > (current->high_bid / 10), "must increase bid by 10%" ); eosio_assert( current->high_bidder != bidder, "account is already highest bidder" ); - bid_refund_table refunds_table(_self, newname.value); - - auto it = refunds_table.find( current->high_bidder.value ); - if ( it != refunds_table.end() ) { - refunds_table.modify( it, same_payer, [&](auto& r) { - r.amount += asset( current->high_bid, core_symbol() ); - }); - } else { - refunds_table.emplace( bidder, [&](auto& r) { - r.bidder = current->high_bidder; - r.amount = asset( current->high_bid, core_symbol() ); - }); - } - - transaction t; - t.actions.emplace_back( permission_level{_self, active_permission}, - _self, "bidrefund"_n, - std::make_tuple( current->high_bidder, newname ) - ); - t.delay_sec = 0; - uint128_t deferred_id = (uint128_t(newname.value) << 64) | current->high_bidder.value; - cancel_deferred( deferred_id ); - t.send( deferred_id, bidder ); + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.names),N(active)}, + { N(eosio.names), current->high_bidder, asset(current->high_bid), + std::string("refund bid on name ")+(name{newname}).to_string() } ); bids.modify( current, bidder, [&]( auto& b ) { b.high_bidder = bidder; b.high_bid = bid.amount; - b.last_bid_time = current_time_point(); + b.last_bid_time = current_time(); }); } } - void system_contract::bidrefund( name bidder, name newname ) { - bid_refund_table refunds_table(_self, newname.value); - auto it = refunds_table.find( bidder.value ); - eosio_assert( it != refunds_table.end(), "refund not found" ); - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {names_account, active_permission}, {bidder, active_permission} }, - { names_account, bidder, asset(it->amount), std::string("refund bid on name ")+(name{newname}).to_string() } - ); - refunds_table.erase( it ); - } - /** * Called after a new account is created. This code enforces resource-limits rules * for new accounts as well as new account naming conventions. @@ -463,13 +143,14 @@ namespace eosiosystem { * who can create accounts with the creator's name as a suffix. * */ - void native::newaccount( name creator, - name newact, - ignore owner, - ignore active ) { + void native::newaccount( account_name creator, + account_name newact + /* no need to parse authorities + const authority& owner, + const authority& active*/ ) { if( creator != _self ) { - uint64_t tmp = newact.value >> 4; + auto tmp = newact >> 4; bool has_dot = false; for( uint32_t i = 0; i < 12; ++i ) { @@ -477,14 +158,10 @@ namespace eosiosystem { tmp >>= 5; } if( has_dot ) { // or is less than 12 characters - auto suffix = newact.suffix(); + auto suffix = eosio::name_suffix(newact); if( suffix == newact ) { - if("bos"_n==suffix) - { - require_auth(_self); - } - name_bid_table bids(_self, _self.value); - auto current = bids.find( newact.value ); + name_bid_table bids(_self,_self); + auto current = bids.find( newact ); eosio_assert( current != bids.end(), "no active bid for name" ); eosio_assert( current->high_bidder == creator, "only highest bidder can claim" ); eosio_assert( current->high_bid < 0, "auction for name is not closed yet" ); @@ -492,78 +169,30 @@ namespace eosiosystem { } else { eosio_assert( creator == suffix, "only suffix may create this account" ); } - - const static auto BOS_PREFIX = "bos."; - std::string::size_type p = newact.to_string().find(BOS_PREFIX); - if(p != std::string::npos && 0 == p) - { - require_auth(_self); - } - } } - user_resources_table userres( _self, newact.value); + user_resources_table userres( _self, newact); userres.emplace( newact, [&]( auto& res ) { res.owner = newact; - res.net_weight = asset( 0, system_contract::get_core_symbol() ); - res.cpu_weight = asset( 0, system_contract::get_core_symbol() ); }); - set_resource_limits( newact.value, 0, 0, 0 ); + set_resource_limits( newact, 0, 0, 0 ); } - void native::setabi( name acnt, const std::vector& abi ) { - eosio::multi_index< "abihash"_n, abi_hash > table(_self, _self.value); - auto itr = table.find( acnt.value ); - if( itr == table.end() ) { - table.emplace( acnt, [&]( auto& row ) { - row.owner= acnt; - sha256( const_cast(abi.data()), abi.size(), &row.hash ); - }); - } else { - table.modify( itr, same_payer, [&]( auto& row ) { - sha256( const_cast(abi.data()), abi.size(), &row.hash ); - }); - } - } - - void system_contract::init( unsigned_int version, symbol core ) { - require_auth( _self ); - eosio_assert( version.value == 0, "unsupported version for init action" ); - - auto itr = _rammarket.find(ramcore_symbol.raw()); - eosio_assert( itr == _rammarket.end(), "system contract has already been initialized" ); - - auto system_token_supply = eosio::token::get_supply(token_account, core.code() ); - eosio_assert( system_token_supply.symbol == core, "specified core symbol does not exist (precision mismatch)" ); - - eosio_assert( system_token_supply.amount > 0, "system token supply must be greater than 0" ); - _rammarket.emplace( _self, [&]( auto& m ) { - m.supply.amount = 100000000000000ll; - m.supply.symbol = ramcore_symbol; - m.base.balance.amount = int64_t(_gstate.free_ram()); - m.base.balance.symbol = ram_symbol; - m.quote.balance.amount = system_token_supply.amount / 1000; - m.quote.balance.symbol = core; - }); - } } /// eosio.system -EOSIO_DISPATCH( eosiosystem::system_contract, +EOSIO_ABI( eosiosystem::system_contract, // native.hpp (newaccount definition is actually in eosio.system.cpp) - (newaccount)(updateauth)(deleteauth)(linkauth)(unlinkauth)(canceldelay)(onerror)(setabi) + (newaccount)(updateauth)(deleteauth)(linkauth)(unlinkauth)(canceldelay)(onerror) // eosio.system.cpp - (init)(setram)(setramrate)(setparams)(namelist)(setguaminres)(setpriv)(setalimits)(setacctram)(setacctnet)(setacctcpu) - (rmvproducer)(updtrevision)(bidname)(bidrefund) + (setram)(setparams)(setpriv)(rmvproducer)(bidname) // delegate_bandwidth.cpp (buyrambytes)(buyram)(sellram)(delegatebw)(undelegatebw)(refund) // voting.cpp (regproducer)(unregprod)(voteproducer)(regproxy) // producer_pay.cpp (onblock)(claimrewards) - //upgrade.cpp - (setupgrade) ) diff --git a/contracts/eosio.system/eosio.system.hpp b/contracts/eosio.system/eosio.system.hpp index 04ddf000803..2b6d16d3d59 100644 --- a/contracts/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/eosio.system.hpp @@ -1,6 +1,6 @@ /** * @file - * @copyright defined in eos/LICENSE.txt + * @copyright defined in eos/LICENSE */ #pragma once @@ -12,65 +12,30 @@ #include #include -#include -#include namespace eosiosystem { - using eosio::name; using eosio::asset; - using eosio::symbol; - using eosio::symbol_code; using eosio::indexed_by; using eosio::const_mem_fun; using eosio::block_timestamp; - using eosio::time_point; - using eosio::microseconds; - using eosio::datastream; - - template - static inline auto has_field( F flags, E field ) - -> std::enable_if_t< std::is_integral_v && std::is_unsigned_v && - std::is_enum_v && std::is_same_v< F, std::underlying_type_t >, bool> - { - return ( (flags & static_cast(field)) != 0 ); - } - - template - static inline auto set_field( F flags, E field, bool value = true ) - -> std::enable_if_t< std::is_integral_v && std::is_unsigned_v && - std::is_enum_v && std::is_same_v< F, std::underlying_type_t >, F > - { - if( value ) - return ( flags | static_cast(field) ); - else - return ( flags & ~static_cast(field) ); - } - - struct [[eosio::table, eosio::contract("eosio.system")]] name_bid { - name newname; - name high_bidder; - int64_t high_bid = 0; ///< negative high_bid == closed auction waiting to be claimed - time_point last_bid_time; - - uint64_t primary_key()const { return newname.value; } - uint64_t by_high_bid()const { return static_cast(-high_bid); } - }; - struct [[eosio::table, eosio::contract("eosio.system")]] bid_refund { - name bidder; - asset amount; + struct name_bid { + account_name newname; + account_name high_bidder; + int64_t high_bid = 0; ///< negative high_bid == closed auction waiting to be claimed + uint64_t last_bid_time = 0; - uint64_t primary_key()const { return bidder.value; } + auto primary_key()const { return newname; } + uint64_t by_high_bid()const { return static_cast(-high_bid); } }; - typedef eosio::multi_index< "namebids"_n, name_bid, - indexed_by<"highbid"_n, const_mem_fun > - > name_bid_table; + typedef eosio::multi_index< N(namebids), name_bid, + indexed_by > + > name_bid_table; - typedef eosio::multi_index< "bidrefunds"_n, bid_refund > bid_refund_table; - struct [[eosio::table("global"), eosio::contract("eosio.system")]] eosio_global_state : eosio::blockchain_parameters { + struct eosio_global_state : eosio::blockchain_parameters { uint64_t free_ram()const { return max_ram_size - total_ram_bytes_reserved; } uint64_t max_ram_size = 64ll*1024 * 1024 * 1024; @@ -78,12 +43,12 @@ namespace eosiosystem { int64_t total_ram_stake = 0; block_timestamp last_producer_schedule_update; - time_point last_pervote_bucket_fill; + uint64_t last_pervote_bucket_fill = 0; int64_t pervote_bucket = 0; int64_t perblock_bucket = 0; - uint32_t total_unpaid_blocks = 0; /// all blocks which have been produced but not paid + uint32_t total_unpaid_blocks = 0; /// all blocks which have been produced but not paid int64_t total_activated_stake = 0; - time_point thresh_activated_stake_time; + uint64_t thresh_activated_stake_time = 0; uint16_t last_producer_schedule_size = 0; double total_producer_vote_weight = 0; /// the sum of all producer votes block_timestamp last_name_close; @@ -96,48 +61,17 @@ namespace eosiosystem { (last_producer_schedule_size)(total_producer_vote_weight)(last_name_close) ) }; - /** - * Defines new global state parameters added after version 1.0 - */ - struct [[eosio::table("global2"), eosio::contract("eosio.system")]] eosio_global_state2 { - eosio_global_state2(){} - - uint16_t new_ram_per_block = 0; - block_timestamp last_ram_increase; - block_timestamp last_block_num; /* deprecated */ - double total_producer_votepay_share = 0; - uint8_t revision = 0; ///< used to track version updates in the future. - - EOSLIB_SERIALIZE( eosio_global_state2, (new_ram_per_block)(last_ram_increase)(last_block_num) - (total_producer_votepay_share)(revision) ) - }; - - struct [[eosio::table("global3"), eosio::contract("eosio.system")]] eosio_global_state3 { - eosio_global_state3() { } - time_point last_vpay_state_update; - double total_vpay_share_change_rate = 0; - - EOSLIB_SERIALIZE( eosio_global_state3, (last_vpay_state_update)(total_vpay_share_change_rate) ) - }; - - struct [[eosio::table("upgrade"), eosio::contract("eosio.system")]] upgrade_state : eosio::upgrade_parameters { - uint16_t current_version = 0; - - EOSLIB_SERIALIZE_DERIVED( upgrade_state, eosio::upgrade_parameters, (current_version) ) - }; - - - struct [[eosio::table, eosio::contract("eosio.system")]] producer_info { - name owner; + struct producer_info { + account_name owner; double total_votes = 0; eosio::public_key producer_key; /// a packed public key object bool is_active = true; std::string url; uint32_t unpaid_blocks = 0; - time_point last_claim_time; + uint64_t last_claim_time = 0; uint16_t location = 0; - uint64_t primary_key()const { return owner.value; } + uint64_t primary_key()const { return owner; } double by_votes()const { return is_active ? -total_votes : total_votes; } bool active()const { return is_active; } void deactivate() { producer_key = public_key(); is_active = false; } @@ -147,22 +81,11 @@ namespace eosiosystem { (unpaid_blocks)(last_claim_time)(location) ) }; - struct [[eosio::table, eosio::contract("eosio.system")]] producer_info2 { - name owner; - double votepay_share = 0; - time_point last_votepay_share_update; - - uint64_t primary_key()const { return owner.value; } - - // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( producer_info2, (owner)(votepay_share)(last_votepay_share_update) ) - }; - - struct [[eosio::table, eosio::contract("eosio.system")]] voter_info { - name owner; /// the voter - name proxy; /// the proxy set by the voter, if any - std::vector producers; /// the producers approved by this voter if no proxy set - int64_t staked = 0; + struct voter_info { + account_name owner = 0; /// the voter + account_name proxy = 0; /// the proxy set by the voter, if any + std::vector producers; /// the producers approved by this voter if no proxy set + int64_t staked = 0; /** * Every time a vote is cast we must first "undo" the last vote weight, before casting the @@ -170,116 +93,54 @@ namespace eosiosystem { * * stated.amount * 2 ^ ( weeks_since_launch/weeks_per_year) */ - double last_vote_weight = 0; /// the vote weight cast the last time the vote was updated + double last_vote_weight = 0; /// the vote weight cast the last time the vote was updated /** * Total vote weight delegated to this voter. */ - double proxied_vote_weight= 0; /// the total vote weight delegated to this voter as a proxy - bool is_proxy = 0; /// whether the voter is a proxy for others - + double proxied_vote_weight= 0; /// the total vote weight delegated to this voter as a proxy + bool is_proxy = 0; /// whether the voter is a proxy for others - uint32_t flags1 = 0; - uint32_t reserved2 = 0; - eosio::asset reserved3; - uint64_t primary_key()const { return owner.value; } + uint32_t reserved1 = 0; + time reserved2 = 0; + eosio::asset reserved3; - enum class flags1_fields : uint32_t { - ram_managed = 1, - net_managed = 2, - cpu_managed = 4 - }; + uint64_t primary_key()const { return owner; } // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( voter_info, (owner)(proxy)(producers)(staked)(last_vote_weight)(proxied_vote_weight)(is_proxy)(flags1)(reserved2)(reserved3) ) - }; - - // *bos* - struct [[eosio::table("guaranminres"), eosio::contract("eosio.system")]] eosio_guaranteed_min_res{ - eosio_guaranteed_min_res(){} - - uint32_t ram = 0; /// guaranteed minimum ram in kb for every account. - uint32_t cpu = 0; /// guaranteed minimum cpu in bos for every account. - uint32_t net = 0; /// guaranteed minimum net in bos for every account. - - EOSLIB_SERIALIZE( eosio_guaranteed_min_res, (ram)(cpu)(net) ) + EOSLIB_SERIALIZE( voter_info, (owner)(proxy)(producers)(staked)(last_vote_weight)(proxied_vote_weight)(is_proxy)(reserved1)(reserved2)(reserved3) ) }; - typedef eosio::multi_index< "voters"_n, voter_info > voters_table; + typedef eosio::multi_index< N(voters), voter_info> voters_table; - typedef eosio::multi_index< "producers"_n, producer_info, - indexed_by<"prototalvote"_n, const_mem_fun > - > producers_table; - typedef eosio::multi_index< "producers2"_n, producer_info2 > producers_table2; - typedef eosio::singleton< "global"_n, eosio_global_state > global_state_singleton; - typedef eosio::singleton< "global2"_n, eosio_global_state2 > global_state2_singleton; - typedef eosio::singleton< "global3"_n, eosio_global_state3 > global_state3_singleton; - typedef eosio::singleton< "guaranminres"_n, eosio_guaranteed_min_res > guaranteed_min_res_singleton; // *bos* + typedef eosio::multi_index< N(producers), producer_info, + indexed_by > + > producers_table; - typedef eosio::singleton< "upgrade"_n, upgrade_state > upgrade_singleton; + typedef eosio::singleton global_state_singleton; // static constexpr uint32_t max_inflation_rate = 5; // 5% annual inflation static constexpr uint32_t seconds_per_day = 24 * 3600; + static constexpr uint64_t system_token_symbol = CORE_SYMBOL; - class [[eosio::contract("eosio.system")]] system_contract : public native { + class system_contract : public native { private: - voters_table _voters; - producers_table _producers; - producers_table2 _producers2; - global_state_singleton _global; - global_state2_singleton _global2; - global_state3_singleton _global3; - guaranteed_min_res_singleton _guarantee; // *bos* - eosio_global_state _gstate; - eosio_global_state2 _gstate2; - eosio_global_state3 _gstate3; - rammarket _rammarket; - upgrade_singleton _upgrade; - upgrade_state _ustate; + voters_table _voters; + producers_table _producers; + global_state_singleton _global; + + eosio_global_state _gstate; + rammarket _rammarket; public: - static constexpr eosio::name active_permission{"active"_n}; - static constexpr eosio::name token_account{"eosio.token"_n}; - static constexpr eosio::name ram_account{"eosio.ram"_n}; - static constexpr eosio::name ramfee_account{"eosio.ramfee"_n}; - static constexpr eosio::name stake_account{"eosio.stake"_n}; - static constexpr eosio::name bpay_account{"eosio.bpay"_n}; - static constexpr eosio::name vpay_account{"eosio.vpay"_n}; - static constexpr eosio::name names_account{"eosio.names"_n}; - static constexpr eosio::name saving_account{"eosio.saving"_n}; - static constexpr eosio::name dev_account{"bos.dev"_n}; - static constexpr eosio::name gov_account{"bos.gov"_n}; - static constexpr symbol ramcore_symbol = symbol(symbol_code("RAMCORE"), 4); - static constexpr symbol ram_symbol = symbol(symbol_code("RAM"), 0); - static const int16_t BASE_LENGTH = 4; - system_contract( name s, name code, datastream ds ); + system_contract( account_name s ); ~system_contract(); - static symbol get_core_symbol( name system_account = "eosio"_n ) { - rammarket rm(system_account, system_account.value); - const static auto sym = get_core_symbol( rm ); - return sym; - } - // Actions: - [[eosio::action]] - void init( unsigned_int version, symbol core ); - [[eosio::action]] - void onblock( ignore header ); - - [[eosio::action]] - void setalimits( name account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight ); - - [[eosio::action]] - void setacctram( name account, std::optional ram_bytes ); - - [[eosio::action]] - void setacctnet( name account, std::optional net_weight ); - - [[eosio::action]] - void setacctcpu( name account, std::optional cpu_weight ); + void onblock( block_timestamp timestamp, account_name producer ); + // const block_header& header ); /// only parse first 3 fields of block header // functions defined in delegate_bandwidth.cpp @@ -288,8 +149,7 @@ namespace eosiosystem { * If transfer == true, then 'receiver' can unstake to their account * Else 'from' can unstake at any time. */ - [[eosio::action]] - void delegatebw( name from, name receiver, + void delegatebw( account_name from, account_name receiver, asset stake_net_quantity, asset stake_cpu_quantity, bool transfer ); @@ -309,122 +169,67 @@ namespace eosiosystem { * The 'from' account loses voting power as a result of this call and * all producer tallies are updated. */ - [[eosio::action]] - void undelegatebw( name from, name receiver, + void undelegatebw( account_name from, account_name receiver, asset unstake_net_quantity, asset unstake_cpu_quantity ); - /** * Increases receiver's ram quota based upon current price and quantity of * tokens provided. An inline transfer from receiver to system contract of * tokens will be executed. */ - [[eosio::action]] - void buyram( name payer, name receiver, asset quant ); - [[eosio::action]] - void buyrambytes( name payer, name receiver, uint32_t bytes ); + void buyram( account_name buyer, account_name receiver, asset tokens ); + void buyrambytes( account_name buyer, account_name receiver, uint32_t bytes ); /** * Reduces quota my bytes and then performs an inline transfer of tokens * to receiver based upon the average purchase price of the original quota. */ - [[eosio::action]] - void sellram( name account, int64_t bytes ); + void sellram( account_name receiver, int64_t bytes ); /** * This action is called after the delegation-period to claim all pending * unstaked tokens belonging to owner */ - [[eosio::action]] - void refund( name owner ); + void refund( account_name owner ); // functions defined in voting.cpp - [[eosio::action]] - void regproducer( const name producer, const public_key& producer_key, const std::string& url, uint16_t location ); + void regproducer( const account_name producer, const public_key& producer_key, const std::string& url, uint16_t location ); - [[eosio::action]] - void unregprod( const name producer ); + void unregprod( const account_name producer ); - [[eosio::action]] void setram( uint64_t max_ram_size ); - [[eosio::action]] - void setramrate( uint16_t bytes_per_block ); - [[eosio::action]] - void voteproducer( const name voter, const name proxy, const std::vector& producers ); + void voteproducer( const account_name voter, const account_name proxy, const std::vector& producers ); - [[eosio::action]] - void regproxy( const name proxy, bool isproxy ); + void regproxy( const account_name proxy, bool isproxy ); - [[eosio::action]] void setparams( const eosio::blockchain_parameters& params ); - // *bos* - [[eosio::action]] - void namelist(std::string list, std::string action, const std::vector& names ); - - // *bos* - [[eosio::action]] - void setguaminres(uint32_t ram, uint32_t cpu, uint32_t net); - // functions defined in producer_pay.cpp - [[eosio::action]] - void claimrewards( const name owner ); - - [[eosio::action]] - void setpriv( name account, uint8_t is_priv ); - - [[eosio::action]] - void rmvproducer( name producer ); - - [[eosio::action]] - void updtrevision( uint8_t revision ); - - [[eosio::action]] - void bidname( name bidder, name newname, asset bid ); + void claimrewards( const account_name& owner ); - [[eosio::action]] - void bidrefund( name bidder, name newname ); + void setpriv( account_name account, uint8_t ispriv ); - //functions defined in upgrade.cpp - [[eosio::action]] - void setupgrade( const eosio::upgrade_parameters& params); + void rmvproducer( account_name producer ); + void bidname( account_name bidder, account_name newname, asset bid ); private: - // Implementation details: - - static symbol get_core_symbol( const rammarket& rm ) { - auto itr = rm.find(ramcore_symbol.raw()); - eosio_assert(itr != rm.end(), "system contract must first be initialized"); - return itr->quote.balance.symbol; - } - - //defined in eosio.system.cpp - static eosio_global_state get_default_parameters(); - static time_point current_time_point(); - static block_timestamp current_block_time(); - - symbol core_symbol()const; + void update_elected_producers( block_timestamp timestamp ); - void update_ram_supply(); + // Implementation details: - //defined in delegate_bandwidth.cpp - void changebw( name from, name receiver, + //defind in delegate_bandwidth.cpp + void changebw( account_name from, account_name receiver, asset stake_net_quantity, asset stake_cpu_quantity, bool transfer ); //defined in voting.hpp - void update_elected_producers( block_timestamp timestamp ); - void update_votes( const name voter, const name proxy, const std::vector& producers, bool voting ); + static eosio_global_state get_default_parameters(); + + void update_votes( const account_name voter, const account_name proxy, const std::vector& producers, bool voting ); // defined in voting.cpp void propagate_weight_change( const voter_info& voter ); - - double update_producer_votepay_share( const producers_table2::const_iterator& prod_itr, - time_point ct, - double shares_rate, bool reset_to_zero = false ); - double update_total_votepay_share( time_point ct, - double additional_shares_delta = 0.0, double shares_rate_delta = 0.0 ); }; } /// eosiosystem diff --git a/contracts/eosio.system/eosio.system.wasm b/contracts/eosio.system/eosio.system.wasm deleted file mode 100755 index 9bc8d3de02161e9e6b9079de56553863d3a1340e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174107 zcmeFa3%p%tS?9kl`*!v|mzDd;t*pHXa6%gzproM`de)(r^a2DZv@q2qG=b*i(lnP^ zH0?P+fq;rpbPN?W6r_Q%7_m6TK_@6k!1|dEBT^l8IwNXT#_4%BeibAO)qUD1|(yW%K{;=f8>v^kl&Bifu!-7%%V`khSK&pY^8 zow|d6(dL*E{nnF;u68@+ZtYUDr<6y$Z4FLM#Z#N3_>RpLwZU)N<*ueXdqzWQpsIU4 zuW+mO^@5FpvRI8!q6eufSLdLK{a$cf4Dzre{Z+B@(N8e zHfBsCRhqKCH4C26l{II@-1C|ZxnTwTi8j~$PX=>PO&@fBr}*6pZ*f&ecf@xj-yC>p zx^4HZgL}8#a$x)3ZJV|n*nd+rWJTL<-naecOVz?){tgZ{M{o8c|Ve>zg*cdHe3Io3^jJAj(v-c;B}DoA&IzdC$%Jwr$mqtp{%0 zws&7NPY5)sJF5TJRQzxOdyWn-A>0aoeVy z+jni>zb{&3Pc-$W#<11wvkQI!tu+Xghs zvH_LN?;nbqiL8l*>TdUzT^htbptj}49kjDlb#gs=%YiL>x9r}(4M6VNzI*$w1G~z> z?TeP_wP6SC?YC~72X^m|R;Zu`WOnc0?N-^{?2Br8(y%A{ zHtqDwz3-+iFL=?qXr-QITW;LH{pQ`90Q{Cs`?mk^w&)xy-n(tfR@yJ0xw!~a7>vV| zMQ+CHE|_Csw3@6O&$@sZDa(4-sY$7pN{CR1+jiZ2t3w)_t*;-D#?UOvRe#jA9G=WInyI`}p> zZ91^~=Dl0@zxf4@JnEY^ZM&tsYi!!I>%h+aZ+>A^)hM{#vXytcxBCvfsl27??rmk& z!A-#SwgbEO?SJ#W?NM9RhbY>4^OpS-Zr}fA2ZjNAt@}>Pzteq#S|B7?yY4*}wO|?i=^Nc`I$K()$dT#S8Ohxc-HG6g^8X7>d7X=gl|n zxbdbf+joQhdm)AWFpEG5#)5L8z-eDIT3RVeII!m}d$(-eHfK}!C7(xnxOWNwd+ctz0seHkYhiQdv3H;?hjVmZVEorfGBa*yzf% zSzp2pudi&?qk$!9b1X_5+{G)?CFygQ@NYEX-e-^0m9M?2$ zSJDcPmaJ?h&1L7#_*Xl(rV?HJk8{suP)lgH`>(0a?O&SK^w8coZ+Oj;)lH_cq}jAL z_%k|MtEn!3o|mpXe|XJsR6D1(_W3VZdtUQ}%{3Pc504Hnsnu$uOGayzYP=_Y&U311 z9L2T8=~P@ypNLvhQE}qqak$ifJsuSK-@Ny&Nd$u3v~A0tO>f$=Z(ACJ|HeK*uQaJ{ z*|TToTcbaZYn$Gf-&LzB_(*pDrf(dQC9+rqQoO%~d-*Y1D*^SA8W z{+8XFZr!qT)BflSR=#C&X8Flvk!yA4gD=_xnwY(_r|{+|GS@!ABcZ7{zm-w@h9T#zaRg9@ju7E6+agLar|@f z@5Vp)WAUBIe~jM~|5@^I(meLQ<|h+C@YVTgWh%L&HucP?n2vXJ;#E-|txlsX%Htwh zmyGA}M08=go*z*$I@w9a5?&}4PIgj$bt*fG^y;Cgh>G`rB+lYoh3!gq-<)czY3a2?QC^#fUNaQCSJedVv}>)rwvi4DYDZIm zMI(9-7H;oY)6rzRp2w@x_3f%_m_{Cs1cXk5pI>u7E0+%evveGQ+~lF2&{|%7HNB?o zO5VuRD~2Mb+v2BtJ5P!~vD$f^+D!3_A7*u0d17G6>#83W4@R2X#Psy^Z~e?i?)k>O zQ+YM3bkZW)K+E+!%`VY$-!P+%sJLGfa%&K44R|-n##wY0i3d?}B-JE#6#p1qDag=L zVGnuquz&Qn-)&wwlz=B|CxvEU+r-p`Y3{IXOt^PZbU{vKM%V$W8Na!Va*2zKUK_mn zSojBqUT;qtpi1#jG?`5ZqUw|hffYFmkWIv|9g36jfFV&>=fT>Xm|(Tr>0q^0%x@2d zOQlsT<1TdEC@e5rPCH>eOric{Q)`c>3qxch@st3kVD* z1|ETd!Lk5tK?bmFbnw`~RD?mD>PFhDPK>+*T;jU$UAlSKo%~&Df6uYM&$Pc|_IHi_ zeS!VG@LhM7AgjNAZ#xm4o`g=*?BxPhb}8Tzu&lIbfSwE`O-o2tJP;$z)q!ycU12s6 zF-HSE6-rRN!IPwDEAM^4qO?@trpG}11Z}F{#uUM4``5VEQ6M9^27;=Ck`MzlVniC} z4Q9A5xdvK^;cLZXkEOR1%_|sX8sJjNUf%!o@*%-2yA-NRRV#J12CfFpD*$gOf(X^0 z2zxBq&<0D3Z$-T*ev@0$JzC(4qJw!{qPQ@ap=|~Nj8;Q6RMt8S@bgjFMU(3{xWb~^ z3IB)qft%O|G#9HYf#s!2G`hu{!fUvs2<1W8H`D7GLD_>3ZE zHZN%9VuXnpVLtc__?-CQisBret%cQ&CAov;6+@lDHY>Cmb}>K`;(aIH{}0E${DB_c zcTSN~?|DK3I0?jYS*P2AMpNPO8VMKUszsIC^TOLvc6m{w;9Mrd$_ekTph|-4SXX_t2j$yDOPKD zbdq+OC#xc88(bRgRABonG|x^oi1%u7n+o#yaxoJ$45WQE2zKtp5LBo|MaUpHQPrv= zUh)78I6#(ns>Reb_U79vucD!ZIwOALbYS4>yoXK73-4CQjFFmqq;~G)Ftv$JsETi$^h1B2xO9(x!~|BCYt2D z+Tg;g)X;E7HERh|iqTp1tL79Rsj$3be=_|VoXmbA&TFWP!pXmbyQ9I5wjls(h~CWK(VS^d>k z_!TQ;DMXV<#3EwEV?VhF+9pAOrl2g+=9V*VFO5PCevL zb)tSqi-poDQb8X#>|!3BMiJi%dx~#aF8-vK8~3)Wb5|AgY4UBAD*<+0=#qft0(-zK z&>FDi2}|WJD5QGM;I$x9$+9;t(~LGx*m`9HJxwNpI`=ifA-k4NBJ{h7JJ74+gZ#Pu z^~=FCb#MwOpmfrUE$P@KHpABvzhYy_)=s^cx{4-aDbogM{b}^7yoQgptl6o9C#}5B z5K~xCh9GyV+WPrsQlF~a0UaO4Kw`Fx5Dixdr|F>HMNIV&v{oD$5QXsIAP>0QrVFJ! zpXRyx)xX;=1lEZllN5(y{bhBsE0_rS_FzXS=gNvTlj0kC%-a%i_H*r|trjmI?9^1} zwjIU#UByDFA@R9s3g+gil?RJJBs4~kQJbh21E*+Ds><<(JiVWf^I#IlpBGA4sS zWF1o?q(bq~1-*mCK~z*&8y@C**csHDR-MA`4bvLXlSeu3@h$Jf6kj*@Y~%e1S8p?2*RfDLf0PRG?| z4Gw9%#NCW}Fn8xzV;VFd*xG#DdyLAJ830@v({p86fKN@6lFq6Z68c)t5+5|e(1_gY z*#_u@7qcsu`Gu8!=*Hb$x=yUc1C1U`C(n%dVm-yetQ6=mgkM!!31(-gdQP zW)I)eQvKn_zw+EyIb&*PCl&_Qa$^C88!J?6lwLZOV&kr2?M_gNx-k6>)P}5Jd@y6Z z)D~e2Dxw8VYh6NZDFd(?>r32DU`HN@yDQ@#4@lV zAT(6yN+7E3DAw+}w>K`fjBinqc#E_D}Rt!3E2ut8LtSN4i!ZV4K02?q0cM^>Gf zx(-kq7TLP!kFf{Q-aZU=PpeR!C{YhM#@_2%pSCOWo!KHY~4nTfbjMYK{AVG7% zbbRbF6feUnUPEf`KwhN>ihtojgli05S1*zSAW$u@Ub(y@H3gLN4Pd3#igZ8Zjs_6n zU85)b2I>%{AGOrsQ&r>;Wq-(^wOX`Up%$*Vk|@n^La^-soAFKPDR_zd4JpH_C5};` z65-{ML(V!71VAHUT*bkJUl1p_%F@V7HTmKezC#e%}g;MHupBD-oK$zE*4uGBo6LV_>9G zs6f4QI_T6=oMBa~Q-1?9v$#O}Pbk;{!tewO7&{AT2I0*?ANM1T# z;!O#b)|oO;tsYfb3$Va`$uc+(;34m%KV~16t%OA~1{HwXGJs}SBx->t@G^(Mu%jo_ zBC&x~CbK_)%Fketct%{>9skWS;n~(EJU5qPL5Oo&ivz%PDig3Z5c+m`T)`{3qj0=Z z0*gVbC^o}w#owju;{ww~cM!c+%Jc-Mr#tWgD}hXO$3=3zAL2iYq#H|C(Q8uVW)-l8 z{Z>F#RzS6D1%wgeD;Aq!)V6}XdOy_QXcO)sH4AL!(NYd>R0cfUUA40<%7h|78wEaa8ZQ_i*f^Ge- zAlQECr@pvKa_u8r`^6giDw6Fdzxa{)L8`_3`=pvg+4`NQ5M{Vlf+!Q;H&JF{Z2itY zF(!Rq@dOrI*lWVN$E2x<(0g=EGeCNDO>f$ix~2pm@0Yr!7n5X(G& zdaN@L61`FS3pz6t+9xcHRE%c>U9eHD&lZ+{azhE$Vo`r<31s2Qc6ly zujShnl1=1q5RwovIN*e&*V`OPXY&FMI}sUVxJlvJVlpxXEeZb19tABmLD>_tGMQL; z($QFoiNQJu;MV_HUTLDLHm9nx=H-=J6`3Y~cnV#I#oNGQI_91li+8`Zt~r_p%B?Ut zDymqT6sE_T_A5!dAtkgz zK`lGZoihz9=f`V11L9hkgKIm2lQCt}Ro{qqgsW~KPp=#zK3Q!y83%+5gAXIlo6ta~ zg(=yAqp5aN>`yU20wSgj<|(d0dZws7NOip+l1aom;n%u|XS%X{Qp16IIzwd{{(@o3 zxQvu#2#}02F7wK=#$;zcWn32cvLVJtQw#m=2)DEnTAs%(4TMG)a7*3L9#hfKLQ{!z zIxMi*n8w-+21h&6l|2vpUklCo@0bLzN`X}ySmgp5=28xq(FG6LBoQ1DH&7v7 z*}yL>aDgWsB1H=J80CWuEQ5kHw|U5SS4~Jow^}#?9JX+1?$|1q4+*M@>U5WG05YVB zrWUW;MC6+At49;IdVaB@Vh;`-Ke0rhRUUq)(|MhBi|ypt|bf|_$8cQE}{8v4Dqr& zEcjO=L+N;4Z-WvB*>-S2*F})n1|?k%nrh(}28V|&44g#~yShCSlGOzk8Mwfp zy##JxGNz5M8)&L*DZk7G)yDa*g5}g1n`GEydhXglddclw-s9kqJG;C`6O!lD-;1G_ zqRY^tkuOt2h7QB|QumwX8_7rAZ>?Rv#D3@UxiSVMs%GQJ!ZAbOY961VcGGbjc|`K2 zCisXrR@7;U=b*p?a|R>efcUk%^{OGR!f${h!cRD5R8B!&21*FR zg-MyWH1&xK8e*yt5PxyOmhZ^_mdaaFmOx~b*l<+I(Qk2h%9C(A5+4>1FUQn|(1De; z0PQ$Nqva?OP-~F~)VH`oKvJzj9;jd-uQ9t;K1BEJAwUWuf@VU+fmSQX7vmq%aBXNP zij#B*lofm=cW(o41=MD4ask+)Q8FxJ-q#LQ2wVkxRvkm;ge6m0EIcqSV@+niDo@{> zkBtJ{OTW_l5_I*dp$2WysR;}9Zb%|JW>~tk$}+fx)FCcio&?JftK$+lhw&eA)*kke zh)PB%;}W2Us8myd5e#ulE1~6KZfPJi>Vgd=JJO0Ww9u62G+8O^Fd+P>=i)E*lE2I= zc~=F%IbqR6SZG-spK0n+Le*G%jnFz%G18Gn;7O%!`2rw-sLIJ`$q^^tC2=Jt5DzA8 zzQh5FR!hu}2eh$Vl?%_&NzXd*4 zxf;0vukwPw)@h2bv!LSt?V)ltWhQn8vZ`sO~^nst1exGWmSa7B!nC!-F0IT#S6LgQKAThwG52O)>2f$S_YD`wM6`i zkb;;uN?b@8Yq`kdhV(R=TI_G7r_oAiS$Y}`ghm%}OWn|3i(7^kn(~ymsFaUgwQWHj z^uoCmxWy&E70}xe+LYj?MI3S(Thpo6moF!z>7ebEI0tlKro0;CwkFE~nq;{VkrvEg z>xqbkFmYajJFp!i9S`pA5&#^nFc&43I0gxXJjgy-4vo&|7I2650D{0BnkrSSpwW}9=SMh~iUmyl(*=1zCPQ@bI|6}DEim3Cz_Yfx zI91u?S>6ys!}#0ouxwRmU^BX#Vl$K6a;mXipDq~L?mupB-IC{jiwyrxhCgWyAAT{05PM88!oU;dXES)`aQ8~XT z053f~7ErZ7nj&Z;t=1>WpvM(qdDlcm-Sh4VhA~6mEH4XS4rh*p@6r{v1BZU<|3RHiBiaUz`{ zXlIK@!n|dINV_kNzdg=g)v1tnQGAu3u^y!&X_l+_@;fLI<;+e*o;xL7xQHkITcTyu z@Qt@mOj~mC>xy^Y+0)cn6y!dDw2&C4T~8vG}!5U;54e{?)hbyZomw zxjV0Z`tD1kX!_#acYS8!!=1(lxY56dUw-3|WpFAWIz2tr9?+y4$7Q<>5V}vMI@Q}r zx~@(khXy7ZQ*Dz^f_g8gl(biZY7lZElg&UyMhl=~Fau@Q^d$$&pO2wz1MT97_C3hH zZQhQ3SIt|uw}EM!15Yo zR_~069o6%ZqDC^l7$C)J$G`X*(mkxULd3NV?GY|eai%KDHOO4`Ox0~`03-dP8);HJ znE1$KqQKcngLrW-sPq>sSg{B0?eKs$vHe(jHNO9%=p<`YoTPHRR~16gA7Aj$L32fi;05kgR=1?5RSEQgfz*?8whoTuC ztpRSnhJoJKa`V!(4ST!v!%)wHcS<^~uX(zNz-*`$gmfff9rMf)#ys)COqxJa{IcrU&wQSd$uY z2{5cc0)|jLJESdAMk>~U_aPb)@$?8!59=vddX%eX6b7d8kb$XT&Y-6Izw*xi@|UmR z?JhCB))CXuKunrtpqR1bl*FWotcl>!Mu7(CCx@QC6*H+IjHZPVtkXXsrXN2&jRab1 zKJ97gVf1BBOOM83p^ahv`j?EB9u(L}+I)zsXz3B%J6bx)&*Bm2;w)&1*G@@GbUY_5 zeO=|fwDbtP>{PV$Ek=JjS~|(|v!x{l+e1s=ioH1Lp(R5msO2QjJT0k%8MO2ePy1+z z83kI>>rXi?F_B(c0?MygsI>l zgFrpDi&l zrygRue-2_YY?5H=64N0YfY}+PV46ux%qS3(UVqAoiHYerj zpO9D@e%h1L!^HD~SPGjfrAPnS$Nr-yrHue;Set^4kZvmJNO{zRDcd8(JKou~cgz|` zAqn=5Ug1VI&uraJ!9*>HjGkWI?&~V=l}9&89MR1j6hw&f^x}x;XG=khwTFV(T4Kgc zk2DenK|dS1vdDVxBcJPMilVYng;t+>>R}SS)B}u5>M4$4?8)~oMOID-oDPvRNrHva zZ}ZkcxfIy&ln>i4^IXUR#17{409Jc`f;O-JxQ7|gw1ujE%>5j(kO0Q@h|(sch?*-s zkCjbc$EuvCJ)CZX{k&Xn=a_z@`TQB#Mowa<;Fh_HO7xWRxZ%ziY~HwAbItN@O@^#C4{6(h z5Ev|!H>d<%zM%Yhl)NYP+gWh5vq&E5zqMo*HWCNs{8|f(%4EGX~T1d;V9#*J> z12S*S@I1z52AS_>$~M*V`a`L0)9jAb)`NsPAbtAQlHS$^dTu68-1*Yi4y6i5ccLwzA4d4{)qj5akaudegCNbU;Su_tS4czYF)yq45(fy8{LRs1=vtbA1 zQbMu4Xf`%C0##^gLMXS$buF@1_Oxrd;O_P!)hL&+UiJlAvQ~@3S}pEbmmAsf8dkff z!lHcPhgfFye41reRk62kdCbH<_+D!jLv)5hS?D6Meg*$ik*_cYr zf733!nGWjW$Z3={p!VcAr|HD2YG?19X3H<61?WK!Q&d=5(*c!#Hq$E&{&W$>*ee)w zNGunluh$bGdRsr+OTGT_yVcIZM=hWB+#c!yk%Oq_?8zbM-%pqvkbfmPNHYPdWMR%E zhE}h0n{5JR>;{0&;oLr(Ku+sli86>4xzG)hP~;~%tI$pHAD$p(=tu_(+tBSP!!~sC zZBhp75GVsJ%~6J876_W2aE%d0K%G9qz{;CR7>(J4!M29J=*?O9o0Xh!27mLZ2_qrw z>oP36v6EkkV-8X?8$+8_Qx?!2@S$wkWE}ItPOqubZ%0XZ&b+7+-)EauFfR~GGB0Xj zF9aL6;$S1>HI6S~BMj0(J|X?--_C)R;kVDC0kXx1(EbhD`Zc2kc5$L3)LNhIA8=d_ zSlcmdzPN zg2Tkfua>2g17M;S?z$BQ@JlcIT;FP&J5)o_m@wNvX?-<`tAf#q$WDL}7-b`| z)em5>G>TH!_&+&03LQ z-_zPuB+p$=H49-|(B55ffPMIB4p>R|Rc8ZbFebUc5F7l+(=}0!SA&CO_`^Xm2{~)B zneT10SS(08f#vbYWSLTxl>1`&MXXMUlyeM%mPgpG#k;_M)g8}Bgp@#D%W$c?u&he0 z+Q7vYLhq6juSCtw^F>DXo1?>HRj2I$-PCnbkxFsnEonwZlNH zfn=_M7`tzMB83+j1J`_9o+9~wqr2eUc+6_We;~b8$7K~C*ALI^4FK-7O7REezg{if zgMpHVscnir4-;(xbJg-(E9l!IOa5OmJJfB)Rnj6JNE8jns;j)-0Z1erssKrA;))Z7 zBq5#ru{PbV%Td4U5-_@qL!w^1*0D1XIpS~Z4sE6ZNV0`U3^eK&74VyQmu9c^f0hckkX=ss6EU{WJI#dGV zaNcK3Q!Yh=r)8q2@JGJmmQrhU)CJ;CXJR}QCma)|O{h9CMLOWBNcH4aKAX+@%t~i- zs8I&({cLW}u9gLWg9Z-f{!S+=niAAW!!CZ~rOLgwt;vi5@Hr_S%fHKDR!lJ9V)U$b z=1Vi+2$Vkx2cU(c2zqAMqt!+zHusn-kvT}23I6JE0Gd!qd|nGF*G}~=F&bnAlXXX* z+0<=ToRX?@(A+UYAm<5|(Xa*KLn@erj~&vBf^?d5(8&71D|&CcHg{DAZGR40qI1wv zfg0U1oP)+1N?d`D+EdF#3K7o%s`kZ#JiX_j@xb7k_$E_-4jOpae*!{XCm__z6A(@b z5^2nqEv_9|u!_EO(2_HrgH{nouX7HXumQYf>D7?VL6bhl-2UomF6Xpm205pbDx93l ziuKZD=azF4ccTO-xa3ai=?n|m0$$94@8uyOlATomXa`*gklJIDkzi)R@jN{(g<}H; zgN`>;o$tV5A|HSP9_L{q->h_xu5`KSBGOV?&J=NdLaJ9jj1n-GUZ1vpznXA*mW1L_9ag85d!?;< zL5Csil!F?@_qm^LGgpwpb~Y$mq-J_58p!rq-UyzGnmb#0K>QNRy!4JZt7E{945W*r z6(7c(+gpL@jIThD!CMj9in>06O7Sppb1csR{+N^!KUEpF?EruJ%EnaIvj<4;!9@o6 z)At@hg8?{8Um1$e03hruL)NhWqAc@!SvO-aPvgX?T$oC4HV{welpq0oT} z$vCiV&Y7y-lv`9fM$ApE&NesQv*XTy}d57xpbJGTLEuID+Z?^eU=@MryG_5)lb zIK36=>UYewFX62R?#A|D=z-)rm$4kWgc<3r><76V)!}%pKYyBw%T;jU8D`;wa~%Ne z?ZO~0Eh0KS{pckoH&x1cTDq*|(({nRf@MmI{ebD!>=%P6d1=42{0^Q3{J*eQ9;c}M zg@IZ;mD-jdzE>qb+oRqc6gwJ6Oog*lYr^N5|H(9hgzVTk+#|LDmD`#=DPedjyi54g zkp?edz%luSwkwTWPpzz?gco^3xJcWCgMgGe`NrPMHW68M{HJ6KVC?F2GY#rg6W^ts z^<`m7ywsnz`qpt?JNW4jUmDrJM=v?bgIsEP_Q&qDMc6B2Qua;xPCoFVy#DFC@9GTn z?$=2I3TVGhRT(U#ZoGyw4Fb)ca<2J|xuWc9u6fT~$23yy7KwSrN#(B%?7B85yRL~{*J+cq zmU3qiZdTYeyJy9&IT79OETR~89bVMVBCE~2V%Ovrh+Pk=irDp_t3EiJUBdz`Aw$?| ztdgV!nDj?}692VxW&f=G#J2t(1BB$J9okV6vPj(#vIyEd3(i89mhfolVO(oSKL_Ra zk^Ndn3ThFifhSy|%o=H(m|u<2spM?116m*SniWPr*;Qbowk>9x#g%4CTDO1JZyZW` zq%AeIg^&p~Iw;|04I#18`+can+db;V!Uy$wOApcgeuMkc&ZCcY3KoH!Xzb!sG^ z>|v-U&~`|-s5^QI=@y=R+^zH#0ss&_FP*sW6CushrCY#0Ov1xH)gd<<)S(q`G_MEk z+X+~H96E2F;lM1U^xvf4I*DG~g9f}COF}C*$h`AimMfa}?u|>`-Z-_Q^n=`0W@jAR zjNLXMZSq%=sKOfZPD{soBJ?1)HCFdIL+YVx}CU*DOwm2Exj0JNd021K2;WNKI zlBcNWV`^CAr8)LiamB{LN;?@*O}gBNSoc0wMtx{I}lqb?x&-QaxXJ&{Hd&oj>UctPZRS;J~0)K zCHKer5Cucif_z3$@sHL-xvwBCty?E?DLZo_i`#;F3goaJkd&++0cmcB9kKf#T{6YdmkwviTY+6>B=9)dxsr^}+nG`sQw}xC>Ol zF$lGY2d_(h%peOX+cyFbr=AeDh@2YYm-nd#ogX3z56fR8(IcPCe7LW4kCobCO@4V5 z>eea>O^&KBxl=nVsn%Q#O~)ND;Bagp#T{X6_V_pd?7gpt{c~VLh_)jn8G+HRATTW#hA|9TM-Hbfk_*U_;PZ7k$_6p` zgp2lB%Q6qtiX3Yj=s?~&GHqpf*@czRUFI&M?vlI97{l_7XfBK{Z)IC)uEpkL%|RFWs54<3-I%vaf+weSTSlF~9%D7nk(4r;DIMb;s zjXe+sDuK(^I0ZdipAYuXL*59VIk8OTK}Qtkm@urF{-=+B;0L-`6YH3_?9GCdwG*Kw z_6W?kc6ru`G;Cb1Q7Y0XTeQ_wV`SvY|LL^Ky4HK2j zHEIA~dUCbV04$-x;0}nK$KJwm1pc&S4#L=3?l^nagOO<`bobXc`9vn4K{hSMth84b z|9DvRYE~G`UuaatR1&PJxA$5}U0aA0>FmQYmWWY=H<7tEvs!rVKOfC2pT4_O>$R66 z2X@&JvLS$G&1?v|lFTNU=(^R7>648P-kk<;I2&W45^~)TX~45OmnmUQVN6u)N7tCB z$N}3yiYsgq%Nj6+kz%z}`hM`_S|`|b7!yEE#su-1oWoj_18mhT*NtO;l;Gr#DfoEG zM^{3GELLP;i8j=$Ug0e0(y8{ty6H6 zhVsV5BlW3?>0_V$*r&H1ocKmMb&moc9~>9z5q>PbZr>@?7e$;vA_JZ$r9gtm1%afq zR~`6-j+XMn7KfPvNvAqsum&e20SE~s^58(@9s+buNYL3)-Wi;b*ntv9914dx3T**} zK!j|#KzJzI4C@WpyLojKYTlvq>mEsW>R6URxkvNLz0M$IF$dwd!Xf-~Glp)GNqdHCBF1iE%$htww?la`O0J;UI4EJg?ac{;)vh;Za z2=Z{XN;-(VlFlcoARg+7ol~Sh9#JD$03$N7A<0PR6ua}!W;z0tk3~wVJ4*=Dv2?pb zhjIke#o(1n2z3}EY@9-lDDr8JF7j4Or&JTxG~k?41I{UBa)e^!NO(~mLaWWY&MBp+ z0Zu8ZluoGud{gpi>G(LOB%1qE+``zZlPt5IC5VIm6M>$b3A+KyuNP*a(|u-7*Y@$v zoGzIN2Gui9cgE>HHgmq#B$_im2~#9iXlp%&z-|fa2Ee*BA8_Ar;G#QJSShv+>l6gD z(CxA0*!jQM4HCUVB0JuPN)2sH`HsKyy`Wi}CXq4#+vaZ3%?m+5Irg34zXzf|obQe&ha;n+FIE^x8u;eaxXaf&a#Q z=zAvO*A<7~-Sy;ET_%aa%I~t3E3aF;=XQDNz=4TN4&B41^ zb4VL$4AL*Pfu*VoE2t}l6)=s| z0D&b`E$2s+rlr7=J~m4!nw$leWJIBrSq?sN3i8hKG&^i;i>72OIgQtiqr3!K}3K5~=O6*B_IK-7;`XL0te2&N3mc@uA znC}x%TwIA7oL6aw$;o-^>!{m3#4>IB>r9*y4vo9e(kXDrI=p?re5=+YzJ*te=d_WE|Q_=CaBd5jNQnuuFPuvLV*H zs;@*VKVMk@?ywPx)lTW#!~3Y(yepdw43kYZpenM-EU$}02ULBAk&-GN*h3J<41da-q+>(nN=wvoItOoG{eyf_|Rnk#3K2TNKp302!+W8|393x=M%oP1*$4ZehSG!aFoK*C207;N9bfNUyI4h~jAAYv zhqdS~bDeI@HOfc5gWVR;(D*-9>dt8>$D#LAXpiEzp=Y9+v0&0O z+c;K2iX%Qem5GB}x-~UaRl_BBU->`glDGHq2R?;e@#aGagVOJ^<0ljb`43{_8 zCCa~4XFVEzfb+xA^B+dfuK}W(=Ni)UrS*{g zhxmy)jXQN%_;14(;fH#H6ycA9^q=Z1XM3GJlEy5-rh_4chG@~a&i?TkHQb(vUTbRe z&uRa(^M)PLPrP^8|1u?lF7=CMtXY0@1iW^E4ZL>w8;bv-VdR{8q(zq?Wh1ZgI%>!{ zSn~6l!qLY_q?DoqJFPi81ltLtgw$)rsBOF`_@=CV`L93tU%!MRirUyTbA%pnHo|k8 z*{)7g28rGFsp^oq(5fb@(LYDq)K_t`-R|{vi&@fCqd&+jL?ei-cj-cvo0P`K-mV3cKl$JGy>|AqMqca%%Y&BU0_I(!)9w){<%hkpaH)wlw4$+@YbQYt*ZV7r;*l75pxsN z$a&5&XxSd^!svhB0^2Q<3I>8U-dd)d1smu`-y=k$Bo8I`s)V82TiQa*^eipj_7m%Y znMjX*l-Dqo{_;aywep}27{Xw(;SL%VN{D8c?Lry0p24qM^d3V*I~)vhuvU~1YAQA! z!MdB0gS5vPU;LJ=r(R2B;AS$I+t&bT7xhH1S(f@>yUBNf1W<*H_!ML!t)V#fc5qL~jkUKNe1*p6q94977Ojuv`Z}N|s$mgTM2R?=@M;+CwU?6kc%(fU7ki}Q98t*oeForjQV!j)$(CaORgD5NHoz;NP{AzK+L(8UbIBE zkz*;m-=(Fdn&eAzU{EhDskWn~=4ok(*p<;z&-+G80kfD>n@_$m+(w|B|6;U6$!I9~ z2P-kBK?da606UKho0!WJ5*?o~UZIbVX?{bc7lG6%Fdu?w1%l{Uiimj;nh*yc_Y6K8 zcm{9hK+vF(@R5mQn@%{|jYXh?x!edtqS~0l--d7_goYw$+~=53bTLAsc1t0r z5O9reAyKATRBkDfTa8qcTR5OVWJpf4V@rn+ERH!1=FmJeA;gAD8grh{cZM_`dxMh$ zTe`#HFd?GRv5Mk37N2s}z%RgS2`unzMA~bYpT4EOh9EC}F)Oc&saZQ>Z{X3U zI}CX?{K0z@XjUMMLzy_6KE@d){mk|WE^ z|DZC=Ve~es>QY~TW0(Zu#3n&0@|_dvcmYoTpw^1kX37VvE=6dS{W9T@+s_Bp&{)vM z!~jvjBu2!IxuARLGU_h5yNm_B$pI3%(adh6xu*Ck7uHrDRrP?dzCfze1B|R6?D!YNMlVO#zXUa}IlpRN<(WHp8AK_iF z=%5f!lp}YveodS5aX9YOdn^~@MvkQP&TV;e5OAX~`E`O~6jI$OTa%iGP<*Xf)vzFm$0}ThQsap%VTUUgQ zT524Gma~@1xW}w5a2m&e>s~W%1U#frM>9*+P?v1P?I8QCVdQ$hwn|?>0Xpwt&DscQ zi#*N+^K2s*AVU84gEC+k+e2K97U%X$1w3NW<$JBIi8gvile8k~yDH^lK5?h%_#vbF z15HsuMqZEn>w>bc5!O_6BlKWmPo*|)&Ws=+Trehxh_!U?wsf)ry_oC3gfD|~kQT7o zK)dKV3wuvxc>`^+T2-gB+1i$j57V{=OKrYK0rzWg{b}%j@0n zvD)sSKTtf^h4E<}Z9D+r2FVSRXbPS=Te%7FPS}~`PQN#yfq7W!_edzUCsVmcz0U)& z^7t|i@dfX-%!~r2=`-(9M_*ubv0&~KfYWB+^(WF0PMtO5Pkfn?d7~9-%h-`-JuBxX>OJfPN3C*hR2B|lq1TYV;}8X9 zpSG03W3jtg4W2fM$F-%@N*j@#*fw)Mk7q=NN!=06HNn%4M=M_@5Pd(x5=;RcPnQhK zf1jom;+}j^wm+Txr|$9oYvpGLz3D&uLZ>S_eNm?jC!q2QzuC)94kksN_H@!P*kt^Q zKb;!v3ee#0W4Ev+RuksoP+Ed|8z(Jq1OK+NfD}+xEsmey%sQm89guSquaF>41*v^} zt5fFI|J9HR_efaUsazrF08K}i?UKuw zqH4QM(&C!gGh3gTDW|@Ttbd7=8XX#3v>6DoVS;A1*ucZZ+Xp*XVB$(B(v><&o}3LTF2pi@uBiX3)t&HA=uAB!L}P2>j6(uEdF1002# zR7t#5&PQT5&E{UN z=-^O^n$b2HX67D&5POcGRl=v3ZR_*ZYB%NT2^e8Mr?51`h>F=4;t@WueuYMy_fEM= zz58;d7GT=*p=|BOMIzYlT%Wk5Lj>ybNKCyfF)=sWzWeAgb1KAL`|fsU@|ux=!0rCm zW6aIUgu$Ea%C7kC%JFxK89jCOigNzei8fghA8Y5*-1!G8Q-|b zgisO7!CP!-b%MFafb5-7ZX>Z7<;I;|7JZo0Q$8baO1Vk78}v(W?`mGj9P>h013sr^ zlsju{lJ9dFskP!Do`#n~sIpwdu1?<{xLnxXoAA@OfhbN=;OS|h#9PQg3N9jA>h zj@t%Q`#2y;{RAqbkm(p}SO2l`wtdK6WG0Onbr~@ZLN*{Yc7Z#5HEI)?;#;jul1V-5 zd-tRzD`i=`RqLWGStxVnciQ&LCX|aNT;_Lb^Jbk0G^rMK9F-xz^OT!6btGmRrlI`K zK~<62Vh5KqRKk1W*ou24zzjfHZf%S zbOx3^;V1~CO(RH~+;5AmBPH!<7u;#iCS+Tz zXIXev9n0!&jrBMtOj%T*;Rx4R0I8n0;!YYC4K>D$A;m z+@}`!^dm3eOf`42xL}A_x+pOrvYK2ZrU>%TMZR$oQx5efD=1n;fJFv3+YvBJb?y<~ zArs_kPnI53Rr*|Nia6OjHGCylZ`a^?8QW=L9R;#v?qNT!=rsB_M9LS$nfmMsQjU#m zK1R-FHSsg-M1O*_AVc^~!Al%MJK89S?HI0%8vz}HPZa}H3bA>0B)rmb~r-dHMX=}~~2ZE%~CyYYngX+>f=53y&zOoM2G9Wcz2e#Wu zZG`Pk?jbCNL3LvDO=j+HQ%nY>3`Z(Hk=exqz|r)>jx zJTRI*3&xZ{1-p}U#j_e^Z;O}N+98MdwyffJ9B2bJaBn^`%~0AS4e4`Enjwej=|U6Q zG{XsduBErf=zdI-no);lXdvfAnlw|W#ud8e84OQsvK3H`%>SeAODBToTAQy&rwg24 z5yyWFE`dNC*65BVPdx~2;`+vg+rQ%+j+HhQ{0BiI>?F;>zU#p8UGje#9K#`m^tr0H z&#@VOGWgJ$ZLhY@EdJm=@Re%4D)yFJFFz#K%eEBFHKbK%9MWj-kZ=Z@K@!05R1C?s z$(;g9>}38zlIZY}Iqb{p&-%!e+{`Kno?YV-@fJL0UlIlbsJ8PTRJ{9q=-z*NqpGiYSXQUDnvG17uEUWB zARA~4=j@5HK}n?nbC-~qbb*OT1S-N}h5LOcxY!XNE}!~zz@nN1Rs~@3ksomJ zbiiU8cf|p#G7DJt@gHU#fWk1F*@W+Z9Vv*=v;I1g zt$SVHmH-b+0p>*+0!(ZSFcW2ROt>xyY#;la@pU9#GFrB8B8k3T9NWH*tS=N zsY%xsAAX;?lDI|cYXcy=0%o*A8y0UOdhEz5WPCMY2X2?Xwq2S{Iv}{oDev;@?Fh#j zsuUTFzy!^Z3_;tZ32-biUb*%-)O=!mRT)BT0d*-(RU~Io+1nmsw!CjSWV3U@bQEZ< zn7YOi(1A)wxj);MfzKce0f{}kV;Q~C6~zMr5-?O~Obar|hfz59K-xmB_`e20wiDzQ zNEkdf9lJF0|F-cK?~{X-O7oS+E{}Cu9Xe)sX?$ir(?5RcGY1b&On-CLlqEIM)K%j; zgE5aeWO4Y#DbX(*fi#UO`$*_e8pG=fk@um$$iD3!g~z() zjK0x{H`-9$-ib!<+9vSb?#ec87O&QE>!u{z%BHL=3z~IC0GX3|Y@R@d6@L_Ivqn`p zpTe^F{wLWH3jn>2<%;yxwc2Exyw#zKBDL^IiN29XZ3R-W^W{F$9Lo-3>+eVl^JU>CVA8+wH;)nQ|vqr2dE zBhtfQ@#ta|R_`Zx0+VF#4XM(jSgRJbUrd>k_t$nB%fb1@^2RL3F9zSi4B7!tYb@{7 zhF)D%Y2x_0R5!E0J&pZUTohOQ}Uyz-;GGT&J)(1g;b zA(*jfe4T#D(yA6Gpq|R)`BC%|E31XF+9dhNs)3HFuj5p)1^9zVlaUV1W$y%Yu{jv6 zc2BZ_DW=EwYK&+bt~Ykeo{OJctm{KB=En&2A!HIBuQsH94{v+yXT%`(;2}za80cda z*Az`2zZ?8-aeWuB&1M|0C$mP7nktpb{@g@k)`vv zJ1EZoM~;V_n~s*e_03}VR~7Slcf?_WxUY^3 z6C;NE4i&$vw+`^uEMl3b5Z&(L3wmL{rfS5ZEpwh&PU^|NCqyi}iZ7~>y&{%*mk;eK zzNFh*y2P@3@MdI+?=zow#n4W6q3{Z&Y%7xC!9F{oKJ)X$GBO9Tj1=D%u?Xnx;o|qy z<8DtZcw*)JV#6uqg2;trL@9%!DdOTT!R4ZXCun6jM!(0sZ{v54Cpt#T#X zOS(V1IMiOM6>)s)vY|_g#Z;Q~tF#ym4r!{d^{{J_D~s74?6$I$A~xk9N~7Xot{K+{ zxoRuqk6}2?=gPLI_5!YKglh8@TzkH}+r((Ggbug2^2HAHnY6h^zEBs$@@2&+k8hfb zt}iQ=^5`v0H|PZugM}ySON$rtY@6td{X?{+Ey`~!E~0R2mj++L?Tx%MblKpgZte3S zMj-+JTR-!Wd%kh+6gsxlJ+{&n#d6->!fWV!muTO}4QjI}R#3dT+vZAcHxc@9#QgH& zMcn_OBW7M+$K4MwS17_ZcA(T1=TLTous*(}_-mu_ChOfEhMpITn5&AYU!x7Nj+(M} z#{pOi1SNJ{dSJc0cm{3#yIx>9LGtRMmlrvY-spgZB)MW}eDIRqffVnv)4CVSDY{tb z|AN8ii&j{JqPT#%*8y66cXi~7p-Tr}?&M0o%z^9ev%oc8bg1(>UISdnF{^p`(0K7o zim&a0YZbSz1w5Iq_I+^SH)-U1HpJ1P#&&zw5-v#*63C#;aHbpng6-U)jo+ z=EEO?&`6e$_S_yV)>8fo!9RfenS&j@wpe}gX3vA-IlK#Bj*7!%?AFk&!3{j6>>jIh z;_HSkF4ph@0&T`@^5MJNONud`U8%~45bW4}*mosxG_0eYl>lz(T{kXg#!JgbH}dh= zNK`9JqAbmq*R$6P+HQhd>5s1^m8j=%V7eJigqqCVZX6x!hjHtyqs7LVoX{3t-vC zn#1FROPty~K^T(P3wIe8fi8joV4sTyU)CxTE-!7FQg(66>Dw=96+@I>yIkNdFLaj|v`lnAzh#_yZObI^`E2l@!t+|js@J%?^IFEK&u!UC zKi6GWw~R54xyy50;j`V{vs%S6T3N+u6I`C@?ufUp=dSJU2*_Q?-7{K+7}7cJZl$|h z(K4R1++CKniZ-t-Z5i_zb$3ARg%mD!cZ*tvuM1m+$aR6c%x@Vj&ubaUWbQK3DxS?N z!!5(Rp;n<49c&wi9TUQsJ0`EOckCYB z%tR%wf?uqrIvA-sm4mNhgh|mfQca4M{G5FAc(9#z{hq1(o*bMezb932Tt<3l;ThlY z`LiTtD5cnfRQH~tQX(eieLRV@Ao&8Fxe95>ey#67>963VJ;exV0d6hxl(3BV%tGkZ z!U9T|v1(=o&CoQ*#_Tv{LsS{Vl^cBcHgAPu#zb5lo7AfBnn_(?lfv(<=m#rx8UT9~ zu$9v3k3tk+)vy+7lSN_>uACi&i4yt{l5Z9aYu%H21oi&uRp6;yy-MtHIAs{6$SA94 zzE?cX3Um=@Qsm3NJD1*rNu}cjJ@$e&_5B!Ck43lay(rR`d_e?aL_3PUFZs$j7QUJr zzT}H%h*CM#)#iRu4p+T)NWv8tTJ z;~M?Q*^HieLu}E}Q;gmsKtg1X(jpogeZ@wPZKctZeQ)kXUSmV<2NNJI!emVc!dnFZGd_>Wgh#o9{s`Oo%&=rG(itA->&@eJJ~*(8r^f%;B^2 zk^WA_xh-=SwszjjI^o=NpmjV*?!3iRtqiZEGN<)!r`*Bns6=!AHPMzHd1i=`Um*U z@peBgC&2*jNIZ?GS9mL2eo%pv((;4CvT)>6_h*lVIHYy9XkA@C>4-<}fRcE0rChe6;U_6=u`tdrFII?`9o5;_^T{ob! zmv94Uf%;GU?OQX_gu82|SVDI_%nY(jSGn}^1vAW`68;JH4{t<%q( z1p|->Idx~x0FGj>R`Vg5A&c{&@=>n^(^CNh{zC|9_1sx}ltY`Stg1BBUsVv2Xb?6j z0)SW5X8-SX#Tq1(drW)N68FVWGCA#=Cp~Be-s_N1F7hGBzl#JH@@r=iJ`R18ig>Mz zO^TIebQ7=W87F4jE7$6K9{Io z<(o3k1*ciho_Ml_vPD1{Cpun?nExJ?>u;-6Q{u{4TnFd)idAs7oyC`|q^Q9u@Q0vs z-i4U*G8(@F+}mlF#Y1=>z%5|UyLuB5$^~DjmKwx4TGH$`6aL3+a8#^s_2_0-&P?`&1sxu#8@7b zsMlx2q)PIzo#q_R9yXlw99&h77+f~a-e@^7nN9W5 zBVmYYD1!t0FLr&v?fe7{or0F0swv`bzBNeI_H5dh*s&6*g&Ps)<`-QLf zq(arh!k;1)s?NfbDkzh$Hx;U0rb4Mvr9xG;7$JklQlVT)Zz@#ZnFOqe=aD`Y>TlDU zzAonuinisEzRl^mg057|v<2t=r>a7sX;=5U^Lmx%MS7e6WW;-Zlo%M}vpv85gf0J~N!n)Lix6Mq zpgtpF)r3>85qsFTxA>^qL6zx05ot(Di9a9@jZs}Apw+MiZHaqA^!AdpBljh~8g$mZ z(eYGkg?QT6KxT~jX?4kSs1=z^f-*VN z8y_1-ZPN;t$9dag*XP>s0SwRbNN-CNoOz@-`vt5ch^uz`Bfa%`GFw;npUz%v3TG{L zJ*?qZdKX*USD9|_Vh{Aah*n#-#kOjwdM8umdm$)_Z`f+f;jfkQ2OM{_iK1L>Qn<9t zF1g$?d)#WDt0*4<(d z$dpIju{U23ZWA|psIi2 zyCyd4u_D*8SSAC*$2HJ5?G!s@So3Q%ETL|4-Joyk97n00e7J<)%{wx$1o zA-1uf^;_Y50}XTA>;je-1o2M6c5O(UXchL{G!i zA$lm=V5ZK|!?EiOE=dBNqgT^8dT2AS3!S6Ko60)mGgF!4PQZk@79S7!)Kk54GHjhN$YTU7J&djM=VkHL$2G+_7RmtD3M6haw&=s zJ_LkT8OhF5r~=qRMUX2mc5^pbj>vqhi2_@CEs|zaZKIAM0Yu#{)jxswE$~Gzd~f#m z(AR!t#B!l4O80!UO!ut3Bz_sK0(2-|ap$Rh%-#|}i|NXhdyBsn=_!=}w79_IVYN7K zl0~4mDm!7quNC2!3KsInP=i$ijM|54^1od%?-(b35*t9P-xT|beRE+t>?>0xOuYe|zM-rk zXs%#TF|SXd4a#ij9QD`KnQS40D%I>@3~Dfgzk6o=!}EXBq-mttdB z3a|qFCP&tsvE>?=PQnsruh+u$zRkihF`#WMOV+Qj9b3|YOepT~UMRi-Zw-dOK_Ej8 zxb?wEFQmlQKN zJ1HihT|mIryQ^_LRoL;iiu^KM{+K&-O@tibkKGhi<%{9Zu&XPwTwIFm>Wb?AExWp+ zhA*WUWnBD=bxMZUz603pVpU*Qv$YVjyWb}*hS|=AQUB^oU}6-oM^@LS2UDj4O)VW zaIBnM9Yq%!nvUt*Ud*J#6v(*1rzj6bd5?Qiu8?p~Fi&slNC#A?^;|BtRvX#xD@#Hh z44xmGdSW*^D_LrY`Frc=Q0)o|B+8BU>ec1wtmw|u=AE_%OquYGrH zj@l+oE8gW-k;~7W`a)0M*NbaS)+;-^2Hk~^Pa)DO2seEL60d>t^_t$$t9t@Imzv2; zaB)*bd-^uRX+wWC?+7+=*!$)18J}R0lYvIC+Tu7vWv2mgTI>g#$}!mOx||889U@HbtDbmD?}Q6`q;{(E21ZM_~*miV= zpixx}et@8nwz$emA%}nq8p(SKlU&X^2B$!W;V}@)+{k_^ZXK=E$Q4pdF&ulXsD+n6 zLifjkpIv@exeLR~P^;?Z=rM_P629T)i{+L>r5p%2;pC!Qsi=M6eU>}hN-SzL!xAk#B z$L=3KnS(x#j%sk2qG(h&(1HYaowYTj1z~)&g%5{+?7I1A7(#G+_^_0LV`sARDay@QG&Y z;UBb+eh4&;l>6h`&W#GoD2WJu1)i4cQ+ zqEUS@=u<}ZKl@~mlS$Yy*5P|H=k=$naZQJiNP0mqV!#(gh=bVtClubebnu6d%)Wu)=-F)()RwAPv$4$n`3I%XW&1-H(`f;dm5&=aU(q zhG9h7yzUt{hqQ#(;31UCH!8pYk@do82HNi};~AdeTrcZ{1l3VS;+S`8W7f$dYZuZ& zB!@Fbgeb?sr)RmljJeBNJ(EFcZ(i&QH@M42G*7y^-i8i1KESBu`sVW?<`R^nTjL=2 z1M}xg)uA_cP*%;nWOe?AS6fdCVVgoDyZn2Supj3M zLuUk*`A!DdI@aU0=$A&IQTI5e@17He{hN??=#(K6$2ZxoN*XrlA#+ z7-Mz7-B8;kppN@f77@)Mr6h6n3%TbEXzOYNB?=e2UiFd1P~%~K`YOZ=eU;+t`YD^( zYVV;o(YH{W*k`Cs^e@yVirLelG>t-8Ail?A-8w)n)ZzQ3J#_?^;$*kZgWWn0b?ZFb zSLYGcd9+*STirU-N!ib#q_>~Ltax#ReS>~%M|si1?^w6abUG>T>-4mT`th=j;t-Em zW`X%|_veu`bW$AcmLBW=Jl_3zfA?LEf(nh1*cN_1s29LKy1?QJr4M(j@$iw9r(d_H zw&dUHzBnE7g=|l{udd4>V%?he5yiZZhPYZ(9IKQtIL=Rx5%;SZ;jYy=p*knKbsp^2 zd8k|G;j)fMDNN#i?7P(I)uyDyKCkV>BOko)YnJpe?S6*i9HL~L-ju$CZkGLgtPen& z$nC1ak-n)p=Jh=B#`dH|sde8tb93 z6Ge)*eGG{wsj2VxT9z0@Z;mO6zF$^R7v9}p0QFBC)Pz0Il|fC=PCe98zTh|m(y1cz z#8%kdtsErl$V~@w4b%BtcPZV^u*Z#cV|U0eu!J+)opUT&!PAZs$V|D4Uw(BtgEWaJhZVnz`!xXvi)v%+1yW-4Tq?VP-Iv3i!9sB_Bah< z_87EmryWDWp#c+7JMLLJw_HH%>_>&RVWZLk$TP>qUKJ%r{{QT~4}e`&b?<-Ax%bYU znR_R>K+tGV&s?b;Td8SFkya`>QxFg>h+^q`_WfvI?T?pWB{Wc4kqkm<{MV-b4QT5S zPy&jgqK){6QBhG*qk>|gHd=~7t)^8ARi1gD?^=7Gd(NFZlaK`8!$NfK`Lp*vd#}Cz z@3q$^I)%?@F6Pk8H5bGrM4&3mhWNe%m3Mb~o+N=2E;bCD%=I8R!8KsS&Jyw=D4fzh z<%57hM$q1R*PU-a)kxj0B5M<1N4m2BcA#bTX>WT3aQCakSwUn{m|^b4+Wl? zIU>E@s8rsSdP$~8am0r+aD1wE>exwpnb2G=k+f1rA;{FBsye=0uo8PB+Bo<|Mj{`P zN@{v3x;0mw)!n}*j@cqG_xu!}8|yz)*s#)&!Sx-@UJ9CMa*3;xDy^V8pbGx@3~r4x zD8vHJaROa*KClUlb@pWf-~YfT@U}YV2UMa_#J=_uIV-wh2st;LoE3xNH~L6V0FSnk$_jVBATfKmz8Pkc~MhbF{(U zvs}68VKbpDTWh_L5$3Y$w7O_rWLm`m3%Yc*G}cjoYnd)VET>DYH|S~+y1XGytF0hP zX<7JmEeCn~mWSrFE;IVmL^7=+!-h>$ePBM(2z)-x38Gc4FrOE!%@xX5ollS=Fj|EV zmB`ct`U9KJP-S0c^8*ijHve-Jj~=*%CBez1Q&c))xDo z+kWNmVHoc9_q?^mzULNt`Foh}d;L9cZL#lda5J}rFN(U~Tm1Ca7W*Eav;ln4&Z0g1 zp2(sfG&W>wM4wXpJB#(iliv8YawFBF@S>=1EO_pQF4Ye$TiFRL4J}(CdGP+&vX#>; zD_4PL?K|EPf`Jshq}6MzY2w*%xPClI;u&hJwu)x0Gk(f3@(x*K%09x>{WW|U6*uH2 z2uWDNt~oIyaU?%>VVLIxAB=2PaTYu$!>tK-?rb| zA3`a@_4hnPvV|mGh{xN>3&j|cHVSEfv!2-c$DFT!&^rZi z#@_jsH`IxrrajDfVHS+9fC!^z+(futmN53Mvfp)UMsmEQimlyXLkldxtIAWdvPm3U zW9t`jlh-Fc>_M<;S#o*+Y^I{?TO|-nTeMT8I>rbUrr0x+98~J7{#7$x)Xb-;DCLO; zO$n7vF8dp1t7sBCX3(?3FshbV&t^8L#K_*t-_ z-J_?@Z8xjR`lWsz=Fye{l(F36JyEk_w&7m*d^&5jhR(Q=gwD3|r9S^jKw~KJ>|C{i&(S{aM9;g-lY0?>Uaf8dV%!xfqHvpgxAaUrNFthOUeVx^f<7m2ZWde z0w~P5f_ge}57ZMinyrtCZ<}Cb0`ej9)KQ~bMK+gy6=aK6yO0fn+Lb!z%oag3ONTJ% zkpQ&}V@z)g6jDJggJcMqFbo(y8+IMnz)L9OJqjojZmdGrQD#{gW%?s3P^L5@Lm4bs zLz(zIpbYl58d{94#Tr$V(OvLp%;##bh=4L2gnPp&q5FLWC(g1V$UH zBvDiMHDXVE!`Eixg4FDnw}1QQFTQ_$snz}xE&iGA^QDhiTP3YuNfxyi+*FO?hHqW$ zI;k=rrrmd9c?nn^Q;{OsAieh{sdCKXA_lDWew4aCvPWE7xMp$a@IQ{pB1RXWFp>3M zny+$pO~3tQL(>$n>_icY>p8=?_A%3Nbb?`wZ8tTq<&PKd$$kn&wuyy4DCsw z7VTf^KQ%X6Lc$%1hFbokR7@dHPt0HOYQvE3jVd;TB_L(APXl00|urL9D(EpcT zPfc>kG|RbUnx0DrTC!X+Q}mC$Kje}D3w$NUQXG&=riJ3oQ>Mal$xu>SBcfdt^Cac` zqmVu`%=0ERCX#D0+xY9XFLam3U|AOIs^PRwQ%`2ej|U z3ca#+c?X*beY0&x)FI)6({SwRD)o)e4Cxzgj?gznE=^k$j&<`i8X|y+{(Ys?zjUyF z6j`u;v^}(c97R&?-)ZU}pBd6WqNS!S$&Naet^?K$Z61fSDj4}Jzav0ElSZ;Jf63~B zlBBS3UXMg_U^I!6(=dJzY zGuOP}^#AX7pL5DHf9sf|j(hsa#~%6Xhaa~72^aJx4?L*-h=Z4{Xgzwg{-{U65T`(7 z5-`7pQ`)F0@wV<-MH;f#|`=R5XQi!;r-*+Dw)#z(zGqg=Wo zTc%M0m^UOGZ`SF2oAOJwQp){O?!U!-|6>DPu|6)*R4mksO=x(ua*qwJ^kF(Oj&7h) zbc%MoGbp%{4xb6O}v^^ zReEuk>hJ|n>%zp&ZL=AAZNi~yoz6E@jkm{6yqM!#I-Rd`PoiXvV3J3NfE~!s~y8@mi80OX)b?y#*m}N6u>t3G#H5^dZ_t z%iTEYgf^jwPSi*62&Vo6DEAm@>BLWo0X?`=qX)++rNjbCs~_1OrYB8clvxg zDGuD?BsP`=P6PWVUXkSASI=)(1BcaCg=hb+XJ7GWQ#?C%;*|-9(sVjs_7APZ5zh%U zoz88#$DL3T_EY&iQF^}U#EDlWoYmFo+)6nU@Z9fI<6lxm9x0byRsZ;_#7;77goI(* z&=Puf?8GGrhm>_XU$C#DV=&C{UcH2rbRZzD_7#MMl^0I+>U2I&@ey^{(QG)ar_=egb)1iT$1@&% zYM|q&bWR@0t0}^W?)k30%4jCFyj^ z$fc)v{XyODM=pJ0{zO{+fPFNw!$p`hF;ytP(1~@YDj-+#~ zzdm8&qJ)#yI#%47^wAu**6Ccw`@ruGK5pWu&Qpb~_;8rqV^!x@DDw`rSn%j0InT?= zFq@ov)#GVr<;wLE8 z`E?2hhP*!ihG21(>K|4!PPab)x*omNn_SmxMj2*|q{d?JY&g|XTevxaL!#?9D6p9VI+#%z49c37X-^f+s;>i#aOwcF zIetP9)dLlRZ#|W!E~lwMk{wy$2zr3DEK{sTRD8R}ILnNz&ko9H>x329W=HYt&9s52 zhjrJEUm>B-)nm@h8*3#I0LJ;8f!gW(os~k%ydZxXw|}c!xmzcs0U~-l^LwwQ-0vFp zknxS1=7{FDqKEo%0SB~pI&Y#Kfk(v;$i_K9LM(DZLI>{CT9GZ0&cp`K3$S)6|3=EU zq%&Q>DX0>vksn^leka^LmoY{d=!Vc1~MkxM| zD*jrVmSx4Xd|VG-LmN81(&xpm%JcH)I_s^|A@c{t5Gv>Eql((SJ~B9kML!f}cUlIQUso(Ajkzlc&x6u_xsBqBJBv zBCGjgj?d-2i4(8Mp}quosOIPN<`Qdj{Dl1S*Qn+MhJOAzy9ACTV}3192~rIHY8;|+ zyWr+DW!P3Sb>7^#rNDng{&_X2W08qrR>F}-Oq1^B5lVOSXShUo7wna%bN>b1Uqsn) z0uyCM9mm73=;2Gj0NXLbKy}HC*ZN$?w9PuhIWWzDiB`lE`Fs+!?j-2=meV<-MJzfFxXh1M8vrJsIT&zJ#E zo&aTa->WrRTq&x!uKPaS&4fIwqEv+yvz&Q`G2bFANuY86zn^lKWOLwq2J&4r>ENAYRYU*78~VMr8OP7p z0hk@mh|PbZ8viZ){%$+}-?~37{QgecEY#<<-HJW6ZRRKEw%XU9({|(Ol(w0F&u-`U zs`AO<_oTKNPaK+UX4$iJyk&=@$W3cMv7MV;@eCba+2LqX`#D|*TXs16GygZW^mH9! z+2L?c)5VWzo0k4t?fiRsIjwUmJ37iU<7bu5t?cM1Pt6rac}hX$C{HPv9OWs!l#@LF zRTVkP(|$S2^E>#5G$n-dJf%i)p654JnFBrT zmlHjugm9v#sW%+yDSUX6 z3FF6y+g{s5?_}HMV5XBpJ380XWCsU({*!vc$)5Jh(Vq64vpr3maJZ-aa=NE6G>-Q) ziNX1v#=kk>(^xhqeA;`C_`Fl?amJ_pa>%DW=af(5fgJN`znt^Q`I{Xb^l3belRiJI z`%c?ARlDu%WxS1b!E;Xg{GxtG+s+VLZKwQ^wi9Txo!_JP>;t)5zxD8&2F!-Z7|D>< z?A8V=o-BYQ`1pGy*syR1JpQRgglgAIuYOpqGr@WWYh=DKBHlOZv2u9#3E0s>9$Z4@ z_)$4K#YN0kl6T;hxQC@8V|{|J@=ewOMkn~$u54hX z?y^d#_ARCls1FR$M(2a}(P<%x6_jFhw(1r(gBXMWR57&C1%^hj;e6;m@SzEq0)G3I z>AXWaHGS62H6}0#-s1rP*G788C4*p*Ktafhp3J#F&bAbgM{MSGWoH7GJJFI4-|%2w z@&n9oVxuJyNw=xxq?TN#{Y(YqD-qV&nVwRb!d~{Hgt9iDAROf-Gwq5~cvz{C{@*o+ z5D(o|h^Pgd^|w8*G9orqe&22@U*9M)L=ZT$h`yCyI6HCzq@N&a@ z@t${5{79YDHel=KzzQ$wd$9``Br!}8orwyhm-Oj+ok6!@Gn4^_^OJKLq~@9!}+hx(rdH& zwcUe&hphy^>t}1P*YdE}OH-}59pDYWu^h8R@}bw>2qj~!+GaZWjBR&Ufsl*r8$njl z0+SQA=auU0%kHkZxN&69x95GV9|>6B)9CF^T-IMOVz?Mji9R7p$R-AFgYRZK)G8#7 zuolhBoe7`y#}__iE^UJ17?I(n4_F+kpCIEI6#D$%xtf220UQU#)FVEEsw zD*{Md5Nk)IU_ZL6=jRvUiXQjg8lp!E6_0F)fXxD2OGRx-MQbd(i)sEY+jGfPDNg(4;uEG#bz z%P)XY44|o;BkT&53m^lwYO%+0JEIbZr~w{lblnfX_VVW;w;LRc2Z?}O!otz+ALz^{ zU0d7_{(^qR<&@yzQKbG( z&kaZ4uwb@!MgA=f0kuUm&%TZc%?4Q{dTo?-15=YY;NG};Jt9X{je%HdJ`IG<^Ia&F zTBFkLM{N#~dA5C}>3r5RhPNdP$2|vwr)UdlasOe|)J~gMo!0{^q$^C|$$b>4D~<-D znzO%0uI>hl$ALcouRKcDAr4jIgyeYG*{3!Y^CjigXG&U-&lJaFqztdcUKv-@W9C6D z39YV^6p*+uom?#mV*2lpCYnQ=zy#jYXgPJugd131FOmi`LVO3FM zk+pkc(jiXVJhnil8Mi6y!s00+Su@(!l%NwXjL ztYk8gW14>Uu{(*`&y?S-)H~%Jw30{p!{hzuWUvk1aT=e1Q>5KFbrV(Rbp2??jC{>L zv%G`Pw<(g62K{mk8gO~i1i2tJB$6SPG7g^3o$MJw7{y#y*h!~zqG?^7Jv~d0ofyLk zUu*_oDX1L_Q`_!lfvl28VH`{<`!;RC0Mq;}hJ4DRU?h{(#ho&K;7(Hv+_+Q26&KB& zfS^c9v_FgS?DHx6x@azd?+=8nM1=#)ibMyLQ`7g2pO3*dYB_nhv1>~T9uh8YHPj)~DHqGY;uIo05k)Mb@arQ?*Hk*ygTj(@mG$FE7*LuWPB(KNkwzi{>*=ra2>i_` z%f=&6wTws9@o$QgSeMMwwd|1~7{!1dtur{{R8%$9ZaLGvy%CjhL6>Y{O5c;}s;3D<`V*SgMb;?ECj&$wI7nqUsn)uEUe^iK?SYaXlJRTpJ$gT%d5# z{=+_e6ed%1mDPwV zvqc-6u!OMLrJQx?D(o6%&hi+a5o-FXO%pFFt6y3TAlQS;;M&81ig!yHz{>7?Tc46! zo7VBfb0O>O!>_yYsvlfS6c-CmO<#DzDc(I=X4Ug8iCD3CqeYhE?5sfCtx5vRCs^nC zOhO1+aLk4W51%7Qte;k9S`VKih})QzPjf?-&k@W)!$<-%?9i(nR;%T^lH>AMk+v$y z?3U>L0%f`EevXGv zEU&&pn;rW zY+2E2-<-v0GR0`+!(){) z`hE?c+f5FF%&YD}4gyMQJzhZteEU!qwA8+BgM%;FYGcSl;J$2lhe4dQy%$#@8^ik7KWqh5duh?)^%rZr!zNL80+b6Q*oJ=<_QIn_W zz+F1e7K8qvS(;Ne1Hpx@uWWH7MoM7O@Zg>EixXQ=D%4|Fvx>FUDwz&OV-|){Taux% z5Rsr&p^oh~APkGaYY|p^7x@6=vJ>N#k3pGSbl!z6J+VG$aay52WOuid(wHvhPT^Zh z4%A>FxkuND_+O!C9gTx+#w)sCB$otnE0aK6YoTBV3kg6SW^Q30`OO@oLSUf>gMe?s zAX}#53PZ&)Q}HU~QRo3E!xWttp;{KTs1;0yrLBK!LDCxmw@E!QJ+uAO*S~e{hE2Ee zn4A)#zmxf)<=eSD%b|0gqn{xvZ*Jzzs_B_qUVi0!tfo+Z>urDW`MaM*Ptbe~V%_Hm zSq`h6%)ljXSkKh{QaJ{J!C8tA)>uHhb+*O8UI3|3Tu4x=aab*jFIYCTH9N5&R%8ty zfgpQA~;E69?Q=|(cW^de!OF!8aM@aIvNT&#!o zcbWZty#1ZBzlYi1U$MVWx#Z&MUE#mh^CsXDiprVQeqm6nc(sc!18&0#=7y`4tGm+< zi$fh2Obr=5r<3OhE(XS=dz^6T@|8*I<^^{_!qZw?m`ku+dM*OsS;ej?vuEvoHny=}>XNt?FFjmp1#d#fjt z6@&PoUlNZ|Gv;Iw85U<<*1=eS>+4|9kCo0)pE8nuqE-CaTbtDjv zVC-_2X9UB;f>cj*`l05$RzR= zwlvt`qn@Kqs5>?k5+8pVl>zW70@AsndUHfdHw0{b2jrvyzC+dS(ns@a5TG4HCXQWs zaUZRwpgpxEN^|rwN*jf)n&nkIqXwu#K%y64(JwT7KFv?18vs0FLOhwrLIxuOm>2v2 zWlHD3m>C>39ExV8cplpiiOFHpW`h%NC20#Adz}Glih1vItXT`+DhO^bzU4B*`S_4; z<)mo0NEU;m(m#EMJuZX;5zVp++3?h03?aE~qWLrW+!a0P6ybh*>!Oo;>5BY_41=Qb z=5hYwmj_?mU-K7F8h9bIMlJn(_4yp;j~@4`kDumGA5(dHU*b=fm!HNS9#no9I=7_s zEcR=x|15S1yk>CHnJ((pPiB-L!gP|=uDxXXtn;(vBI50`+L~)G!InJQF3y)hm9&4* z98*NDz?l$bRgaNYMCTSkLLhRl!5K$B+0(m{i+Up`_gbh)BLp2y5_vRfF;mmy7Z5Ww zI=%k<9?wp7&x&3u7^;bZx)v1#BVcEJE!%f{ZGuF{(d&C1(J>PyZay(xdyZT>8O=|x zX9NstA{)E#0>!E7Ya>}l`+O7dSvxL9(Vcor`0`7xf)K13dz~$@3Uq~9bcLOKmtCFY zZ@pSAPIlDhdOF*LI475qBcz!>IdqI}X`uQCj~Lz18%0BvIbv!|J%1uns0UjcJxYC+ z83j4!O_@=nRy>+fb=r2--{J!iW^P8|buu%G-43u5nNdV=D^hEqcwJyA3iaR*DrOLj z$?O#Hi!Fn9nsSPmBo=13qlYvLS1t~V&DL9a>C(!^z7|M*&9zUBN4I}mk%)kN? zFzea4gdEXol1{c;F3V7TIPp#DshAb%IWjg9cE7HZ^x_+7s~Qko#8?J~y4bmPY#B%> z*_IWZlr#Ayo;1&(ikYe64qZ>_Je4k%V)6ge%~l?Zy*MN*!J7jW)_kyFdtn!NpNN z-ANGE8e$l}yVG#8&R1bU&Gx4o>`G`B3mlacobYK^rWcDa4Ot!7IEE=8cEFJ3laYX` zdb(N`846V>+bPa22()l^v8!RrDp}#p!fHMG{Ni#~?YRd2O@8E33sz%Z5_qLCrqQeN zyhS#bb{7HMqWoO#Im|V$-)OJVba{&(L7dWW z*rB2K8?K-_OqOAjJYC6aEeR@Gp9h+gy@^EDL80${RJ)PTtf$3fmfKdO5h;%4tf-Pk zh@_W1Zv|K{rOCGN-JdoTx*%CfymDEJ+6tLAZMgypw~`_S#-NRk`i){(rGN@};&7Dp zjo(6}P0?Yxah2`HKviZ&Z%jML--3|iw9Cr9nKU^5`; zVnZFci2o}d=L9k%46Kt!X(uE1uXi2B9 zni>V6jSVH(F_GLjz#A(O?6@=)ShY#RMnSMVRy(1f2-a##tASv#K%8JPD++=|HqIwl z;G-Q?2JMVNf%JR~^l3sSrcc;XnOHE(WPwb;4<{3OMT8L1M^V8C^<}y6;SdeU94N3c zY)-?6F_F+%K|;P)vHgxZYkbBRG+Q=Hy~dh+`3vR1B4jat+ilm}rk$VpHM;PrQ!&Jb z6~kn^=C)rrQQx7v$vWHs%7#c5AvZMh_mdbfBa2gm!+9`JJ+h(%6HjLQP~1*e@c{6gbf;kF=RzEw&uDZ9_&&TUV&0 zmh|3-A&N842lK-24mS|ZM`vDZFv7aT+mgiOd-|GWR4NV6_HH~pU*b{^q51k@lLILS z=&QLVpL!vr1%0*hZ{Bk0uZq0x=6BNMv{RQCYUc00`<6$Ey0lILbzy{M$~dd7M0g7F zDWfhKD^Qj#M=Hz?dV+Y_NU`B)1eKHao(cMnxW*|@^C3OfKuNj#3{$T@=6ZrgAuS2Y z45wU>ECW@6w#_wEjIl9P@b5gf>>&Qk8KQd3XA|mq%^ab>JX@zoeYJ(64;HJ{g$YsNUEb zhM2q?Yv_M$&^8~Ecn347pRm9o1M{?KKC1gB#hv>&$8)9%n-%vl)@j^l-1!i5#CE8f zIEh7G)K}?}UeIG(jTc!N)H3H1VWh0yFAEMa4 zx}M6r;aXW`n|rn=IzHoO$e^2+FHlI((lYV?h`YLRWsY(@>}wRJxKWp)8&{#6K@FIL zbt%CcS49nu*0^7;`KPk0%|T~f?7$zXbWQPbxyk|Zl$&3fe!x|!b-Hpzt|>)MS@BGf z!(R^U>!@q84?xYTdRy5Dg;CA$!^ks=o5iR0AdkBw3wV}*@V=oSQ95JFt4)N?fWoLA z;L(1J67ERe(hIfJeVn`wrpCQvyuY1zJLdw}c4BgMt=0z(3n6`5Kd}v%8<+`Dw!V>H z`(%$TnCC%Ss9MtROUj8Nb!r}|Q&)oseHKpe7db_EK)JU#l6cX>$bF3dsG~rWCa1o64n1LLT zo}nMc+~fKze{?4OoU-*uO+iuERa)u6mi9IPNkG9R;Q#VBG7?-awX(~l<}Md~Ml1V; zw@Dg=TTz@!VP2OJsZ8VvfOSTH6P85gy~5wrQu~0D!xMl!s|y*^CErW@ewJ237Fy{l z7+*b~#WOY1V5RfO^;cS?pPYNHnm&Kg7w#87V6N0 zoNX42Ft0fBMllPaP>&Iq2^j`&F$)@ej9DnmC}@tD1?!naU5znmB*gF<@5=f_US(aPGdo;7VhH6w)~YF&W`<|^3vRwy36eD~#Y z8PGiJ0e_Y-wYy|K_u#M)XC=+RK>Xv6?if)+BvI)zb91SGAsRx8vwuU$4vQfOvxoi`$MM5@+^6DaUjNaYzaWhL>oB|XEB3%AJsOgAbXC;;AxFgH7GA+CDw<9d#eyp6i$#v*X{l*J4@>C}yRAZXgAkT&L4o z#}QzsXSB%US2HXDg)6NPh07T&A;s$%7MY4`twR+zGyN7#M3tJg`+nPYTYR7rOVh^@ zU4hH2HGVDuRiNpW%&T?X>d(w<`b{3#sE8fZd)b}Ff}EjvA{XNd8P8#OxRwfW4d2R? zIH8c*TAnSOJt<-v#-z+H;+h1`9;+EqJId`+1Bc2o`<*7UB@>DD%|8+Ex|T!aF5S}c zrd1n8?1$3q9C;L$41Uu{U3PQ#4)`12H;)3PNv{%?TA}b%I3o(T1!DY55oavwH&VFA zuho(6hQdY->g`|7ie{&4lY9XnO2R$ciKXQWu}1$|h^>^t=P76t5FPlp81yQh0B8L$ z9)w?S%+^^YpmT^`p@UHl3#tko93qB?g$`<1B{7^OQ4IHcApQ^50BV!POr1m+vbf(L zr*3(Jipapel%<%c>3G#+WnJ1K^4|1UN%m8|l%@zk_WKE_1mSL+L+oabvD?|vC$@QI$PqsAS z*~l7M&oChQPQFnCN&cBAG2J%RBseQY89r&_PzGb_{I=XF8Sy4c3*OAe*YIcb=_}|c zl5Car&m@q^Jc+0zz;A!f&#iC%8>~Hq*7>Q3DU0pFkIdB<92kH`9s+x-pERy4nT9K^aRWnRA!Ij zp^N$iBy+gJS&nkfLwQI`k`jl398+2%!gXNYf>OSilX+-iJiLXe+rXyMlQp>l%gGY$ z(&t2O{Mlv-8i>L5QLDQz(A<{`GihL(!C5eyM$ekASze_`^l(0bq)PZT;lTqYe3R@J zVo^}GI=bDGpL|meZAtdv(AL_tM0~baB3^1fB3@3%o>C5FP5COV&pawbyz;xz14KNt zJ2}RvkhK!=V-oR@gvL06ARiC4T7mc#Xd*d2uIUIOo>hvnh}SFszKM7ahv6&IZfK7W zNokPo;KYELic7`>1~ONj!DFi^+o)cMEn@|%21@XqCiN*bH)vtlX^|lQN#;R zEI*e-yt?5Eu;QUaNPK8aL^ozwfUaB;@#d6Z5)SP?-q1jXuRuEHX0HK~Kb2Jy_e#+( z>|RF4k{dnhPN4$PeO4~s-fDX6MVM@@936_vCEZN$3L_FJOrmt-pT`O`iZ?-tsPqtG z7E9fV3itTLog>I+M8#DwI2M!U#9J7joDy4Cg&;}*$C4x*GRHEf1OOyl7Zp&FZk*CK z+J=x`XY$}yA9sDW?>8o8&Jgg8yWbdH(xsj0bns195fxv6UTjcqYV8B?yX!>LGIWV# z#8xGR38f_x89*%{i-aKmOR+@4W3^LSBC*<()e?z%3(uw}*_cqomP8rVuq6^_cIN5@ zDmBLP@B~D-ieLOxCSW;ECOEiFK+0^NjQ1BRvB*e;&A~wxJAh{qI*@LVbpM2vTuDA( zRQ3d1P*Fc!kZ@t8mZ%OFN~x%=^2&G|wq$7zs()>mVsGh`iY@8|LYPwuSBlHP`8ux1 zh;Om>Y@~YGcdq~LFVoZLnc|h{HMQ>763ISE%>gYJLN<}CA&ONa%DVp)R8kjpYQHMc zgVa`W3G|gzMD$~Fh?Fs*wAR*K1AoNFnRXxLt#WJrZ&#x@IyucVJI>Qup&|HEc@ROL z_saPcX)Y1V0xT*Oe?F{d(Db%pdka#8@W*TKORbb=$L3;;*|9=^J}-GEsx zY&t*jyk6+418yZX5Tku-$y;hl@A30BOGq3 zKSaZof}*H-SQ!*W;lf~q5))L!5y6WkDV{u>73)>dG(;Rm$l zT4YG1!lW~2~c(;*iMGm>u(1m+K6Mj*&Qn9*`sY?^BYVU-q5gd(kZYcJWiU z6ZiQL4LJPN?bfmRtu_WpgOYz4;WE0R`)m{D+Aw;dT)D0VuzlwPb|)|xm$C5+CLyo2 z8^9QeGIEP3@|{P^bzZs6GJ-H9hm#U4B!*P#o{Qe~wMR(4as!|cPbe?4Tvzf_%Q*zx zA>fCi#|1^fbw`W6K~u0aaSn+jUkK9O5$PX>=ay(yMk1im8Q#NSiP;86;J}dB65<#Q z=@@z}v9zTD=b#f7kb^KzC5+ROFounu&ga5fy7nVw+|mNUJo-l)9L#QRE;v#x-)hUh z6%(DpcpJnOPe-)TAdC>^Q;6f!Cj-+wJxk(x6BtMp4NXJl&mpV3-!OSxE^YGI*L-8P zkB3>)LyI-N=b%KwFrRCfYrOlLBBe|VdX8WF`Zr5GE^o)3U!7(PK)Ai)`=9s%w@J9Y z_D5g))|KmjX9AUadeD|vM1@tFPAwb$Hp%cq_i`||cLziR6Z z|MuL_m0jGv?u}QU8hSEwt>1b_uKVI&{m1)$c=y$(dOvnvbMxnR+(9DLQzm-ejhjUC z*a&m{svC0U8+-HTVAD$}8TZTY~_(1N&D=*Z@u-4TQ2|7 zM{04rKmNkIKX&yOKFz(mpzja&@yi0h+j!sq^vuj%-~9pT0#_1;xnJLgAKw?geIMTz zLJ9J7eEXYx8;64+f8VWFUwrAEm#sfFd~0UMyZ?3WORrd8w0-lt-f#`uM^9net?fJ2 zHrnCsq3zpg8PJEXq)&2wSD!w-|^*d3E_Mgfao6ZFSdNm``)43Qzjnk4Sz=s zx*lyS&cglp8K!Nfr)nTmV4WYvu=)-0~oc;yu+v~wuP7K`fq*a2EfXawi>%rXo|IddqC6MLDTKAG1fMXT2^>t z%)iJZwl%x?d!N4g-she&@n~=A7Bw{v=7pwirm3B=DK^ttQ!L=9Ute^Lk4>)Jy0zT9^`8MNW53v+c+)4oc_rX_q&IfG8k;yGy)HC% z9gS^>jcuT@f2C)S^v14nH1xLS-h9pbHofkedjQuXys5j?)DqHNgr>IB)a?;mw*#*4 z($piosT&;eVz<6}nemHLCJqk061bMqs{pR+0M~}t6dORh-=nF6y(y!HuRSd^wf&m^ z`0IN>y;CO0RWE2=tfsmkZD?wSrnbhW*eKZDNmI+@9;fgJHG~6>NVkR|Y-I>D5gP0o z?EV{t4+@3fq?Qk4>v(8+6D`limS<`C-zj`xD14U+9|Ukh;q4SYEzB89Al>g%_JC0K z?V__~OmLvHEznsvlGQ1nU_Js8;#ip~b5P^dr*!o3u=jNOdw2kOBij!1Wg z9_*wC$A?x~1?t{M*(IUb9|#VQq-=o0PT+8J92$`x-T$QUL@4}PHN5ylD4A%HeLda#spok7XJHyk>#drieEji`9K8h1v6Mh2O5uK91=Po!vrb zH^$Bqonf72^~MU{ua+|;P-yu+TAquA=K^qvhqS`Cs_^6y>9$aK8-;ho!aFE@5#1n0 z!U|unvGt&_Ft+O$8`?epWchUef}*y(#Z26)9zULTLyx!7<1<3h9iYx7lx6Y3n*EOY za46Cz^kD~m*cSV+jXwOPg>!YqpHb7~i+XG7nS*9YosSnIQ*54`5sJc?VsU~6CI8ob zv?u&;S!R3|MZ4clZKop+e&6tj<25A6$3X#qpvVe1w5?QZqB-SusiPCN2K+_jeqil4 z_>AP{<$oSIPp}`?+J45Bf+Fdkg4AqtH@nkt4&$ZGgcYjSWz%(d#LDqGd`))gCLD2m zl_wU2gwqfoq84~``U$?tY~+BN3i!YnJqh_)!|h(q`L2z8wh5H-r2W;Da8t9pw+T`} zsotYgmL}w8)>srq42Tr4B6#t+3mYe(q&6K)njxN5;nQ^ZlDCjL4;cOnQis}ARj7$*g(`{ zn-PzFw~^Y6Uue{ae>RkzSVs_!!E`ew&)3RJSEpa)7H_JoKYOzzhRcWJy#SXwUA9MH#i$jWnuEAY~v0-sUVeShQ4qZL0 zR%3;W+1j`}gdWr0Y|uK{JzC$J*0poi?6!>yu>j!TGVE5AlwtR1!0u6Dw=MrEmNH;> zOVp2J*RY#k!pNp;EF!aWg_i1dkq3*%W{ASyLrCw zlxRO-F(r=j&P~K4``koqmlg2?QUN}~sAc@59ry`oXZ+-C^)%!}-ae0?+`jMpMA$Ae zhsK7m7O@nSxY=0A5$ToT*Z7IE7I7ajKmK0VT-|+zVYJ^v$d!AzLa*L^rIoJ<+`E(baoywYiFJN=?Gk!j_Z^zC#i;T_?n1vy1RK%@pLpo~}! z>$QR9oZjP+Xn~)z_^||ZF*B16C*+};+Z`HjZa1CwB!qpT*Q_UpTB8N61E8UG}z|D}d#koua(pB-u#Rjf|45D-upwLbp zkaQi>vOz6~)r8(IL<&hJO|h|iI=Haj&46YB9J%6>AFCr9M^xJZjTSbd8rP{~JQdDbNtOp8GB@yGcdhyOSJ2vsFq%7u);h@BT zu~tD3?kL8s0rZ0v)vwn-bbt%vn_O{YLF>YwC^U0zXVb-LQ(fi&~}7 zqVcqEl#Vx?dr+`SEJ`MherGj)v^!@_N2IUiUK6moSB?-P&Qi$g!#tXoahkYEWUrh8sZC5W<68ht)Fn^g3@4W(Tt< z>VtdaZ5e?1sN5_xy$5cVQMoDj{&1X*d=fk{c)xRZ@ZKYx1b~@8D_V!#Z(;{a-Q2~n zr>cWd4%k>DmXsqhjiAJqtSB+~TK37?N?N1c8kFXm+5>y?HfI9nnwnvI%~0?3z9Fd? zagA*_-9rGANPYoz*&NjF@O-&hhC>~y##{>{5&GY)W(>b$TGR{rU=42_(G#8*Po@ff z0AVbo9m9aAIMhs!csSE?JY;9=XA`&7vju>t z6=xanSc|_%orB+)UbfS(ual030`1;CxN4VJ4e<@rV$DhIYIjiEf)0-G$4PC$=P1%< zRH_A6yjfEViuX3xL&k?|)aK!U0h)(H*5Y<6o{gJgE(MTRGBeSRt%JK`gDw_K{r0A$ ze$zWKS)Tfp8r^Pv*B5vOB!W!`Nexj8BS~Qy|BN!zpFQ#Lvp1qo1V%nrd(c!8aS>R7 zex?>;Q;gkR2=iRhW_P>thUpeK2pme*`XOpdUTF~{x{tISzOTVc3WBVNREX}+As%S+CmHp!P&M*kE~T4M>=MslSRZhOxZrCzI2dBI z$jDRpfc&F!^=%|RQ_SED~2{$loH-3su=5%y8h7H_o?X)*p^$m%!CU}Di5GzQUZ7IC3 z_&y}-5Ptkc_;znms_({=0m$SM9x*oI%0#Sf4=_JEl?Rx-g@4vcfTrWIggz*Ttzg&3 zyYUj-9eEJfq*=AmNTSRPI_2wgC2^t^p z&!}A9?)H7dwhTNdPQK{W6ET@~2+=HH;cFJ9EK1+x8k4*fMzs}O`zsO6elq(JF+tfA zpK}-ARQmSjBqSrHbdueU3o<&zyvxNmKHofwrv6-TIc;i|mxi`KIIIqG?5_&J6e_Di zVp)Rz&Efa<@VkRwIkN8KSFG%V;n%?L?gG>F&9O@Cqq8zNgYw6~B5KA0;AOAYv>X}r z`MF|sXv!Y-SBF?PVwq!#ukmwaXu=hG!cN2LkT0{qxC$3}j#?}(H?TT%J1j!$IARw& zxYkOMlzF$gSS3Q=Ka9bdF&Nfr`fbko;!VMYR97oQwiv_pMy|DDWWx-IvLHgh3Fof` zF-Gwvc!%_ixLgF2i>pMA$f1}A==2wgSQ_Gu!yN0y7K&`yh^LBIprq2zB$>Vee>G*M zujG5a5H@iHw4foCU85`nBd`x^8D+F|wm2#yORn`indSKX0Fx;>dEanIG5TdDw zF}4?Lg-0pbq-C+4%_7J~EP^am1ev^@gpP4qlJYAPoucqDTZCfkBL{D3*Di5GO5anz zsyCwTZtMllnrphxw65@$X-tYt%qmX6D=};Ae0!EkD+x2FGI{1&nzn&ZDjQux7#uCP zXtZ$HYIxSB0wGBMCS6I-*w$ykWbP`fwSS!Sa}sntdkA3C#HQkFbcy+tQpyJtwlM31 z^lg*_K#!$Yb~r^sSs{R>_sOJ#QQLP#+Yd@`2DVs=j@;W|}JxEXLN#d8#|$q-h}@+rtqx#-68o)&4anDu>~}OoiWhxwAjq$n8tU382f)a1d}Us{9c^>Au*>!~X7x3BS#R|?XT=6(>^D+7 zJ`flLi!}XbqI+v(yTQ0BD!^zI0^Se(4CAfcCW`hwPV*a9Gd2 zcI43g(vhis@5qz(OGj4jdq)o6FC95@-#c>De(A^@heyvtl5&7#g?=qYGSF{Y(CD_2*Yf|GWrHiZ%NOMc{e00J%5t2(OF0ZMLl&bKa!p65n_t2rY|0Jp^;?q zRpn5%6KMaT$-b~#A#39E0 zs=lK<_3W-NTe%eKL3~(Jj#~Spu2?koqhf)2I;dETxMDHlibXo0SO{VgS%^&uyB6Z+ z90EzvE>hPnn4&e^w*e}9&4^7S&=JVWYDabQffSkr8bn@KHg~$AuzICkMW#xwDDG5g zgM|u>x@JnM(qKHig{LZcr&_Y*h7o&2EhXZYq-PTY6CYBdwLcUBdAH$=b42-THA zSH4RGI zrjGDsBwVf1m7TK09LTnh4=p1xSr7f0I-P{nxPBT*RDYO4+Aj2;05*&UqjK;qTv?h! zX4TpY4=q&!PE7xXvLCwUS@J4i3lXw|_%?JR2E@C?A>t?5}W zv6|k&_D9m)47wb&WxXj4+K8guLj{G0K^t)m88mR*n9pt;iU5_RYjW>=XkNK+D*vXFVtHfSoq_eQB*}wjr&##aN({&|jzo z7%TH^sib8w3YFq+6!}sjDisY1Gv$iz)6HHCwrFjix;HhvdvOx_EL_>{3x7u>zdqrZ z;4bqYNpFpkHFjhxYNdtrMFOfnj7!Fr_9HGSj>QP4G-m@CTAH?IWOg=Z1>u-RI-hIK zqrgCZhU$fUDB)7kpfO?Rnm!nN2cZo1JW9>+5@ngNn5q;RHF# z)oRvmS;CmL+kVgPbjZDgJvt;Rz-Fwzie&)(Z9p%r>UYs2l7g`B8+Y^30xG<(E?c%mJF+EOwj7-+?f`oZ05 z#idiy%3853y9G1_#19Vp$UyptCbes?TEj2DA6x4T6O_q7-=bC^KSmV4^R$BYWx)>K zUkY(w3fnf&LP>~hfD;EQWMxVMIVy~Ej)eIL8UkD-svKgQHYtSy17~}n%6ie7Z&uyU zeH-nomV4AU@ijxg`C5O_3Lef42&ID=50yhjU)Hdg8w=)ldV=Qrnyk!>GV_Q8DwzwD z?k}noUWp!=;o#dyVmsDI%8~iQbY^T)Zc1JZ*;9%$cWLL24ZOpliC~n02TxgkkO z2PZsM04N$TrfFd8{++%-PSU?k+uwKZv86ExTp!H<_ag1A$s6ZfD{W5lPpT6$-K?WC zUd7rn&S=dv+Tszh(e#-X*u=+$Se2QMV>HA{wQpzG3>k*XBk$S@4dIjws<|Ke zp>~v=#X6*AS^JbxK*~uRX&dEM2Xf@~$7XAhT!&3%Dq9>3ROJyw2J|f(@c)x>iK6do`+o+>!T{rVdx?@hJE|9eFJW|r*!qBF zIYBV!FEa?LdsK~hmH9Q9Q?Q9Z4PT%r7|9J{Bn1#f0@8a#pK>BsRH-;cLiLKm7vu>X z#b&2vI1>tmXpgD=;X=`=2iV?Szft@_%US^X9AXvkjpc;=%$S^WpM; zaO$H-CA`O{tJ=ggw!hd~u>Xa`72}_KlOVGE6%tcdtdYf)u@q-!zePTIwknSKlG)F` z9t1@*4`x5P@}9;lLX^iwx^TxN#~$GV&1!7JPvt)Jlsw z!E!(mGN>e4g_@Kb9dJWcA|i%anO!q%7-O;jsc(IRHpt59r&WgPLtPlOpcA3`E{xEO zFW3jvtG6#r?VN`4+J^I6rnJX&o~t=syW(02b;))_JUwT!H5(>r8ZK!X67hsHp2A6U z!z9h1GG`1iD05N1_a%+tZ;&)WqT52d4v9S%0g5ZBwZc!Wqq~Yg<6PAiN67_j7i8*d z?8v!!lE^+V}uve`u=pWdt z))w>+lv|5*h<`xlhmBOvdH@Z$gB~#W_Gi+%QbHO}3qc`}qt?S~7PXL{LEW%|%{jIu zTtmwO%^KIr6^Ym1)fs{>#5IIu9KNR`*x*awTfn38W*LFP&s2oLygMhdE~h$snW8af>>7t9igPp=#==0P67}6s65b&y>Xt*2!KUJ-!HZfSG`w7FYt}yAQACt~$abnF5cc zF^!yMKo4qxKG4L=@DmftQmHTm*pS7H_d1b>515-SWfhNuT~QWXgI~jZpwNeym})~b zWe3&i+{b51O9IDDS>j-61AodC&^qrGm0~|i8;paPb0UCIR_ne|x&r7U6`lqZ1k$5j zf20|H@WN%?YfBFktvE1uaTr$Nh!)u2pMqyloJ zv&GpjBmkFIsT~wYnWDou!QO?LR+@^&sB{SHUtx>}7Xnyfu29pr2Gy9zl3?vdv4v_- zv|L6)q^ncI=3+jhpULD1_zKqGoKa<1hmQ4Vl-XqEVI` zh$A%HEP%+EZqbTHSjeu=RJ^m5Nn8YWi)#PI{;JyKLK?J!^;`hDH));$A>l8hzk&j# zA*SMh(SQ(th%3LMLEXdW5peC>JSHsonE==wf-PW`8E`}` zOjs}y=UEX_@UUZ;vZ@sk0qb@IW|OVc%XX;Pi50t{g!1Z9-+>yYOCrhPJr{I->scIK zrLBlenlXqL-&`N=e6)gXbcITG-Z$c>7 zXFTlfoGej*CAE#dy!0qjpxXa$3W!1m?mrZL0KOhnsrfh>i3f&P5zfQ(AmL49 z<-<5!x_7iQHKbb=()p=P<^yCn!A17)WR{ef-A-ls>Gv-uH9T;wvg@9IK%S*YXf!OE);(z`2h_q1GVpDh|wMX-Sj59TD zwPx_%d0c2L@jmZ44sz+2G5z4g+&hP9Wi#z+5~NTzJM$9Fg1! zJx0Y!-=aMrx`b!^#DRu6iogle6I%{5)5l|*l-ORF z^Vn1X?9u`;*ozasJdwr8=%edsfv(*G)n%I)DA5EJ!N483X@Espb->mmUTr5I`eL z63_aRghRUMvhnK#paO|FT;taxJS$C-@#`>2p0ilaMP_H*Cn+MY@#~IHQp8|wXrrkJ zXt+fO!Z48ZcsS0s=~FD{v(spt0YGFaZIL z{YI>LFc44(vUdn*kX#G_S*bA48Z~1)L-exf9j->vbu;m0Sf4Z@mp6TZmk$6{b2cAY zD>5x0s{u7~$$+c|)W|9WvKmk$h6S0{M^>ZiKC&8B$3~YpRmb+^y;uxc`)bdgBC7$_ zkX5)gK-D7=Sye!uoI#8g01K`Pa1DzCaEEgOHNprUJeFzm6k(0NQe~MoJCS7JhE zHNb;HVJj)c@ z@C?Zlf$Zi2&qf~c<}1B$W1cR(=?Q#GI% z$CejYjKkDN1{C01R*WNjhbhK=C1F4@HuwTU5CQo1^*5;oz!!_7E;M!+!v+eytinVr zCe~ClS0zxL3RS`Y8yHAbHVv=?I+p=sWKlG8s27u!T}Q80-}4GZ34D zM4Nw)d?6tlO}wB?N`1*lqfu)$N6@j8{CLqqLh!vA=Hv{BlNC&GvGza^(hFHHsp<5t zUJG`m_;C*Am1X$!i4^(Een&-N)v+~YH<|uA85M8L&y{#J%#Bi)lFmr1aWj!Hq>Y7X zaGVo?vmn6F5;{n@az8m?3bn3eX`6Fu5^RQ*=^5s>esEYIcK}mx72#c{0QW_b#uin% zFVZ!mhIe^wVM>G-G-n zWZ|=d!>y(uj!}hY*z{lrWJ)`ia{#V!B!^5LNHZ3hCd55-WWm_XpC{lnG;4C*%@Ri? z!Oi6+F6;UBnri9u9N}$G2tO;HY_KOR?i5dE>y8?3@^Myi zHm;|m9I=d2g)T+`ip4%na-K3KxwVN;DQl~qrX!1PDa}Q8Q23}OB3oA6Fck-foAIop z(QynZ3T-|Lk`ZY~SL99NM~J6h7#mV*NkpMI5)xIf!Ac$k>(5yX{Vj|kQ?CZBl~i*r z(~O@eFKy0c!XX>Qzcd&ViZb;;fyjwXC?3c&P$3RYJ{g*dgM+5jN^BY5pc;sc9>gtm zV|$WbQ~jn=2jfA7wE~+6y%{UuniLjyt1$4E6@EEmF{YzvsjBaYYXam5Ik)T>MWl4& z5qLut-Xze@FJMf-i@kGbF>x7V7qNx*CzXK6vK@ugGHdeXFOcvj`ZV!os6aLNkjm9dtjFHiX&Y#9^vL6A^!0QV$udKDrCz8tdS{)9I=1i<3axfV zPNCv!)){j!lPkwbSW#OZr=^o)0lr3HISBPN`byC2@)zn2pul0vx4Bu`)`IC98^touT;Q51x2lFL9S6ToS#by&y}Rg}`&iJ++c9mOZ1IZJ>c(q=i|_k?7=k=XR?sEb%F zQEve3A{C7(oxvy(jzq-e3>tVcv2dm^?3N8YG-bj_V=wL*w#HuEGl$fGqn?4(HU>Da zw?G)%)*-BKXNiFt77f0PsG`(kft%|%hWspj=&b(zdAdJub%0$9`x-a%9)Q~`wEsCJ z6vhP~{XwZABf3&2Ap}L=@LmN6uvfHx4mqNP6ox^HJM_>&ASMPKQ;gMEktJA_frmJN zdMSoLNtycL0}c686hHty!8kH(F96UFAE*I@`nsN_le8_YJkVG5F#zMKg6dkc*NQzD zRRpOyif47vfO#Q!D{LnQvR4A)hs$MWb+pA_bJqo?rn} z@~u+_8A}BPoq<3qK^tmVp$0~NnTIW0)*?zSgKE1v&&MO-MPLH4b!@R6B;qk1U?_Ai zLOnbgfXBZ#)D}?jrTM|2LR)G>D$4_34T_3wDE=L5z_(ruR@b-`2tf<-vMXj|5Aktmv&t%l5-tyCqy7 zI$OKHk`4)?UI+;_^L-=?vCAM~*v+LytO}DL(z1d?4aJ^Yzf0ufEd$p+%JTa!gx^)9yx7#=kef@TGN^+luNbw#uF=?d9sR zE9lz0u^)yj?d7)0UD-pWyjxg&dEn6Al(rlm4gkSuJQ4&6CD)Yb;JRd>%kHairy z<;_QNNOBVI8Enh7OR!zQ=zrMn6nrNSDZkTj6dXoXfxH)y^g~gw+*Cxtg;kDyBX38+ zZzp@RdEb0q(1x5}g?`pwme3x_;8W9TiSjd-M`jA5rHPnl*Vyh#h1G;*wet3%!Db?r zSxYQR#2gGIU$f_j(Q2!r_u9Xaq?UUjXDMh!FX?`p9oAZzW&H|Y*;IPdsve7@@QwpI zTBy-$7KIc&(QZ+nRp<6n8qFM2M$tcGhqj)W`l&aXQ7!ctb}S_($d~m~9T!9~7OUj>=W&|1^+cWW zlYQvP3~nQjUd9mFgPAkCI~b znlIB0Eto3Yz|$AegLBo5wY@sg1dKrI3f|XdK5j12+F@i~@(f2dck=#UuvYnR2#T>J zTPu8Av`-5<3a7UR#dk}^B1vWCCTq5 zsG`NfrTT22ue{)_r*_`9g2}qxxcbd%3_s(FwaDAH1PhWeU6bUx8nd0-e9X@GOSP8G z{0J#*1sJ~sOY-b}_GfI1Vii?e#EBxH|9Z6yU~)Nq4$(JEpJIpfM}tKV`&4N42|_7zaK0q_!PW)8VHSAjt%6!>OG zP+5Q=gJ20AlHi3tP;qfWx#nh=;sEU$IHK)@+v z!%W)rt2?3}Mqrt#9CoHof_cOl0hpwF2nV`=?FBaomBcOV;4?5GA{!sIbCSxB8cZXP zfl=gav8Jv2YZ1TnM*Qqfab5`bMT-+|3&N%De8R1~2!yMG5Uw^;2ErYiPq?E5-whBh zVVWf(6^UB3LnKjN8;L4n$+!CGB4SXff*R$it{dNq94YYKIMI4p zOf>x8CK^owUdkBZq#_1M%*3B;i&7$xD6zf;r; z9EqP3S8z%BXRWXriVLaxl71O%feBEjUICkNj?%++7wXdlFpPgmyI)G%+Y?W&GxXrX zRBUz}I^Je>87CDURwKI_z-8J=i-_#DB07kWD?Cwvw!Vty69V-dn1EQ_QgKbo>il(= z2m!uMKFPeMvt*G&&q8F^T@FxW9oTD!9PxGW5&JqHhGj^@(-&#a9$Rpcb)l~cDTuRc z=K>NaO7b|@u10G4&2ywmuC)&Y1Rt7KNTdB%yXeOD=er}x`N;)J*wvj=t=Jx%{ps(?Q?q_<^&6$Sm-u6sh_@L65#6FV5PM* zVilVCYplY^xjnWhSp|xbuG}hUfHugYzr=K^>oeyQ=>~|JVU%?~pCtjZHBspLXRI(u z>oqNY-OeROdxPXXB8^zQgyGjVFKzd$CYQz9fEpCi#vS6&cF_cdyLYBV;hjfkwJXFw zx?d%)P3-IZ-%Qg+L}+52otbHz?akwWVlgwaa@9jIR_0v-Aom z0K`3BIFB-^?$ZnfdDm5uKJ6J4<8h@eN=U-&eW0 zH(MayIP-twF5D|=Y-oMBFfh{#ATUlKpBKs|j(O8LwGBtz^^^-X_pf;RyWhX>qmTRJ zw4%I0#`6=6guxwjZ7a#SqXx+O*$B?@eAzit z6E8eZI{!%i*Y8?#0uUU@fApPSV!U~RxTqJvsE$FXLIaF>7l@QT&@otbk>x4{x{C|> zc4*=iNAm4gC#!$4USs$C=(W??B^kpJy)M1z;u}d-Fp|%O_ub2T2Sf-T&5o+2vsd&E zw9zXF@~n$)?1{J5Hunw+rAAlHGRd{s>_9R;h%F@N(1n?q^lYZ@z;&y+$yV!=H};lo zzMK+Evt?QL^2;}0zPYz_GgZ^gYI3t$^?%2F@3)VA#?%F$K6>*pFSz4VuX)BLpFMhZ z^D#;On5$1;_XS-(>j{7T>{Cy@O_#gg_M$u9_rg1LS^ri2_s?9;_PVnlnlAdgxpx2` zBd)A55k30BYK10*T9P>}VcVGJ* z&wZK!VRIR~0<*SoR#7yf20G8?_UG*i| zoF#M!Sj_ekx&hDU#DhMkppry@vG$7_5?glp;}@d^89@)FDv*|9_ep5vGn+nkG4v{i zFtg)>KStokibDvGib)vA8;+W#&*|lVm?wV%<5=iBi4}SQgzD2}wDC?}e$4lN{YO8U)-z`D*U#M2`Tgf-APbF% zmHIzJea57T-gf1O&;DOe`J<<8KIY%neDUM?hd%IOkorkq`?+_ne%=AX@7H|j!;d)g zBY$)wmrpZH__#2kC&~#JQ=6L=Pu3LkoL}Y{q^;S1JCEkOLCBL6La;!#YP&y2(B6?E zd$@MqP%l`6J?(SK{uQOu_Pdi>#2wVenNcB3?92A`n!EfI9)}y-ovFwnWj<@m)Q&w8mNFMx`u^o3pGU1_4T}cE|Rp7uaV@a z=Lf^KwWkjSOzNP}mIHSU=_zfyQKgQ;Ue~Ia?64N~rLhAFs!X<|gE!alR>A;_)g)5J z#kpDQYz0qiIFTew4K$4B{EmdaX|xGwox{s zh;(gTk3{4ucLB;hI(`0F906<3M!B@wF#Q!)3rg&z1)r%&fTC=xpFUuBe5m{C$78$^ zj0TMwr%bHLnyhv&lTzB>Y5h$elQ@7w$`#o;Ra9hoyHb&c%Jo*1>%l>adU(1k^=PMD z@6qLYL^T%maI#nG(N4MEqssMI3M}ejLssh1PPyLlay{&{q8=(hr5^2+>pikm511A8 z1iwl>S-B;6KB82wG!FyQFgObbmui(JVPFmhrU3pe#IboT&A`9}48Z@OQl%332cSOy z`~ypsO0XXQ{Q%?-C{-!}egN(RVDI)Tm7qQd^Z}SJ?N{o9ybAF_fG_D+>BGAU?g3~| z^lS8iU4``ks5|`{eMnaUJpku+zeXR-RVWVtdE9Gg=asxnPW1-jz$}{DI*#L|ot8rlYCrGyymT&SLR*eq>*<%SI-MZr9k2lGW>w z_}cN>Vk})^{WI3`&5T{O+FGk^InGewisF=K2cGQR!A%1f7eg|^f!IXPUoW@wIOv^? z(}ZLe$}@yV=hz54CKW^4v@~e68q=Xw99<@V+`6-`z4%6*#9V9N(;MmhpXSarNRF$z z<2^h3()P~oO0r}Nfqi$dBCsVz4-6HXL~1BQQAvPG!iPL6Vs^H7w|8fzcjuwiI*x6O z9Rv<3L7PHF%ey)o)M&_vaI>9B8IrM5STpI2iE1uF`8N0}>^&)%d{r9xqWwTE44N zbdGviNlBi!5+Mxgib!~b$3f7_!^TGQFwNE&Oe-boLo`yG`tHskAM3My!Uvmac{2K4 zo{WsXsw0TEMsgz)zcms*%U@wrJBpqC1*1VoyT1@@3&Sye56JO&^)3bBFuD&j8;N+0 zF1Jk4v8m}XY3&5coBsv*7kv)qKY05YHWl>B5`hX=Ba6}LmGYEo2HWpuX`fdGf}mR7 zv6sUVCZb`}zm>hn3e%y`HqBf8P`M#D!E@JWadh}fXyJYrO=s{J`gMtJL3yPhO=ZNd zncfLCS|`SlS1C$Yrc2S7oM0((GPW@KT0kLAU%t&b2o$g&6h@En9kG_876{SKh%e|Rq{g3c0B9>jOKkwr^~OJ%(+d zvl0M{-2r?=(%X=gta_RaAcKVSP2glk7WrK1>e3*3$K53svTke%Yvn|XMQNq=!Wjxa zi!j$P($@<@Tk2^OdlO30bW=;~t~mvJH6f4QHaVBb+d@7?m8{m!~CV)mIDPSs-kgOpy{p8v$S|mUOkGcF8>|`bKJ{; znr&&=^Kn&~8Dc9#*?xOYC8`33ry=W})Ls6yL%0hefK4f;V4awRRinB@&X2zd5iRfz z#YBu#F*m~9ZZjGB;P9uOQ|T^EHn}b)*XLBAs`nII7Fir8$>{sV9;2QO%+MbiN*-`^ zQ>#2>^dpuBoudZ$&1g2u+&5el13&wEfYrMixx$B z(CySYQlhM>Wt84>2+uzJ|rR?Go+dDoQW7q4JsDjiN`7&&qus<{in-Mr9EbYs0G1!HxklVZ$<&^`e9ZI(#lKqjIcFu^nn zd=1IMznpDxCDQ3m-oFOLJdF^Sy(9H`0`AusD^zFqWyxit@s)^M);pT$i(*eCc&D8olQJ@!! z`$ERJP2DWRYtZ;~$)+4E`UuTAe?HVQnME>aPbIQbxUe5Yj!!nj9_PFs7cSX(YP3+3 z2+lhag}Xc+`Mf~wadZ2>h}vP&Dc_UmxBg>iJpZgx6mcA8C7cDe<$AC~MkYt$VTXo*?+BH8OR z6C!VfRc(!(aP|Try-hb#F2}oM*-T9hC#za0XDn-BqFXb(bx|Dkla>8)Q&OooeUE1L zOBU_%Er5-b8}+qz(%jSTdE07#f_)hQPinPXcf`a`wP(vsBU#mXUjbNN*xPK!AVeIcmQ4Zo5%+#KatC``8)arXE1Ek`UaIa!ienwFNWI-h3jYvv@R`N z>k2oA@7C@$%2~my*VX!L_Z$hR-D2W)Ai`9oPWiKAKheMdL&1d;l2i#ph8D$`k~X9u z4N9uQMiA(ZNftq{TghuWU&RokX2hjJLl&wteX;!{))c+z#+ycKzIW0|s&{3&Y|+!x zKmEz-B6kDZlj=^uNg;RJDU_2^Cu?gWRMid6<5lLM=U~&dg_B4lI(P2S1=+&lTWXJ z$H8TGW)-%LV6Y1M9G20(+*fX3pV7|_Wa7?o&nN0m|F4#-aZ(*$}R!!bgu+Ztp5 zxb)0u26otw(bQ1rM)(8a>%7rDO1A~94*6020#;|SX-BxWN$20x98CGM0N5|+A3=cF z($tl}J{o9e;P3e-K{9#+(hK%G-XBa|g`6J<@B~p1ec{H!lyun|aRATMoj)(k6Mu2n zL)cjLkO`*3wp|E~FY@i>Mv-BJLG-nA%yR1Sf$-tM!nlGcj1Tx=gX+0R?^~m0Dcd>s zInvcBxFO}_Ts5?Yck1#q#S#PLk&0q2XP#Gy5HuThA&mUSYuqe37og5i)rRmOx{04< zsT9B1qj1h1B%u?dUbJ#W@q~uLj%n^PvKdttfPH&r4%p)eT=DBz-S?N2G%Q3eP*6;6 zb}FTZ%9KUcq|;Y}sUM*0<^`V8<^fm4H#4;RnI=l+=S%(r8}5(FyzAKpgequ0?07iC z6x5LopOg3ajvqv5Ph~>0Nph8f1YQbsYA=G)`b+v%9#>;2+qz)PDRJk*cn?G29w?Zy zjE0A3h>L<}xno;5;H%&BfH*a2pEi>r@>Ty)3w*w;yDn{U%fjCr>nzS$DL->&us193vVAP zjN%!k72Z2W_{0ZKpk#Wq>7f&L-skaPdH~)PuB3(j*jC$C4-cJSOu1$;2knd~qJ`YK zJI?zTG#_${r4zyP80wJYBI!~_*7DaOh!Gzj5~_HMxW;Q*u;s+qQOhnVg$^KYxIsJI z(F2vO^%FH$mm@2d;Yh29f21$mRUBZPnnx*M?8UyZbB<(NM=fdWYz~#;D`uM5*kxiJ z`!YT{Cn@*|4KblnO^9q!6LMT2^kkD6hLdCxBWN{qAxxOVwAn}*Wlz!$c7f}I=V-E! zji60a%aU5zA~hvfvJEd%uOJb50UNCr%|a*_0xIEVFka0}O@io)SO@wj5b1-xArrQk zO(1%iHS7_#H*zr_hrzM-%-fI6c_HBH{m5jl3IGTyp9iiEXDpi-)U54 zYO!mlZZ2uex}|t6nCZ;T#Z8yAxSG0Fy;7?MLG%dFf3Vz4XFF9IXVbXl8fn`tR8GaN z(r}e(JK>w~g^*TH&Nb6I{};!zZne^KvvDh#Z}5Yz(nf8~&BU$>Bv&ePZAhM6h%3tm zT21TAwYVJvWTYzXc$N|T%bN?~v1+BEZmYFQQg_XGwbGnzxlW^<)LeT3iVKybp`qJ& zgy_J$`KChnqDrk9S7z5dD%W%5)ug=u&T^&IcC(c=z|R7HS<-5C#FwSG(QjXCXKmTrJbY=8h@b1xef@+ar58|AWLqhQ(cPNG`{)f zLb$8jcq(njy|&wE+u7~aBhoimAE#wUx5co0!#Ns8gWrgLJQ0k&)wG$-44Cf#;|ylG(}aC3#OapPR$}t3)Ki!X*v)?g<%|kH-_FmH2+qZEu&-8= zd_KUn=jmF#(qwi$9f?mTEz$Ti0GC)-&A1ger8ryk^i`3`#N3K&HP?ve;U&;BYwZ{^ zKLB8=ks`oW%8+B%Ojo)5Q!ejJ8ZERpsU~rw?N&NzyF$ym-cksUnk1c{52)1`inC5i z>eLI8GzMi_Uo(=Q2l1EVW;1ObaFupDt}nNxzMV#^U0I4 zYauMJ zh*&c=d+KWLU#)`>{ziz0ftd0<>>v1dxO}OX;Cmu9@uiSZfovvZigwD=!re}2}EiKwY^s*=li2j0> zBiWSA1NQr9{J;EdGd+!~ZsV!zkWcEO_+9XJWq2N-$LyzQ+uvvCUAme>z0z3g1_7DM zhu%>LU%bu>x4a`|E}cdK6MdLSn?8R16Y%?xeA7c^3KuhOn>&#S>sOc1{voh0uT-n@ zzUb>b4m57!uP|QZZ}ZB^lqL1u5RagZK09b2MF)G+=U)H@BAK zYBHBtp#3_{Q=73K^g{#0ep%4d&fZEI@JH1nC5oWa6LHu)bz;o?psKFze01XNlrTtjQEK9km5(pc`O zn_J(Fiso#POXk{#xkeQ6tf{6AoNmj<@z2nbg}1eC29q?ZWECxA+dl(vk>Xz~xh7T) z(rM6ELv%?M%8$`p?o%!es9rgZ<|9D3XAx1hcFTLvD_TxgI_-tDnY7nV&c@5c?aW+I zFE~=8+qOFf{HKARf|F_UwnT~d!+{vwxmG6VcfwY{$85n6WS(evbdGhWM39;0Cd zR~d|PUys|{zpwbz!3PQFXm>JYD*ZMT2JboaXh`!`Q@{{gpu(=_+f%_rVh z2w!&oZo0jAR_u3pe+Y>Y!u}4}SISwLlXB^t5{Cm8U*&ZagZdnsPZAd5hC6cjuv?me zz+LYr609R&$mhqZ%iVk#tc&F$WXbpf@^H&o<~tQ4Njr{}={^onR8Ja7y+eA8F7{e; zRIjoi_m6C8#>J@mTJ9y!{z)NxZB9oq^EuL~YtcZ;>{jDsexbdd=ui5IicQavD1$pc z(FblVgwwX5$iybQt0ncs%-(xYvF|^DyDIbha+H`Kwh34ZW5!XVsovXKij1?b%;Pt5 z^$~@S+(te?ZY_!Dz!*WkuIMx}BtRbk^fJjwOM=Wh%ie`;otnGOhY7-efpGM-aeFmw zvO4I7?Pu@lBe#=z{sFxFI=!kZ z?^YI^InDckc%dLnLZ*7_K*;H7l7IO}AYamrLR}hiGh!q;MsEFx1uWT5HJuge)1dBK zOFO;vd4{-#RNLwD!5Vs91C>?5X<$EmM*3iR$&z}~;H)PaXekpe3=w@p2At|dTG*g_z-7CgO zou!&-hh!<4owWrEnOcG?U~J!o_J#S#bCtA(msIzEY2E8%pkEH6|7)Zab#GWe+v z+`cYLrxUJ!jO%0KfN_HhpXNfN5wF_s)rF+Gkb8aDSc_ZPQs|iTSxz1xjSGnF$l-lH zTu25B_y}d1qR$c>W3jJSW=Vy2u-tiyKKN&aaM;@TaUVa1BwoIs<@>-GUp~nf2+JNj zy`&;%M75Us82uH%_81Gc7#L)}M|(MEqljfigT9f@#!UduFE%S#-+tItDx|CG>3;(eS&<+A+8MoL)0qZxMreF)Hl znHJ@-*D|@BXEQNzKsX6zb&YB1o9-rb_Ke&L5m{2!ttgq&x)-sQu9_Ed^@JK!ySngV zI{j-1T;8v`<`l?2bG2TVk|?G44=@xqC8VDJi7_%FM6*KH1kg<%^lnN(6oOi=zg;$5 zPiK=kW!3irc_^J(B&{?^Sk!IK1%WEpS;QnKsi?(rIuC=CSFSuI=Sj4r_HA24$<@>S z0$wHOCYWbFMAYgm&sv(5%>wt+n2$;86ACSBwvbv?e&H$0FUa585TWn|C=9QXMM}vY z;K#8|qt6*}l9^<_aMKi?0`o#Eh2WYi=6!NB=)U*EsF1Gn{WiWIsaNI^d}o#l1M{-v zJ1f29g6g@7qrd;sqZ%7{7vzyFQwo_5P2ZsD^1jr$GMi`g7OW8s>u5zDj|Rq>kC6NV zhVo&7tYPEkoUiHJLCcPAi;5~)!&w@xQt7eMOj;o21J6A5#!e7jwhs0oo=(39h08Wt zgw1y@Y0AwJYSx*<`|c@(gBSg#N_DsKb^6eeqsLxyMP;VSGI)L=SzM~s8|m^2xSdn0 zr`N6l?k|NhZzldXe9g5gv+U=nRZoCf&a`Pub;mGTV*GpV#l|=3i?!y{-=ryj2hP!q zxQBd>qxvC;SM*8QXWW@4D`>Q>I_wR3gN5C5Um-l)huiAR*t#dvz4NKbzk;?qhux?t zkJGrTzp+nQ-tm%?>dIAejMY3%Qb zU#DJ&{XK_}{ rammarket; + typedef eosio::multi_index rammarket; } /// namespace eosiosystem diff --git a/contracts/eosio.system/native.hpp b/contracts/eosio.system/native.hpp index 61a23eee0ce..e2bcb319575 100644 --- a/contracts/eosio.system/native.hpp +++ b/contracts/eosio.system/native.hpp @@ -1,66 +1,59 @@ /** * @file - * @copyright defined in eos/LICENSE.txt + * @copyright defined in eos/LICENSE */ #pragma once #include #include +#include #include #include +#include #include #include -#include namespace eosiosystem { - using eosio::name; using eosio::permission_level; using eosio::public_key; - using eosio::ignore; + + typedef std::vector bytes; struct permission_level_weight { permission_level permission; - uint16_t weight; + weight_type weight; // explicit serialization macro is not necessary, used here only to improve compilation time EOSLIB_SERIALIZE( permission_level_weight, (permission)(weight) ) }; struct key_weight { - eosio::public_key key; - uint16_t weight; + public_key key; + weight_type weight; // explicit serialization macro is not necessary, used here only to improve compilation time EOSLIB_SERIALIZE( key_weight, (key)(weight) ) }; - struct wait_weight { - uint32_t wait_sec; - uint16_t weight; - - // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( wait_weight, (wait_sec)(weight) ) - }; - struct authority { - uint32_t threshold = 0; + uint32_t threshold; + uint32_t delay_sec; std::vector keys; std::vector accounts; - std::vector waits; // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( authority, (threshold)(keys)(accounts)(waits) ) + EOSLIB_SERIALIZE( authority, (threshold)(delay_sec)(keys)(accounts) ) }; struct block_header { uint32_t timestamp; - name producer; + account_name producer; uint16_t confirmed = 0; - capi_checksum256 previous; - capi_checksum256 transaction_mroot; - capi_checksum256 action_mroot; + block_id_type previous; + checksum256 transaction_mroot; + checksum256 action_mroot; uint32_t schedule_version = 0; - std::optional new_producers; + eosio::optional new_producers; // explicit serialization macro is not necessary, used here only to improve compilation time EOSLIB_SERIALIZE(block_header, (timestamp)(producer)(confirmed)(previous)(transaction_mroot)(action_mroot) @@ -68,18 +61,10 @@ namespace eosiosystem { }; - struct [[eosio::table("abihash"), eosio::contract("eosio.system")]] abi_hash { - name owner; - capi_checksum256 hash; - uint64_t primary_key()const { return owner.value; } - - EOSLIB_SERIALIZE( abi_hash, (owner)(hash) ) - }; - /* * Method parameters commented out to prevent generation of code that parses input data. */ - class [[eosio::contract("eosio.system")]] native : public eosio::contract { + class native : public eosio::contract { public: using eosio::contract::contract; @@ -96,44 +81,32 @@ namespace eosiosystem { * therefore, this method will execute an inline buyram from receiver for newacnt in * an amount equal to the current new account creation fee. */ - [[eosio::action]] - void newaccount( name creator, - name newact, - ignore owner, - ignore active); - + void newaccount( account_name creator, + account_name newact + /* no need to parse authorites + const authority& owner, + const authority& active*/ ); - [[eosio::action]] - void updateauth( ignore account, - ignore permission, - ignore parent, - ignore auth ) {} - [[eosio::action]] - void deleteauth( ignore account, - ignore permission ) {} + void updateauth( /*account_name account, + permission_name permission, + permission_name parent, + const authority& data*/ ) {} - [[eosio::action]] - void linkauth( ignore account, - ignore code, - ignore type, - ignore requirement ) {} + void deleteauth( /*account_name account, permission_name permission*/ ) {} - [[eosio::action]] - void unlinkauth( ignore account, - ignore code, - ignore type ) {} + void linkauth( /*account_name account, + account_name code, + action_name type, + permission_name requirement*/ ) {} - [[eosio::action]] - void canceldelay( ignore canceling_auth, ignore trx_id ) {} + void unlinkauth( /*account_name account, + account_name code, + action_name type*/ ) {} - [[eosio::action]] - void onerror( ignore sender_id, ignore> sent_trx ) {} + void canceldelay( /*permission_level canceling_auth, transaction_id_type trx_id*/ ) {} - [[eosio::action]] - void setabi( name account, const std::vector& abi ); + void onerror( /*const bytes&*/ ) {} - [[eosio::action]] - void setcode( name account, uint8_t vmtype, uint8_t vmversion, const std::vector& code ) {} }; } diff --git a/contracts/eosio.system/producer_pay.cpp b/contracts/eosio.system/producer_pay.cpp index 7d2b89f188d..1d0af68d432 100644 --- a/contracts/eosio.system/producer_pay.cpp +++ b/contracts/eosio.system/producer_pay.cpp @@ -1,278 +1,139 @@ -#include +#include "eosio.system.hpp" #include -#include namespace eosiosystem { const int64_t min_pervote_daily_pay = 100'0000; - const int64_t min_activated_stake = 10'000'0000; - // const double continuous_rate = 0.04879; // 5% annual rate - const double continuous_rate = 0.0198; // 2% annual rate + const int64_t min_activated_stake = 150'000'000'0000; + const double continuous_rate = 0.04879; // 5% annual rate const double perblock_rate = 0.0025; // 0.25% const double standby_rate = 0.0075; // 0.75% const uint32_t blocks_per_year = 52*7*24*2*3600; // half seconds per year const uint32_t seconds_per_year = 52*7*24*3600; const uint32_t blocks_per_day = 2 * 24 * 3600; const uint32_t blocks_per_hour = 2 * 3600; + const uint64_t useconds_per_day = 24 * 3600 * uint64_t(1000000); + const uint64_t useconds_per_year = seconds_per_year*1000000ll; - const int64_t useconds_per_day = 24 * 3600 * int64_t(1000000); - const int64_t useconds_per_year = seconds_per_year*1000000ll; - void system_contract::onblock( ignore ) { + void system_contract::onblock( block_timestamp timestamp, account_name producer ) { using namespace eosio; - require_auth(_self); - - block_timestamp timestamp; - name producer; - _ds >> timestamp >> producer; - - // _gstate2.last_block_num is not used anywhere in the system contract code anymore. - // Although this field is deprecated, we will continue updating it for now until the last_block_num field - // is eventually completely removed, at which point this line can be removed. - _gstate2.last_block_num = timestamp; - - static const int64_t min_activated_time = 1547816400000000; /// 2019-01-18 21:00:00 UTC+8 - const static time_point at{ microseconds{ static_cast( min_activated_time) } }; - - if (current_time_point() >= at&& _gstate.thresh_activated_stake_time == time_point()) - { - _gstate.thresh_activated_stake_time = current_time_point(); - } + require_auth(N(eosio)); /** until activated stake crosses this threshold no new rewards are paid */ - // if( _gstate.total_activated_stake < min_activated_stake ) - if(_gstate.thresh_activated_stake_time == time_point()) + if( _gstate.total_activated_stake < min_activated_stake ) return; - if( _gstate.last_pervote_bucket_fill == time_point() ) /// start the presses - _gstate.last_pervote_bucket_fill = current_time_point(); + if( _gstate.last_pervote_bucket_fill == 0 ) /// start the presses + _gstate.last_pervote_bucket_fill = current_time(); /** * At startup the initial producer may not be one that is registered / elected * and therefore there may be no producer object for them. */ - auto prod = _producers.find( producer.value ); + auto prod = _producers.find(producer); if ( prod != _producers.end() ) { _gstate.total_unpaid_blocks++; - _producers.modify( prod, same_payer, [&](auto& p ) { + _producers.modify( prod, 0, [&](auto& p ) { p.unpaid_blocks++; }); } - auto modifybid = [&](auto &ns) { - name_bid_table bids(_self, _self.value); - for (auto &n : ns) - { - auto highest = bids.find(n.value); - if(highest != bids.end()) - { - // print( highest->last_bid_time.sec_since_epoch(), " dealed high_bid: ", highest->high_bid, " newname: ", name{highest->newname}, "\n" ); - bids.modify(highest, same_payer, [&](auto &b) { - b.high_bid = -b.high_bid; - }); - } - } - }; - - auto checkbidname = [&](auto &highest, auto &idx) { - if (highest == idx.end()) - { - return; - } - std::vector names; - static const int16_t COUNT10 = 10; - uint16_t deal_count = 0; - - if (highest->newname.length() >= BASE_LENGTH) - { - deal_count++; - } - - names.push_back(highest->newname); - // print( highest->last_bid_time.sec_since_epoch(), " deal high_bid: ", highest->high_bid, " newname: ", name{highest->newname}, "\n" ); - for (int16_t i = 0; ++highest != idx.end() && i < COUNT10; i++) - { - // print( highest->last_bid_time.sec_since_epoch(), " high_bid: ", highest->high_bid, " newname: ", name{highest->newname}, "\n" ); - if (highest->high_bid > 0 && - (current_time_point() - highest->last_bid_time) > microseconds(useconds_per_day) && - highest->newname.length() >= BASE_LENGTH) - { - names.push_back(highest->newname); - deal_count++; - // print( highest->last_bid_time.sec_since_epoch(), " deal high_bid: ", highest->high_bid, " newname: ", name{highest->newname}, "\n" ); - } - - if (COUNT10 == deal_count) - { - break; - } - } - - modifybid(names); - }; /// only update block producers once every minute, block_timestamp is in half seconds - if (timestamp.slot - _gstate.last_producer_schedule_update.slot > 120) { - update_elected_producers(timestamp); - - if ((timestamp.slot - _gstate.last_name_close.slot) > blocks_per_day){ - name_bid_table bids(_self, _self.value); - auto idx = bids.get_index<"highbid"_n>(); - auto highest = idx.lower_bound(std::numeric_limits::max() / 2); - if (highest != idx.end() && + if( timestamp.slot - _gstate.last_producer_schedule_update.slot > 120 ) { + update_elected_producers( timestamp ); + + if( (timestamp.slot - _gstate.last_name_close.slot) > blocks_per_day ) { + name_bid_table bids(_self,_self); + auto idx = bids.get_index(); + auto highest = idx.begin(); + if( highest != idx.end() && highest->high_bid > 0 && - (current_time_point() - highest->last_bid_time) > microseconds(useconds_per_day) && - _gstate.thresh_activated_stake_time > time_point() && - (current_time_point() - _gstate.thresh_activated_stake_time) > microseconds(14*useconds_per_day)){ - _gstate.last_name_close = timestamp; - - checkbidname(highest, idx); + highest->last_bid_time < (current_time() - useconds_per_day) && + _gstate.thresh_activated_stake_time > 0 && + (current_time() - _gstate.thresh_activated_stake_time) > 14 * useconds_per_day ) { + _gstate.last_name_close = timestamp; + idx.modify( highest, 0, [&]( auto& b ){ + b.high_bid = -b.high_bid; + }); } } } } using namespace eosio; - void system_contract::claimrewards( const name owner ) { - require_auth( owner ); + void system_contract::claimrewards( const account_name& owner ) { + require_auth(owner); - const auto& prod = _producers.get( owner.value ); + const auto& prod = _producers.get( owner ); eosio_assert( prod.active(), "producer does not have an active key" ); - // eosio_assert( _gstate.total_activated_stake >= min_activated_stake, - // "cannot claim rewards until the chain is activated (at least 15% of all tokens participate in voting)" ); - eosio_assert( _gstate.thresh_activated_stake_time != time_point(), - "cannot claim rewards until the chain is activated " ); + eosio_assert( _gstate.total_activated_stake >= min_activated_stake, + "cannot claim rewards until the chain is activated (at least 15% of all tokens participate in voting)" ); - const auto ct = current_time_point(); + auto ct = current_time(); - eosio_assert( ct - prod.last_claim_time > microseconds(useconds_per_day), "already claimed rewards within past day" ); + eosio_assert( ct - prod.last_claim_time > useconds_per_day, "already claimed rewards within past day" ); - const asset token_supply = eosio::token::get_supply(token_account, core_symbol().code() ); - const auto usecs_since_last_fill = (ct - _gstate.last_pervote_bucket_fill).count(); + const asset token_supply = token( N(eosio.token)).get_supply(symbol_type(system_token_symbol).name() ); + const auto usecs_since_last_fill = ct - _gstate.last_pervote_bucket_fill; - if( usecs_since_last_fill > 0 && _gstate.last_pervote_bucket_fill > time_point() ) { + if( usecs_since_last_fill > 0 && _gstate.last_pervote_bucket_fill > 0 ) { auto new_tokens = static_cast( (continuous_rate * double(token_supply.amount) * double(usecs_since_last_fill)) / double(useconds_per_year) ); - // auto to_producers = new_tokens / 5; - // auto to_savings = new_tokens - to_producers; - auto to_producers = new_tokens / 2; - auto to_savings = new_tokens - to_producers; - auto to_per_block_pay = to_producers / 4; - auto to_per_vote_pay = to_producers - to_per_block_pay; - auto to_gov_fund = to_savings / 5; - auto to_dev_fund = to_savings - to_gov_fund; + auto to_producers = new_tokens / 5; + auto to_savings = new_tokens - to_producers; + auto to_per_block_pay = to_producers / 4; + auto to_per_vote_pay = to_producers - to_per_block_pay; - INLINE_ACTION_SENDER(eosio::token, issue)( - token_account, { {_self, active_permission} }, - { _self, asset(new_tokens, core_symbol()), std::string("issue tokens for producer pay and savings") } - ); + INLINE_ACTION_SENDER(eosio::token, issue)( N(eosio.token), {{N(eosio),N(active)}}, + {N(eosio), asset(new_tokens), std::string("issue tokens for producer pay and savings")} ); - // INLINE_ACTION_SENDER(eosio::token, transfer)( - // token_account, { {_self, active_permission} }, - // { _self, saving_account, asset(to_savings, core_symbol()), "unallocated inflation" } - // ); + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, + { N(eosio), N(eosio.saving), asset(to_savings), "unallocated inflation" } ); - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {_self, active_permission} }, - { _self, dev_account, asset(to_dev_fund, core_symbol()), "unallocated inflation" } - ); + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, + { N(eosio), N(eosio.bpay), asset(to_per_block_pay), "fund per-block bucket" } ); - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {_self, active_permission} }, - { _self, gov_account, asset(to_gov_fund, core_symbol()), "unallocated inflation" } - ); + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, + { N(eosio), N(eosio.vpay), asset(to_per_vote_pay), "fund per-vote bucket" } ); - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {_self, active_permission} }, - { _self, bpay_account, asset(to_per_block_pay, core_symbol()), "fund per-block bucket" } - ); + _gstate.pervote_bucket += to_per_vote_pay; + _gstate.perblock_bucket += to_per_block_pay; - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {_self, active_permission} }, - { _self, vpay_account, asset(to_per_vote_pay, core_symbol()), "fund per-vote bucket" } - ); - - _gstate.pervote_bucket += to_per_vote_pay; - _gstate.perblock_bucket += to_per_block_pay; _gstate.last_pervote_bucket_fill = ct; } - auto prod2 = _producers2.find( owner.value ); - - /// New metric to be used in pervote pay calculation. Instead of vote weight ratio, we combine vote weight and - /// time duration the vote weight has been held into one metric. - const auto last_claim_plus_3days = prod.last_claim_time + microseconds(3 * useconds_per_day); - - bool crossed_threshold = (last_claim_plus_3days <= ct); - bool updated_after_threshold = true; - if ( prod2 != _producers2.end() ) { - updated_after_threshold = (last_claim_plus_3days <= prod2->last_votepay_share_update); - } else { - prod2 = _producers2.emplace( owner, [&]( producer_info2& info ) { - info.owner = owner; - info.last_votepay_share_update = ct; - }); - } - - // Note: updated_after_threshold implies cross_threshold (except if claiming rewards when the producers2 table row did not exist). - // The exception leads to updated_after_threshold to be treated as true regardless of whether the threshold was crossed. - // This is okay because in this case the producer will not get paid anything either way. - // In fact it is desired behavior because the producers votes need to be counted in the global total_producer_votepay_share for the first time. - int64_t producer_per_block_pay = 0; if( _gstate.total_unpaid_blocks > 0 ) { producer_per_block_pay = (_gstate.perblock_bucket * prod.unpaid_blocks) / _gstate.total_unpaid_blocks; } - - double new_votepay_share = update_producer_votepay_share( prod2, - ct, - updated_after_threshold ? 0.0 : prod.total_votes, - true // reset votepay_share to zero after updating - ); - int64_t producer_per_vote_pay = 0; - if( _gstate2.revision > 0 ) { - double total_votepay_share = update_total_votepay_share( ct ); - if( total_votepay_share > 0 && !crossed_threshold ) { - producer_per_vote_pay = int64_t((new_votepay_share * _gstate.pervote_bucket) / total_votepay_share); - if( producer_per_vote_pay > _gstate.pervote_bucket ) - producer_per_vote_pay = _gstate.pervote_bucket; - } - } else { - if( _gstate.total_producer_vote_weight > 0 ) { - producer_per_vote_pay = int64_t((_gstate.pervote_bucket * prod.total_votes) / _gstate.total_producer_vote_weight); - } + if( _gstate.total_producer_vote_weight > 0 ) { + producer_per_vote_pay = int64_t((_gstate.pervote_bucket * prod.total_votes ) / _gstate.total_producer_vote_weight); } - if( producer_per_vote_pay < min_pervote_daily_pay ) { producer_per_vote_pay = 0; } - _gstate.pervote_bucket -= producer_per_vote_pay; _gstate.perblock_bucket -= producer_per_block_pay; _gstate.total_unpaid_blocks -= prod.unpaid_blocks; - update_total_votepay_share( ct, -new_votepay_share, (updated_after_threshold ? prod.total_votes : 0.0) ); - - _producers.modify( prod, same_payer, [&](auto& p) { - p.last_claim_time = ct; - p.unpaid_blocks = 0; + _producers.modify( prod, 0, [&](auto& p) { + p.last_claim_time = ct; + p.unpaid_blocks = 0; }); if( producer_per_block_pay > 0 ) { - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {bpay_account, active_permission}, {owner, active_permission} }, - { bpay_account, owner, asset(producer_per_block_pay, core_symbol()), std::string("producer block pay") } - ); + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.bpay),N(active)}, + { N(eosio.bpay), owner, asset(producer_per_block_pay), std::string("producer block pay") } ); } if( producer_per_vote_pay > 0 ) { - INLINE_ACTION_SENDER(eosio::token, transfer)( - token_account, { {vpay_account, active_permission}, {owner, active_permission} }, - { vpay_account, owner, asset(producer_per_vote_pay, core_symbol()), std::string("producer vote pay") } - ); + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.vpay),N(active)}, + { N(eosio.vpay), owner, asset(producer_per_vote_pay), std::string("producer vote pay") } ); } } diff --git a/contracts/eosio.system/upgrade.cpp b/contracts/eosio.system/upgrade.cpp deleted file mode 100644 index 32e99d8c287..00000000000 --- a/contracts/eosio.system/upgrade.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include - -namespace eosiosystem { - - void system_contract::setupgrade( const eosio::upgrade_parameters& params) { - require_auth( _self ); - - (eosio::upgrade_parameters&)(_ustate) = params; - set_upgrade_parameters( params ); - - _ustate.current_version += 1; - _ustate.target_block_num = params.target_block_num; - } -} diff --git a/contracts/eosio.system/voting.cpp b/contracts/eosio.system/voting.cpp index 8213d7044bf..8076f79886d 100644 --- a/contracts/eosio.system/voting.cpp +++ b/contracts/eosio.system/voting.cpp @@ -1,8 +1,8 @@ /** * @file - * @copyright defined in eos/LICENSE.txt + * @copyright defined in eos/LICENSE */ -#include +#include "eosio.system.hpp" #include #include @@ -21,6 +21,7 @@ namespace eosiosystem { using eosio::indexed_by; using eosio::const_mem_fun; + using eosio::bytes; using eosio::print; using eosio::singleton; using eosio::transaction; @@ -33,64 +34,46 @@ namespace eosiosystem { * @pre authority of producer to register * */ - void system_contract::regproducer( const name producer, const eosio::public_key& producer_key, const std::string& url, uint16_t location ) { + void system_contract::regproducer( const account_name producer, const eosio::public_key& producer_key, const std::string& url, uint16_t location ) { eosio_assert( url.size() < 512, "url too long" ); eosio_assert( producer_key != eosio::public_key(), "public key should not be the default value" ); require_auth( producer ); - auto prod = _producers.find( producer.value ); - const auto ct = current_time_point(); + auto prod = _producers.find( producer ); if ( prod != _producers.end() ) { _producers.modify( prod, producer, [&]( producer_info& info ){ - info.producer_key = producer_key; - info.is_active = true; - info.url = url; - info.location = location; - if ( info.last_claim_time == time_point() ) - info.last_claim_time = ct; - }); - - auto prod2 = _producers2.find( producer.value ); - if ( prod2 == _producers2.end() ) { - _producers2.emplace( producer, [&]( producer_info2& info ){ - info.owner = producer; - info.last_votepay_share_update = ct; + info.producer_key = producer_key; + info.is_active = true; + info.url = url; + info.location = location; }); - update_total_votepay_share( ct, 0.0, prod->total_votes ); - // When introducing the producer2 table row for the first time, the producer's votes must also be accounted for in the global total_producer_votepay_share at the same time. - } } else { _producers.emplace( producer, [&]( producer_info& info ){ - info.owner = producer; - info.total_votes = 0; - info.producer_key = producer_key; - info.is_active = true; - info.url = url; - info.location = location; - info.last_claim_time = ct; - }); - _producers2.emplace( producer, [&]( producer_info2& info ){ - info.owner = producer; - info.last_votepay_share_update = ct; + info.owner = producer; + info.total_votes = 0; + info.producer_key = producer_key; + info.is_active = true; + info.url = url; + info.location = location; }); } - } - void system_contract::unregprod( const name producer ) { + void system_contract::unregprod( const account_name producer ) { require_auth( producer ); - const auto& prod = _producers.get( producer.value, "producer not found" ); - _producers.modify( prod, same_payer, [&]( producer_info& info ){ - info.deactivate(); + const auto& prod = _producers.get( producer, "producer not found" ); + + _producers.modify( prod, 0, [&]( producer_info& info ){ + info.deactivate(); }); } void system_contract::update_elected_producers( block_timestamp block_time ) { _gstate.last_producer_schedule_update = block_time; - auto idx = _producers.get_index<"prototalvote"_n>(); + auto idx = _producers.get_index(); std::vector< std::pair > top_producers; top_producers.reserve(21); @@ -110,7 +93,8 @@ namespace eosiosystem { return a.second ==b.second?a.first producers; @@ -118,7 +102,7 @@ namespace eosiosystem { for( const auto& item : top_producers ) producers.push_back(item.first); - auto packed_schedule = pack(producers); + bytes packed_schedule = pack(producers); if( set_proposed_producers( packed_schedule.data(), packed_schedule.size() ) >= 0 ) { _gstate.last_producer_schedule_size = static_cast( top_producers.size() ); @@ -130,58 +114,6 @@ namespace eosiosystem { double weight = int64_t( (now() - (block_timestamp::block_timestamp_epoch / 1000)) / (seconds_per_day * 7) ) / double( 52 ); return double(staked) * std::pow( 2, weight ); } - - double system_contract::update_total_votepay_share( time_point ct, - double additional_shares_delta, - double shares_rate_delta ) - { - double delta_total_votepay_share = 0.0; - if( ct > _gstate3.last_vpay_state_update ) { - delta_total_votepay_share = _gstate3.total_vpay_share_change_rate - * double( (ct - _gstate3.last_vpay_state_update).count() / 1E6 ); - } - - delta_total_votepay_share += additional_shares_delta; - if( delta_total_votepay_share < 0 && _gstate2.total_producer_votepay_share < -delta_total_votepay_share ) { - _gstate2.total_producer_votepay_share = 0.0; - } else { - _gstate2.total_producer_votepay_share += delta_total_votepay_share; - } - - if( shares_rate_delta < 0 && _gstate3.total_vpay_share_change_rate < -shares_rate_delta ) { - _gstate3.total_vpay_share_change_rate = 0.0; - } else { - _gstate3.total_vpay_share_change_rate += shares_rate_delta; - } - - _gstate3.last_vpay_state_update = ct; - - return _gstate2.total_producer_votepay_share; - } - - double system_contract::update_producer_votepay_share( const producers_table2::const_iterator& prod_itr, - time_point ct, - double shares_rate, - bool reset_to_zero ) - { - double delta_votepay_share = 0.0; - if( shares_rate > 0.0 && ct > prod_itr->last_votepay_share_update ) { - delta_votepay_share = shares_rate * double( (ct - prod_itr->last_votepay_share_update).count() / 1E6 ); // cannot be negative - } - - double new_votepay_share = prod_itr->votepay_share + delta_votepay_share; - _producers2.modify( prod_itr, same_payer, [&](auto& p) { - if( reset_to_zero ) - p.votepay_share = 0.0; - else - p.votepay_share = new_votepay_share; - - p.last_votepay_share_update = ct; - } ); - - return new_votepay_share; - } - /** * @pre producers must be sorted from lowest to highest and must be registered and active * @pre if proxy is set then no producers can be voted for @@ -198,16 +130,17 @@ namespace eosiosystem { * * If voting for a proxy, the producer votes will not change until the proxy updates their own vote. */ - void system_contract::voteproducer( const name voter_name, const name proxy, const std::vector& producers ) { + void system_contract::voteproducer( const account_name voter_name, const account_name proxy, const std::vector& producers ) { require_auth( voter_name ); update_votes( voter_name, proxy, producers, true ); } - void system_contract::update_votes( const name voter_name, const name proxy, const std::vector& producers, bool voting ) { + void system_contract::update_votes( const account_name voter_name, const account_name proxy, const std::vector& producers, bool voting ) { //validate input if ( proxy ) { eosio_assert( producers.size() == 0, "cannot vote for producers and proxy at same time" ); eosio_assert( voter_name != proxy, "cannot proxy to self" ); + require_recipient( proxy ); } else { eosio_assert( producers.size() <= 30, "attempt to vote for too many producers" ); for( size_t i = 1; i < producers.size(); ++i ) { @@ -215,7 +148,7 @@ namespace eosiosystem { } } - auto voter = _voters.find( voter_name.value ); + auto voter = _voters.find(voter_name); eosio_assert( voter != _voters.end(), "user must stake before they can vote" ); /// staking creates voter object eosio_assert( !proxy || !voter->is_proxy, "account registered as a proxy is not allowed to use a proxy" ); @@ -226,10 +159,9 @@ namespace eosiosystem { */ if( voter->last_vote_weight <= 0.0 ) { _gstate.total_activated_stake += voter->staked; - /// modified - // if( _gstate.total_activated_stake >= min_activated_stake && _gstate.thresh_activated_stake_time == time_point() ) { - // _gstate.thresh_activated_stake_time = current_time_point(); - // } + if( _gstate.total_activated_stake >= min_activated_stake && _gstate.thresh_activated_stake_time == 0 ) { + _gstate.thresh_activated_stake_time = current_time(); + } } auto new_vote_weight = stake2vote( voter->staked ); @@ -237,12 +169,12 @@ namespace eosiosystem { new_vote_weight += voter->proxied_vote_weight; } - boost::container::flat_map > producer_deltas; + boost::container::flat_map > producer_deltas; if ( voter->last_vote_weight > 0 ) { if( voter->proxy ) { - auto old_proxy = _voters.find( voter->proxy.value ); + auto old_proxy = _voters.find( voter->proxy ); eosio_assert( old_proxy != _voters.end(), "old proxy not found" ); //data corruption - _voters.modify( old_proxy, same_payer, [&]( auto& vp ) { + _voters.modify( old_proxy, 0, [&]( auto& vp ) { vp.proxied_vote_weight -= voter->last_vote_weight; }); propagate_weight_change( *old_proxy ); @@ -256,11 +188,11 @@ namespace eosiosystem { } if( proxy ) { - auto new_proxy = _voters.find( proxy.value ); + auto new_proxy = _voters.find( proxy ); eosio_assert( new_proxy != _voters.end(), "invalid proxy specified" ); //if ( !voting ) { data corruption } else { wrong vote } eosio_assert( !voting || new_proxy->is_proxy, "proxy not found" ); if ( new_vote_weight >= 0 ) { - _voters.modify( new_proxy, same_payer, [&]( auto& vp ) { + _voters.modify( new_proxy, 0, [&]( auto& vp ) { vp.proxied_vote_weight += new_vote_weight; }); propagate_weight_change( *new_proxy ); @@ -275,15 +207,11 @@ namespace eosiosystem { } } - const auto ct = current_time_point(); - double delta_change_rate = 0.0; - double total_inactive_vpay_share = 0.0; for( const auto& pd : producer_deltas ) { - auto pitr = _producers.find( pd.first.value ); + auto pitr = _producers.find( pd.first ); if( pitr != _producers.end() ) { eosio_assert( !voting || pitr->active() || !pd.second.second /* not from new set */, "producer is not currently registered" ); - double init_total_votes = pitr->total_votes; - _producers.modify( pitr, same_payer, [&]( auto& p ) { + _producers.modify( pitr, 0, [&]( auto& p ) { p.total_votes += pd.second.first; if ( p.total_votes < 0 ) { // floating point arithmetics can give small negative numbers p.total_votes = 0; @@ -291,34 +219,12 @@ namespace eosiosystem { _gstate.total_producer_vote_weight += pd.second.first; //eosio_assert( p.total_votes >= 0, "something bad happened" ); }); - auto prod2 = _producers2.find( pd.first.value ); - if( prod2 != _producers2.end() ) { - const auto last_claim_plus_3days = pitr->last_claim_time + microseconds(3 * useconds_per_day); - bool crossed_threshold = (last_claim_plus_3days <= ct); - bool updated_after_threshold = (last_claim_plus_3days <= prod2->last_votepay_share_update); - // Note: updated_after_threshold implies cross_threshold - - double new_votepay_share = update_producer_votepay_share( prod2, - ct, - updated_after_threshold ? 0.0 : init_total_votes, - crossed_threshold && !updated_after_threshold // only reset votepay_share once after threshold - ); - - if( !crossed_threshold ) { - delta_change_rate += pd.second.first; - } else if( !updated_after_threshold ) { - total_inactive_vpay_share += new_votepay_share; - delta_change_rate -= init_total_votes; - } - } } else { eosio_assert( !pd.second.second /* not from new set */, "producer is not registered" ); //data corruption } } - update_total_votepay_share( ct, -total_inactive_vpay_share, delta_change_rate ); - - _voters.modify( voter, same_payer, [&]( auto& av ) { + _voters.modify( voter, 0, [&]( auto& av ) { av.last_vote_weight = new_vote_weight; av.producers = producers; av.proxy = proxy; @@ -334,14 +240,14 @@ namespace eosiosystem { * @pre proxy must have something staked (existing row in voters table) * @pre new state must be different than current state */ - void system_contract::regproxy( const name proxy, bool isproxy ) { + void system_contract::regproxy( const account_name proxy, bool isproxy ) { require_auth( proxy ); - auto pitr = _voters.find( proxy.value ); + auto pitr = _voters.find(proxy); if ( pitr != _voters.end() ) { eosio_assert( isproxy != pitr->is_proxy, "action has no effect" ); eosio_assert( !isproxy || !pitr->proxy, "account that uses a proxy is not allowed to become a proxy" ); - _voters.modify( pitr, same_payer, [&]( auto& p ) { + _voters.modify( pitr, 0, [&]( auto& p ) { p.is_proxy = isproxy; }); propagate_weight_change( *pitr ); @@ -354,7 +260,7 @@ namespace eosiosystem { } void system_contract::propagate_weight_change( const voter_info& voter ) { - eosio_assert( !voter.proxy || !voter.is_proxy, "account registered as a proxy is not allowed to use a proxy" ); + eosio_assert( voter.proxy == 0 || !voter.is_proxy, "account registered as a proxy is not allowed to use a proxy" ); double new_weight = stake2vote( voter.staked ); if ( voter.is_proxy ) { new_weight += voter.proxied_vote_weight; @@ -363,50 +269,24 @@ namespace eosiosystem { /// don't propagate small changes (1 ~= epsilon) if ( fabs( new_weight - voter.last_vote_weight ) > 1 ) { if ( voter.proxy ) { - auto& proxy = _voters.get( voter.proxy.value, "proxy not found" ); //data corruption - _voters.modify( proxy, same_payer, [&]( auto& p ) { + auto& proxy = _voters.get( voter.proxy, "proxy not found" ); //data corruption + _voters.modify( proxy, 0, [&]( auto& p ) { p.proxied_vote_weight += new_weight - voter.last_vote_weight; } ); propagate_weight_change( proxy ); } else { auto delta = new_weight - voter.last_vote_weight; - const auto ct = current_time_point(); - double delta_change_rate = 0; - double total_inactive_vpay_share = 0; for ( auto acnt : voter.producers ) { - auto& prod = _producers.get( acnt.value, "producer not found" ); //data corruption - const double init_total_votes = prod.total_votes; - _producers.modify( prod, same_payer, [&]( auto& p ) { - p.total_votes += delta; - _gstate.total_producer_vote_weight += delta; + auto& pitr = _producers.get( acnt, "producer not found" ); //data corruption + _producers.modify( pitr, 0, [&]( auto& p ) { + p.total_votes += delta; + _gstate.total_producer_vote_weight += delta; }); - auto prod2 = _producers2.find( acnt.value ); - if ( prod2 != _producers2.end() ) { - const auto last_claim_plus_3days = prod.last_claim_time + microseconds(3 * useconds_per_day); - bool crossed_threshold = (last_claim_plus_3days <= ct); - bool updated_after_threshold = (last_claim_plus_3days <= prod2->last_votepay_share_update); - // Note: updated_after_threshold implies cross_threshold - - double new_votepay_share = update_producer_votepay_share( prod2, - ct, - updated_after_threshold ? 0.0 : init_total_votes, - crossed_threshold && !updated_after_threshold // only reset votepay_share once after threshold - ); - - if( !crossed_threshold ) { - delta_change_rate += delta; - } else if( !updated_after_threshold ) { - total_inactive_vpay_share += new_votepay_share; - delta_change_rate -= init_total_votes; - } - } } - - update_total_votepay_share( ct, -total_inactive_vpay_share, delta_change_rate ); } } - _voters.modify( voter, same_payer, [&]( auto& v ) { + _voters.modify( voter, 0, [&]( auto& v ) { v.last_vote_weight = new_weight; } ); From 5d90ba1db647bc4bad914b32958fd436a6b5ddf6 Mon Sep 17 00:00:00 2001 From: deadlock Date: Fri, 22 Mar 2019 15:09:19 +0800 Subject: [PATCH 18/59] txn plugin now use system newaccount to create account and support custom core_symbol --- plugins/txn_test_gen_plugin/CMakeLists.txt | 2 +- plugins/txn_test_gen_plugin/README.md | 2 +- .../txn_test_gen_plugin.cpp | 122 ++++++++++++++---- 3 files changed, 101 insertions(+), 25 deletions(-) diff --git a/plugins/txn_test_gen_plugin/CMakeLists.txt b/plugins/txn_test_gen_plugin/CMakeLists.txt index e765f3478e6..286066d6149 100644 --- a/plugins/txn_test_gen_plugin/CMakeLists.txt +++ b/plugins/txn_test_gen_plugin/CMakeLists.txt @@ -5,6 +5,6 @@ add_library( txn_test_gen_plugin add_dependencies(txn_test_gen_plugin eosio.token) -target_link_libraries( txn_test_gen_plugin appbase fc http_plugin chain_plugin ) +target_link_libraries( txn_test_gen_plugin appbase fc http_plugin chain_plugin net_plugin) target_include_directories( txn_test_gen_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) target_include_directories( txn_test_gen_plugin PUBLIC ${CMAKE_BINARY_DIR}/contracts ) diff --git a/plugins/txn_test_gen_plugin/README.md b/plugins/txn_test_gen_plugin/README.md index 8d74e6a0412..3547a342eda 100644 --- a/plugins/txn_test_gen_plugin/README.md +++ b/plugins/txn_test_gen_plugin/README.md @@ -68,7 +68,7 @@ $ ./cleos set contract eosio ~/eos/build.release/contracts/eosio.bios/ ### Initialize the accounts txn_test_gen_plugin uses ```bash -$ curl --data-binary '["eosio", "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"]' http://127.0.0.1:8888/v1/txn_test_gen/create_test_accounts +$ curl --data-binary '["eosio", "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3", "EOS"]' http://127.0.0.1:8888/v1/txn_test_gen/create_test_accounts ``` ### Start transaction generation, this will submit 20 transactions evey 20ms (total of 1000TPS) diff --git a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp index 707f75bd9de..436be297d16 100644 --- a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp +++ b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp @@ -4,6 +4,7 @@ */ #include #include +#include #include #include @@ -24,6 +25,8 @@ #include #include +#include +#include namespace eosio { namespace detail { struct txn_test_gen_empty {}; @@ -82,9 +85,9 @@ using namespace eosio::chain; }\ } -#define INVOKE_ASYNC_R_R(api_handle, call_name, in_param0, in_param1) \ +#define INVOKE_ASYNC_R_R(api_handle, call_name, in_param0, in_param1, in_param2) \ const auto& vs = fc::json::json::from_string(body).as(); \ - api_handle->call_name(vs.at(0).as(), vs.at(1).as(), result_handler); + api_handle->call_name(vs.at(0).as(), vs.at(1).as(), vs.at(2).as(), result_handler); struct txn_test_gen_plugin_impl { @@ -93,6 +96,10 @@ struct txn_test_gen_plugin_impl { int _remain = 0; + std::string cached_salt; + uint64_t cached_period; + uint64_t cached_batch_size; + void push_next_transaction(const std::shared_ptr>& trxs, size_t index, const std::function& next ) { chain_plugin& cp = app().get_plugin(); @@ -126,14 +133,16 @@ struct txn_test_gen_plugin_impl { push_next_transaction(trxs_copy, 0, next); } - void create_test_accounts(const std::string& init_name, const std::string& init_priv_key, const std::function& next) { + void create_test_accounts(const std::string& init_name, const std::string& init_priv_key, + const std::string& core_symbol, + const std::function& next) { std::vector trxs; trxs.reserve(2); try { - name newaccountA("txn.test.a"); - name newaccountB("txn.test.b"); - name newaccountC("txn.test.t"); + name newaccountA("aaaaaaaaaaaa"); + name newaccountB("bbbbbbbbbbbb"); + name newaccountC("cccccccccccc"); name creator(init_name); abi_def currency_abi_def = fc::json::from_string(eosio_token_abi).as(); @@ -152,6 +161,10 @@ struct txn_test_gen_plugin_impl { fc::crypto::public_key txn_text_receiver_C_pub_key = txn_test_receiver_C_priv_key.get_public_key(); fc::crypto::private_key creator_priv_key = fc::crypto::private_key(init_priv_key); + eosio::chain::asset net{1000000, symbol(4,core_symbol.c_str())}; + eosio::chain::asset cpu{1000000, symbol(4,core_symbol.c_str())}; + eosio::chain::asset ram{1000000, symbol(4,core_symbol.c_str())}; + //create some test accounts { signed_transaction trx; @@ -162,6 +175,14 @@ struct txn_test_gen_plugin_impl { auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_A_pub_key, 1}}, {}}; trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountA, owner_auth, active_auth}); + + //delegate cpu net and buyram + auto act_delegatebw = create_action_delegatebw(creator, newaccountA,net,cpu,abi_serializer_max_time); + auto act_buyram = create_action_buyram(creator, newaccountA, ram, abi_serializer_max_time); + + trx.actions.emplace_back(act_delegatebw); + trx.actions.emplace_back(act_buyram); + } //create "B" account { @@ -169,13 +190,27 @@ struct txn_test_gen_plugin_impl { auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_B_pub_key, 1}}, {}}; trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountB, owner_auth, active_auth}); + + //delegate cpu net and buyram + auto act_delegatebw = create_action_delegatebw(creator, newaccountB,net,cpu,abi_serializer_max_time); + auto act_buyram = create_action_buyram(creator, newaccountB, ram, abi_serializer_max_time); + + trx.actions.emplace_back(act_delegatebw); + trx.actions.emplace_back(act_buyram); } - //create "txn.test.t" account + //create "cccccccccccc" account { auto owner_auth = eosio::chain::authority{1, {{txn_text_receiver_C_pub_key, 1}}, {}}; auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_C_pub_key, 1}}, {}}; trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountC, owner_auth, active_auth}); + + //delegate cpu net and buyram + auto act_delegatebw = create_action_delegatebw(creator, newaccountC,net,cpu,abi_serializer_max_time); + auto act_buyram = create_action_buyram(creator, newaccountC, ram, abi_serializer_max_time); + + trx.actions.emplace_back(act_delegatebw); + trx.actions.emplace_back(act_buyram); } trx.expiration = cc.head_block_time() + fc::seconds(30); @@ -184,7 +219,7 @@ struct txn_test_gen_plugin_impl { trxs.emplace_back(std::move(trx)); } - //set txn.test.t contract to eosio.token & initialize it + //set cccccccccccc contract to eosio.token & initialize it { signed_transaction trx; @@ -205,34 +240,34 @@ struct txn_test_gen_plugin_impl { { action act; - act.account = N(txn.test.t); + act.account = N(cccccccccccc); act.name = N(create); act.authorization = vector{{newaccountC,config::active_name}}; - act.data = eosio_token_serializer.variant_to_binary("create", fc::json::from_string("{\"issuer\":\"txn.test.t\",\"maximum_supply\":\"1000000000.0000 CUR\"}}"), abi_serializer_max_time); + act.data = eosio_token_serializer.variant_to_binary("create", fc::json::from_string("{\"issuer\":\"cccccccccccc\",\"maximum_supply\":\"1000000000.0000 CUR\"}}"), abi_serializer_max_time); trx.actions.push_back(act); } { action act; - act.account = N(txn.test.t); + act.account = N(cccccccccccc); act.name = N(issue); act.authorization = vector{{newaccountC,config::active_name}}; - act.data = eosio_token_serializer.variant_to_binary("issue", fc::json::from_string("{\"to\":\"txn.test.t\",\"quantity\":\"600.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); + act.data = eosio_token_serializer.variant_to_binary("issue", fc::json::from_string("{\"to\":\"cccccccccccc\",\"quantity\":\"600.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); trx.actions.push_back(act); } { action act; - act.account = N(txn.test.t); + act.account = N(cccccccccccc); act.name = N(transfer); act.authorization = vector{{newaccountC,config::active_name}}; - act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"txn.test.t\",\"to\":\"txn.test.a\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); + act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"cccccccccccc\",\"to\":\"aaaaaaaaaaaa\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); trx.actions.push_back(act); } { action act; - act.account = N(txn.test.t); + act.account = N(cccccccccccc); act.name = N(transfer); act.authorization = vector{{newaccountC,config::active_name}}; - act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"txn.test.t\",\"to\":\"txn.test.b\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); + act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"cccccccccccc\",\"to\":\"bbbbbbbbbbbb\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); trx.actions.push_back(act); } @@ -250,6 +285,36 @@ struct txn_test_gen_plugin_impl { push_transactions(std::move(trxs), next); } + eosio::chain::action create_action_delegatebw(const name &from, const name &to, const asset &net, const asset &cpu, const fc::microseconds &abi_serializer_max_time){ + fc::variant variant_delegate = fc::mutable_variant_object() + ("from", from.to_string()) + ("receiver", to.to_string()) + ("stake_net_quantity", net.to_string()) + ("stake_cpu_quantity", cpu.to_string()) + ("transfer", true); + abi_serializer eosio_system_serializer{fc::json::from_string(eosio_system_abi).as(), abi_serializer_max_time}; + + auto payload_delegate = eosio_system_serializer.variant_to_binary( "delegatebw", variant_delegate, abi_serializer_max_time); + eosio::chain::action act_delegate{vector{{from,"active"}}, + config::system_account_name, N(delegatebw), payload_delegate}; + + return act_delegate; + } + + eosio::chain::action create_action_buyram(const name &from, const name &to, const asset &quant, const fc::microseconds &abi_serializer_max_time){ + fc::variant variant_buyram = fc::mutable_variant_object() + ("payer", from.to_string()) + ("receiver", to.to_string()) + ("quant", quant.to_string()); + abi_serializer eosio_system_serializer{fc::json::from_string(eosio_system_abi).as(), abi_serializer_max_time}; + + auto payload_buyram = eosio_system_serializer.variant_to_binary( "buyram", variant_buyram, abi_serializer_max_time); + eosio::chain::action act_buyram{vector{{from,"active"}}, + config::system_account_name, N(buyram), payload_buyram}; + + return act_buyram; + } + void start_generation(const std::string& salt, const uint64_t& period, const uint64_t& batch_size) { if(running) throw fc::exception(fc::invalid_operation_exception_code); @@ -261,24 +326,27 @@ struct txn_test_gen_plugin_impl { throw fc::exception(fc::invalid_operation_exception_code); running = true; + cached_salt = salt; + cached_period = period; + cached_batch_size = batch_size; controller& cc = app().get_plugin().chain(); auto abi_serializer_max_time = app().get_plugin().get_abi_serializer_max_time(); abi_serializer eosio_token_serializer{fc::json::from_string(eosio_token_abi).as(), abi_serializer_max_time}; //create the actions here - act_a_to_b.account = N(txn.test.t); + act_a_to_b.account = N(cccccccccccc); act_a_to_b.name = N(transfer); - act_a_to_b.authorization = vector{{name("txn.test.a"),config::active_name}}; + act_a_to_b.authorization = vector{{name("aaaaaaaaaaaa"),config::active_name}}; act_a_to_b.data = eosio_token_serializer.variant_to_binary("transfer", - fc::json::from_string(fc::format_string("{\"from\":\"txn.test.a\",\"to\":\"txn.test.b\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", + fc::json::from_string(fc::format_string("{\"from\":\"aaaaaaaaaaaa\",\"to\":\"bbbbbbbbbbbb\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", fc::mutable_variant_object()("l", salt))), abi_serializer_max_time); - act_b_to_a.account = N(txn.test.t); + act_b_to_a.account = N(cccccccccccc); act_b_to_a.name = N(transfer); - act_b_to_a.authorization = vector{{name("txn.test.b"),config::active_name}}; + act_b_to_a.authorization = vector{{name("bbbbbbbbbbbb"),config::active_name}}; act_b_to_a.data = eosio_token_serializer.variant_to_binary("transfer", - fc::json::from_string(fc::format_string("{\"from\":\"txn.test.b\",\"to\":\"txn.test.a\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", + fc::json::from_string(fc::format_string("{\"from\":\"bbbbbbbbbbbb\",\"to\":\"aaaaaaaaaaaa\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", fc::mutable_variant_object()("l", salt))), abi_serializer_max_time); @@ -300,6 +368,14 @@ struct txn_test_gen_plugin_impl { if (e) { elog("pushing transaction failed: ${e}", ("e", e->to_detail_string())); stop_generation(); + auto peers_conn = app().get_plugin().connections(); + for(const auto c : peers_conn){ + app().get_plugin().disconnect(c.peer); + } + for(const auto c : peers_conn){ + app().get_plugin().connect(c.peer); + } + start_generation(cached_salt,cached_period,cached_batch_size); } else { arm_timer(timer.expires_at()); } @@ -405,7 +481,7 @@ void txn_test_gen_plugin::plugin_initialize(const variables_map& options) { void txn_test_gen_plugin::plugin_startup() { app().get_plugin().add_api({ - CALL_ASYNC(txn_test_gen, my, create_test_accounts, INVOKE_ASYNC_R_R(my, create_test_accounts, std::string, std::string), 200), + CALL_ASYNC(txn_test_gen, my, create_test_accounts, INVOKE_ASYNC_R_R(my, create_test_accounts, std::string, std::string, std::string), 200), CALL(txn_test_gen, my, stop_generation, INVOKE_V_V(my, stop_generation), 200), CALL(txn_test_gen, my, start_generation, INVOKE_V_R_R_R(my, start_generation, std::string, uint64_t, uint64_t), 200) }); From 434385fe2f7333a644e4ab42db10c32b6de0407a Mon Sep 17 00:00:00 2001 From: deadlock Date: Thu, 11 Apr 2019 01:55:21 +0800 Subject: [PATCH 19/59] try to fix gpo propose block num inconsistent bug --- libraries/chain/controller.cpp | 19 +++++++++++++++---- plugins/pbft_plugin/pbft_plugin.cpp | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index d77d5b4d7e8..437c0a7cee4 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1293,12 +1293,21 @@ struct controller_impl { } } - bool should_promote_pending_schedule = gpo.proposed_schedule_block_num.valid() // if there is a proposed schedule that was proposed in a block ... - && pending->_pending_block_state->pending_schedule.producers.size() == 0 // ... and there is room for a new pending schedule ... - && !was_pending_promoted; // ... and not just because it was promoted to active at the start of this block, then: + bool should_promote_pending_schedule = false; + + if(new_version && (gpo.proposed_schedule_block_num.valid() && pending->_pending_block_state->bft_irreversible_blocknum > *gpo.proposed_schedule_block_num)){ + db.modify( gpo, [&]( auto& gp ) { + gp.proposed_schedule_block_num = optional(); + gp.proposed_schedule.clear(); + }); + } else{ + should_promote_pending_schedule = gpo.proposed_schedule_block_num.valid() // if there is a proposed schedule that was proposed in a block ... + && pending->_pending_block_state->pending_schedule.producers.size() == 0 // ... and there is room for a new pending schedule ... + && !was_pending_promoted; // ... and not just because it was promoted to active at the start of this block, then: + } if (new_version) { - should_promote_pending_schedule = should_promote_pending_schedule && pending->_pending_block_state->block_num > *gpo.proposed_schedule_block_num; + should_promote_pending_schedule = should_promote_pending_schedule && (pending->_pending_block_state->block_num > *gpo.proposed_schedule_block_num); } else { should_promote_pending_schedule = should_promote_pending_schedule && ( *gpo.proposed_schedule_block_num <= pending->_pending_block_state->dpos_irreversible_blocknum ); } @@ -1308,6 +1317,8 @@ struct controller_impl { if (!upgrading) { // Promote proposed schedule to pending schedule. if (!replaying) { + ilog("bft_irreversible_blocknum ${a}", ("a", pending->_pending_block_state->bft_irreversible_blocknum) ); + ilog("dpos_irreversible_blocknum ${a}", ("a", pending->_pending_block_state->dpos_irreversible_blocknum) ); ilog("promoting proposed schedule (set in block ${proposed_num}) to pending; current block: ${n} lib: ${lib} schedule: ${schedule} ", ("proposed_num", *gpo.proposed_schedule_block_num)("n", pending->_pending_block_state->block_num) diff --git a/plugins/pbft_plugin/pbft_plugin.cpp b/plugins/pbft_plugin/pbft_plugin.cpp index a5caa667691..3b1762ba744 100644 --- a/plugins/pbft_plugin/pbft_plugin.cpp +++ b/plugins/pbft_plugin/pbft_plugin.cpp @@ -137,7 +137,7 @@ namespace eosio { if (new_version && !upgraded) { wlog( "\n" - "*********** PBFT ENABLED ***********\n" + "******** BATCH-PBFT ENABLED ********\n" "* *\n" "* -- The blockchain -- *\n" "* - has successfully switched - *\n" From e61b5332edc7bf98f6406a00150096ad19a9d28f Mon Sep 17 00:00:00 2001 From: deadlock Date: Fri, 12 Apr 2019 12:57:22 +0800 Subject: [PATCH 20/59] solve consensus chosen conflict when upgrade --- libraries/chain/controller.cpp | 14 +++++++++++--- .../eosio/chain/global_property_object.hpp | 3 ++- libraries/chain/pbft_database.cpp | 15 ++++++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index d77d5b4d7e8..7572ff45020 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1259,9 +1259,17 @@ struct controller_impl { auto upgrading = false; try { - const auto& upo = db.get().upgrade_target_block_num; - upgrading = (head->block_num + 1 >= upo) - && std::max(head->dpos_irreversible_blocknum, head->bft_irreversible_blocknum) <= upo + 12; + const auto& upo = db.get(); + const auto upo_upgrade_target_block_num = upo.upgrade_target_block_num; + upgrading = (head->block_num + 1 >= upo_upgrade_target_block_num) + && std::max(head->dpos_irreversible_blocknum, head->bft_irreversible_blocknum) <= upo_upgrade_target_block_num + 12; + if(upgrading){ + if(head->dpos_irreversible_blocknum >= upo_upgrade_target_block_num){ + db.modify( upo, [&]( auto& up ) { + up.upgrade_complete_block_num.emplace(head->block_num); + }); + } + } } catch( const boost::exception& e) { db.create([](auto&){}); } diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index 4325ec0433e..7627a40adb5 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -51,6 +51,7 @@ namespace eosio { namespace chain { id_type id; block_num_type upgrade_target_block_num = 0; + optional upgrade_complete_block_num; }; @@ -128,5 +129,5 @@ FC_REFLECT(eosio::chain::global_property2_object, (cfg)(gmr) ) FC_REFLECT(eosio::chain::upgrade_property_object, - (upgrade_target_block_num) + (upgrade_target_block_num)(upgrade_complete_block_num) ) \ No newline at end of file diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index aa8d9eff921..4c24e0de37a 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -935,12 +935,21 @@ namespace eosio { block_num_type my_latest_checkpoint = 0; + const auto& upo = ctrl.get_upgrade_properties(); auto checkpoint = [&](const block_num_type &in) { - return in % 100 == 1 - || in == ctrl.last_proposed_schedule_block_num() - || in == ctrl.last_promoted_proposed_schedule_block_num(); + auto is_desired_checkpoint_num = in % 100 == 1 + || in == ctrl.last_proposed_schedule_block_num() + || in == ctrl.last_promoted_proposed_schedule_block_num(); + if (upo.upgrade_complete_block_num) is_desired_checkpoint_num = is_desired_checkpoint_num && in > upo.upgrade_complete_block_num; + return is_desired_checkpoint_num; }; +// auto checkpoint = [&](const block_num_type &in) { +// return in % 100 == 1 +// || in == ctrl.last_proposed_schedule_block_num() +// || in == ctrl.last_promoted_proposed_schedule_block_num(); +// }; + for (auto i = psp->block_num; i > std::max(ctrl.last_stable_checkpoint_block_num(), static_cast(1)); --i) { if (checkpoint(i)) { From 4ab5c0f46fdd81a260433a0de1f3a8d0c04e5743 Mon Sep 17 00:00:00 2001 From: deadlock Date: Fri, 12 Apr 2019 14:55:27 +0800 Subject: [PATCH 21/59] bug fix: shouldn't print upgrade log when chain is fresh started --- libraries/chain/controller.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 7572ff45020..0d098c3eaa2 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1261,9 +1261,12 @@ struct controller_impl { try { const auto& upo = db.get(); const auto upo_upgrade_target_block_num = upo.upgrade_target_block_num; - upgrading = (head->block_num + 1 >= upo_upgrade_target_block_num) + upgrading = + upo_upgrade_target_block_num > 0 + &&(head->block_num + 1 >= upo_upgrade_target_block_num) && std::max(head->dpos_irreversible_blocknum, head->bft_irreversible_blocknum) <= upo_upgrade_target_block_num + 12; if(upgrading){ + ilog("SYSTEM IS UPGRADING, no producer schedule changes will happen until fully upgraded."); if(head->dpos_irreversible_blocknum >= upo_upgrade_target_block_num){ db.modify( upo, [&]( auto& up ) { up.upgrade_complete_block_num.emplace(head->block_num); @@ -1274,10 +1277,6 @@ struct controller_impl { db.create([](auto&){}); } - if (upgrading) { - ilog("SYSTEM IS UPGRADING, no producer schedule changes will happen until fully upgraded."); - } - pending->_block_status = s; pending->_producer_block_id = producer_block_id; From 2d60f5825dff5d8397c2e64481a97ec809899d43 Mon Sep 17 00:00:00 2001 From: oldcold Date: Mon, 15 Apr 2019 09:53:29 +0800 Subject: [PATCH 22/59] reject target setting when it is upgrading or has just upgraded. --- libraries/chain/controller.cpp | 32 +++++++++++++------ .../chain/include/eosio/chain/controller.hpp | 1 + libraries/chain/wasm_interface.cpp | 4 +++ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 1d75e6a11b0..5021546bb34 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -789,6 +789,19 @@ struct controller_impl { } } + bool is_upgrading() { + try { + const auto& upo = db.get(); + const auto upo_upgrade_target_block_num = upo.upgrade_target_block_num; + return upo_upgrade_target_block_num > 0 + &&(head->block_num + 1 >= upo_upgrade_target_block_num) + && std::max(head->dpos_irreversible_blocknum, head->bft_irreversible_blocknum) <= upo_upgrade_target_block_num + 12; + } catch( const boost::exception& e) { + db.create([](auto&){}); + return false; + } + } + /** * @post regardless of the success of commit block there is no active pending block */ @@ -1256,21 +1269,16 @@ struct controller_impl { } auto new_version = is_new_version(); - auto upgrading = false; + auto upgrading = is_upgrading(); try { const auto& upo = db.get(); const auto upo_upgrade_target_block_num = upo.upgrade_target_block_num; - upgrading = - upo_upgrade_target_block_num > 0 - &&(head->block_num + 1 >= upo_upgrade_target_block_num) - && std::max(head->dpos_irreversible_blocknum, head->bft_irreversible_blocknum) <= upo_upgrade_target_block_num + 12; - if(upgrading){ + + if (upgrading) { ilog("SYSTEM IS UPGRADING, no producer schedule changes will happen until fully upgraded."); - if(head->dpos_irreversible_blocknum >= upo_upgrade_target_block_num){ - db.modify( upo, [&]( auto& up ) { - up.upgrade_complete_block_num.emplace(head->block_num); - }); + if (head->dpos_irreversible_blocknum >= upo_upgrade_target_block_num) { + db.modify( upo, [&]( auto& up ) { up.upgrade_complete_block_num.emplace(head->block_num); }); } } } catch( const boost::exception& e) { @@ -2665,4 +2673,8 @@ const upgrade_property_object& controller::get_upgrade_properties()const { bool controller::is_upgraded() const { return my->is_new_version(); } + +bool controller::under_upgrade() const { + return my->is_upgrading(); +} } } /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index c8d89f448c7..d0695629835 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -309,6 +309,7 @@ namespace eosio { namespace chain { const upgrade_property_object& get_upgrade_properties()const; bool is_upgraded()const; + bool under_upgrade()const; /* signal pre_apply_block; diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index a1ac695140b..bc0a764ed10 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -200,6 +200,10 @@ class privileged_api : public context_aware_api { EOS_ASSERT( context.control.head_block_num() < target_num - 100, wasm_execution_error, "upgrade target block is too close"); + EOS_ASSERT( !context.control.is_upgraded(), wasm_execution_error, "the system has already upgraded to the new version"); + + EOS_ASSERT( !context.control.under_upgrade(), wasm_execution_error, "the system is currently under upgrade"); + context.db.modify( context.control.get_upgrade_properties(), [&]( auto& uprops ) { uprops.upgrade_target_block_num = target_num; From 83427a19171a40a9773cf4b9f4e2aa9f7dbf3ff9 Mon Sep 17 00:00:00 2001 From: oldcold Date: Mon, 15 Apr 2019 16:15:44 +0800 Subject: [PATCH 23/59] add new view related log. --- libraries/chain/pbft_database.cpp | 65 ++++++++++++++++++++++++------- plugins/net_plugin/net_plugin.cpp | 3 +- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index 4c24e0de37a..2b51c33ee7d 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -733,23 +733,53 @@ namespace eosio { bool pbft_database::is_valid_new_view(const pbft_new_view &nv) { //all signatures should be valid - if (nv.chain_id != chain_id()) return false; - - auto valid = is_valid_prepared_certificate(nv.prepared) - && is_valid_stable_checkpoint(nv.stable_checkpoint) - && nv.view_changed.is_signature_valid() - && nv.is_signature_valid(); - if (!valid) return false; - if (nv.view_changed.view != nv.view) return false; + if (nv.chain_id != chain_id()) { + wlog("wrong chain id in new view msg"); + return false; + } + + if (!is_valid_prepared_certificate(nv.prepared)) { + wlog("prepared certificate invalid in new view msg"); + return false; + } + + if (!is_valid_stable_checkpoint(nv.stable_checkpoint)) { + wlog("stable checkpoint invalid in new view msg"); + return false; + } + + if (!nv.view_changed.is_signature_valid()) { + wlog("view changed sig invalid in new view msg"); + return false; + } + + if (!nv.is_signature_valid()) { + wlog("new view sig invalid in new view msg"); + return false; + } + + if (nv.view_changed.view != nv.view) { + wlog("target view not match"); + return false; + } auto schedule_threshold = lib_active_producers().producers.size() * 2 / 3 + 1; - if (nv.view_changed.view_changes.size() < schedule_threshold) return false; + if (nv.view_changed.view_changes.size() < schedule_threshold) { + wlog("view change count not enough"); + return false; + } for (auto vc: nv.view_changed.view_changes) { - if (!is_valid_view_change(vc)) return false; + if (!is_valid_view_change(vc)) { + wlog("invalid view change msg ${m}", ("m", vc)); + return false; + } add_pbft_view_change(vc); } - if (!should_new_view(nv.view)) return false; + if (!should_new_view(nv.view)) { + wlog("should not new view"); + return false; + } auto highest_ppc = pbft_prepared_certificate{}; auto highest_scp = pbft_stable_checkpoint{}; @@ -766,8 +796,17 @@ namespace eosio { } } - return highest_ppc == nv.prepared - && highest_scp == nv.stable_checkpoint; + if (highest_ppc != nv.prepared) { + wlog("prepared num not match"); + return false; + } + + if (highest_scp != nv.stable_checkpoint) { + wlog("stable checkpoint not match"); + return false; + } + + return true; } bool pbft_database::should_stop_view_change(const pbft_view_change &vc) { diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 5c00c9c29f0..43a27ea8ace 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3077,6 +3077,7 @@ namespace eosio { } void net_plugin_impl::handle_message( connection_ptr c, const pbft_new_view &msg) { + ilog( "received new view: ${n}, from ${v}", ("n", msg)("v", msg.public_key)); if (!is_pbft_msg_valid(msg)) return; @@ -3087,7 +3088,7 @@ namespace eosio { if (!pcc.pbft_db.is_valid_new_view(msg)) return; forward_pbft_msg(c, msg); - fc_ilog( logger, "received new view at ${n}, from ${v}", ("n", msg)("v", msg.public_key)); + ilog( "forwarded new view: ${n}, from ${v}", ("n", msg)("v", msg.public_key)); pbft_incoming_new_view_channel.publish(msg); } From c9cfa3e8b31ad3e3ae82e379e82956d89720b9af Mon Sep 17 00:00:00 2001 From: oldcold Date: Mon, 15 Apr 2019 16:35:44 +0800 Subject: [PATCH 24/59] add new view sending related log. --- plugins/net_plugin/net_plugin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 43a27ea8ace..a86502dfe71 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -2986,6 +2986,7 @@ namespace eosio { } void net_plugin_impl::pbft_outgoing_new_view(const pbft_new_view &msg) { + ilog( "attempt to send new view: ${n}, from ${v}", ("n", msg)("v", msg.public_key)); auto added = maybe_add_pbft_cache(msg.uuid); if (!added) return; @@ -2993,6 +2994,7 @@ namespace eosio { if (!pcc.pbft_db.is_valid_new_view(msg)) return; bcast_pbft_msg(msg); + ilog( "sent new view: ${n}, from ${v}", ("n", msg)("v", msg.public_key)); } void net_plugin_impl::pbft_outgoing_checkpoint(const pbft_checkpoint &msg) { From 387d55a447b0d15c066ba28023fbba4a877346a0 Mon Sep 17 00:00:00 2001 From: oldcold Date: Tue, 16 Apr 2019 20:58:04 +0800 Subject: [PATCH 25/59] add new view transition raw bytes. --- plugins/net_plugin/net_plugin.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index a86502dfe71..03929570d74 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -1410,6 +1410,11 @@ namespace eosio { msg.visit( m ); } } catch( const fc::exception& e ) { + auto ds = pending_message_buffer.create_datastream(); + vector v{}; + v.resize(pending_message_buffer.bytes_to_read()); + ds.read(v.data(), v.size()); + wlog("error ds ${s}", ("s", v)); edump((e.to_detail_string() )); impl.close( shared_from_this() ); return false; @@ -2046,6 +2051,10 @@ namespace eosio { ds.write( header, header_size ); fc::raw::pack( ds, msg ); + if (msg.contains()) { + wlog("new view chars ${s}", ("s", send_buffer)); + } + return send_buffer; } From 1f27be445a17fa363bb5f3f9686ad7193b16aa33 Mon Sep 17 00:00:00 2001 From: oldcold Date: Wed, 17 Apr 2019 12:37:58 +0800 Subject: [PATCH 26/59] print all error raw bytes. --- plugins/net_plugin/net_plugin.cpp | 77 +++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 03929570d74..2f0343bc837 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -1395,32 +1395,59 @@ namespace eosio { sync_wait(); } + bool connection::process_next_message(net_plugin_impl& impl, uint32_t message_length) { + vector tmp_data; + tmp_data.resize(message_length); + + try { + auto ds = pending_message_buffer.create_datastream(); + auto read_index = pending_message_buffer.read_index(); + pending_message_buffer.peek(tmp_data.data(),message_length,read_index); + + net_message msg; + fc::raw::unpack(ds, msg); + msg_handler m(impl, shared_from_this() ); + if( msg.contains() ) { + m( std::move( msg.get() ) ); + } else if( msg.contains() ) { + m( std::move( msg.get() ) ); + } else { + msg.visit( m ); + } + } catch( const fc::exception& e ) { + wlog("error ds ${s}", ("s", tmp_data)); + edump((e.to_detail_string() )); + impl.close( shared_from_this() ); + return false; + } + return true; + } - bool connection::process_next_message(net_plugin_impl& impl, uint32_t message_length) { - try { - auto ds = pending_message_buffer.create_datastream(); - net_message msg; - fc::raw::unpack(ds, msg); - msg_handler m(impl, shared_from_this() ); - if( msg.contains() ) { - m( std::move( msg.get() ) ); - } else if( msg.contains() ) { - m( std::move( msg.get() ) ); - } else { - msg.visit( m ); - } - } catch( const fc::exception& e ) { - auto ds = pending_message_buffer.create_datastream(); - vector v{}; - v.resize(pending_message_buffer.bytes_to_read()); - ds.read(v.data(), v.size()); - wlog("error ds ${s}", ("s", v)); - edump((e.to_detail_string() )); - impl.close( shared_from_this() ); - return false; - } - return true; - } +// bool connection::process_next_message(net_plugin_impl& impl, uint32_t message_length) { +// auto ds = pending_message_buffer.create_datastream(); +// auto cp_ds = ds; +// try { +// net_message msg; +// fc::raw::unpack(ds, msg); +// msg_handler m(impl, shared_from_this() ); +// if( msg.contains() ) { +// m( std::move( msg.get() ) ); +// } else if( msg.contains() ) { +// m( std::move( msg.get() ) ); +// } else { +// msg.visit( m ); +// } +// } catch( const fc::exception& e ) { +// vector v{}; +// v.resize(message_length); +// cp_ds.read(v.data(), v.size()); +// wlog("error ds ${s}", ("s", v)); +// edump((e.to_detail_string() )); +// impl.close( shared_from_this() ); +// return false; +// } +// return true; +// } bool connection::add_peer_block(const peer_block_state& entry) { auto bptr = blk_state.get().find(entry.id); From 9ebb308a3fdb9d8fb68043bf48e009e7fe7572c1 Mon Sep 17 00:00:00 2001 From: oldcold Date: Wed, 17 Apr 2019 14:04:38 +0800 Subject: [PATCH 27/59] add error net message length. --- plugins/net_plugin/net_plugin.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 2f0343bc837..31c80dfc0b0 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -1415,7 +1415,8 @@ namespace eosio { msg.visit( m ); } } catch( const fc::exception& e ) { - wlog("error ds ${s}", ("s", tmp_data)); + wlog("error message length: ${l}", ("l", message_length)); + wlog("error raw bytes ${s}", ("s", tmp_data)); edump((e.to_detail_string() )); impl.close( shared_from_this() ); return false; From e87b2d1b23c68ad4fcef2650987aa64ba3540d43 Mon Sep 17 00:00:00 2001 From: oldcold Date: Wed, 17 Apr 2019 16:57:03 +0800 Subject: [PATCH 28/59] move local buf vector into connection --- plugins/net_plugin/net_plugin.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 31c80dfc0b0..6e9a4e1a867 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -582,6 +582,7 @@ namespace eosio { deque pbft_queue; queued_buffer buffer_queue; + std::vector bufs; uint32_t reads_in_flight = 0; uint32_t trx_in_progress_size = 0; @@ -1108,7 +1109,7 @@ namespace eosio { my_impl->close(c.lock()); return; } - std::vector bufs; +// std::vector bufs; buffer_queue.fill_out_buffer( bufs ); do_queue_write_from_pbft_queue( bufs ); @@ -1133,6 +1134,7 @@ namespace eosio { return; } conn->buffer_queue.clear_out_queue(); + conn->bufs.clear(); conn->enqueue_sync_block(); conn->do_queue_write(); } From d11e76eb99b5c96e6e0cf7eb9072fa2526c3b32b Mon Sep 17 00:00:00 2001 From: oldcold Date: Wed, 17 Apr 2019 18:11:38 +0800 Subject: [PATCH 29/59] Revert "move local buf vector into connection" This reverts commit e87b2d1b --- plugins/net_plugin/net_plugin.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 6e9a4e1a867..31c80dfc0b0 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -582,7 +582,6 @@ namespace eosio { deque pbft_queue; queued_buffer buffer_queue; - std::vector bufs; uint32_t reads_in_flight = 0; uint32_t trx_in_progress_size = 0; @@ -1109,7 +1108,7 @@ namespace eosio { my_impl->close(c.lock()); return; } -// std::vector bufs; + std::vector bufs; buffer_queue.fill_out_buffer( bufs ); do_queue_write_from_pbft_queue( bufs ); @@ -1134,7 +1133,6 @@ namespace eosio { return; } conn->buffer_queue.clear_out_queue(); - conn->bufs.clear(); conn->enqueue_sync_block(); conn->do_queue_write(); } From 6f0f7b9b4f0fdc0cb8d0cdd5674ffb95a6b01787 Mon Sep 17 00:00:00 2001 From: oldcold Date: Wed, 17 Apr 2019 20:44:57 +0800 Subject: [PATCH 30/59] add new view transition raw bytes. --- plugins/net_plugin/net_plugin.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 31c80dfc0b0..66d144dbc2e 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -552,6 +552,11 @@ namespace eosio { deque _sync_write_queue; // sync_write_queue will be sent first deque _out_queue; + public: + void push_to_out_queue( const queued_write& m) { + _out_queue.emplace_back( m ); + } + }; // queued_buffer @@ -1193,7 +1198,7 @@ namespace eosio { auto m = pbft.message; if (m) { bufs.push_back(boost::asio::buffer(*m)); - buffer_queue.add_write_queue( m, callback, true ); + buffer_queue.push_to_out_queue( {m, callback} ); } } } From 58e5f39bee3afe2bb03347c520df873bc414f89b Mon Sep 17 00:00:00 2001 From: oldcold Date: Thu, 18 Apr 2019 11:53:50 +0800 Subject: [PATCH 31/59] reformat some functions --- plugins/net_plugin/net_plugin.cpp | 149 +++++++++++++----------------- 1 file changed, 62 insertions(+), 87 deletions(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 66d144dbc2e..cc9c5cd8298 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -553,6 +553,7 @@ namespace eosio { deque _out_queue; public: + //used for pbft msgs sending only void push_to_out_queue( const queued_write& m) { _out_queue.emplace_back( m ); } @@ -678,6 +679,8 @@ namespace eosio { bool trigger_send, go_away_reason close_after_send, bool to_sync_queue = false); void enqueue_pbft( const std::shared_ptr>& m, const time_point_sec deadline); + bool pbft_read_to_send(); + void cancel_sync(go_away_reason); void flush_queues(); bool enqueue_sync_block(); @@ -695,7 +698,7 @@ namespace eosio { std::function callback, bool to_sync_queue = false); void do_queue_write(); - void do_queue_write_from_pbft_queue(std::vector &bufs); + void fill_out_buffer_with_pbft_queue(std::vector &bufs); void send_p2p_request(bool discoverable); void send_p2p_response(bool discoverable,string p2p_peer_list); @@ -1103,8 +1106,12 @@ namespace eosio { } } + bool connection::pbft_read_to_send() { + return !pbft_queue.empty() && buffer_queue.is_out_queue_empty(); + } + void connection::do_queue_write() { - if( !(buffer_queue.ready_to_send() || (!pbft_queue.empty() && buffer_queue.is_out_queue_empty()))) + if( !(buffer_queue.ready_to_send() || pbft_read_to_send()) ) return; connection_wptr c(shared_from_this()); @@ -1114,9 +1121,9 @@ namespace eosio { return; } std::vector bufs; - buffer_queue.fill_out_buffer( bufs ); - do_queue_write_from_pbft_queue( bufs ); + buffer_queue.fill_out_buffer( bufs ); + fill_out_buffer_with_pbft_queue( bufs ); boost::asio::async_write(*socket, bufs, [c](boost::system::error_code ec, std::size_t w) { try { @@ -1159,7 +1166,7 @@ namespace eosio { }); } - void connection::do_queue_write_from_pbft_queue(std::vector &bufs){ + void connection::fill_out_buffer_with_pbft_queue(std::vector &bufs){ //delete timeout pbft message auto now = time_point::now(); int drop_pbft_count = 0; @@ -1173,7 +1180,7 @@ namespace eosio { } //drop timeout messages in mem, init send buffer only when actual send happens - //copied from function connection::enqueue + //copied from a previous version of connection::enqueue connection_wptr weak_this = shared_from_this(); go_away_reason close_after_send = no_reason; std::function callback = [weak_this, close_after_send](boost::system::error_code ec, std::size_t ) { @@ -1190,7 +1197,7 @@ namespace eosio { }; //push to out queue - while (buffer_queue.out_queue_size() < OUT_QUEUE_SIZE_LIMIT){ + while (buffer_queue.out_queue_size() < OUT_QUEUE_SIZE_LIMIT) { if (pbft_queue.empty()) break; queued_pbft_message pbft = pbft_queue.front(); @@ -1231,14 +1238,9 @@ namespace eosio { } try { controller& cc = my_impl->chain_plug->chain(); - pbft_controller& pcc = my_impl->chain_plug->pbft_ctrl(); signed_block_ptr sb = cc.fetch_block_by_number(num); if(sb) { enqueue_block( sb, trigger_send, true); - auto scp = pcc.pbft_db.get_stable_checkpoint_by_id((*sb).id()); - if (!(scp == pbft_stable_checkpoint{})) { - enqueue(scp); - } return true; } } catch ( ... ) { @@ -1400,61 +1402,56 @@ namespace eosio { sync_wait(); } - bool connection::process_next_message(net_plugin_impl& impl, uint32_t message_length) { - vector tmp_data; - tmp_data.resize(message_length); - - try { - auto ds = pending_message_buffer.create_datastream(); - auto read_index = pending_message_buffer.read_index(); - pending_message_buffer.peek(tmp_data.data(),message_length,read_index); - - net_message msg; - fc::raw::unpack(ds, msg); - msg_handler m(impl, shared_from_this() ); - if( msg.contains() ) { - m( std::move( msg.get() ) ); - } else if( msg.contains() ) { - m( std::move( msg.get() ) ); - } else { - msg.visit( m ); - } - } catch( const fc::exception& e ) { - wlog("error message length: ${l}", ("l", message_length)); - wlog("error raw bytes ${s}", ("s", tmp_data)); - edump((e.to_detail_string() )); - impl.close( shared_from_this() ); - return false; - } - return true; +// bool connection::process_next_message(net_plugin_impl& impl, uint32_t message_length) { +// vector tmp_data; +// tmp_data.resize(message_length); +// +// try { +// auto ds = pending_message_buffer.create_datastream(); +// auto read_index = pending_message_buffer.read_index(); +// pending_message_buffer.peek(tmp_data.data(),message_length,read_index); +// +// net_message msg; +// fc::raw::unpack(ds, msg); +// msg_handler m(impl, shared_from_this() ); +// if( msg.contains() ) { +// m( std::move( msg.get() ) ); +// } else if( msg.contains() ) { +// m( std::move( msg.get() ) ); +// } else { +// msg.visit( m ); +// } +// } catch( const fc::exception& e ) { +// wlog("error message length: ${l}", ("l", message_length)); +// wlog("error raw bytes ${s}", ("s", tmp_data)); +// edump((e.to_detail_string() )); +// impl.close( shared_from_this() ); +// return false; +// } +// return true; +// } + + bool connection::process_next_message(net_plugin_impl& impl, uint32_t message_length) { + try { + auto ds = pending_message_buffer.create_datastream(); + net_message msg; + fc::raw::unpack(ds, msg); + msg_handler m(impl, shared_from_this() ); + if( msg.contains() ) { + m( std::move( msg.get() ) ); + } else if( msg.contains() ) { + m( std::move( msg.get() ) ); + } else { + msg.visit( m ); + } + } catch( const fc::exception& e ) { + edump((e.to_detail_string() )); + impl.close( shared_from_this() ); + return false; + } + return true; } -// bool connection::process_next_message(net_plugin_impl& impl, uint32_t message_length) { -// auto ds = pending_message_buffer.create_datastream(); -// auto cp_ds = ds; -// try { -// net_message msg; -// fc::raw::unpack(ds, msg); -// msg_handler m(impl, shared_from_this() ); -// if( msg.contains() ) { -// m( std::move( msg.get() ) ); -// } else if( msg.contains() ) { -// m( std::move( msg.get() ) ); -// } else { -// msg.visit( m ); -// } -// } catch( const fc::exception& e ) { -// vector v{}; -// v.resize(message_length); -// cp_ds.read(v.data(), v.size()); -// wlog("error ds ${s}", ("s", v)); -// edump((e.to_detail_string() )); -// impl.close( shared_from_this() ); -// return false; -// } -// return true; -// } - bool connection::add_peer_block(const peer_block_state& entry) { auto bptr = blk_state.get().find(entry.id); bool added = (bptr == blk_state.end()); @@ -2958,17 +2955,6 @@ namespace eosio { auto deadline = time_point_sec(time_point::now()) + pbft_message_TTL; -// uint32_t payload_size = fc::raw::pack_size( msg ); -// -// char* header = reinterpret_cast(&payload_size); -// size_t header_size = sizeof(payload_size); -// size_t buffer_size = header_size + payload_size; -// -// auto send_buffer = std::make_shared>(buffer_size); -// fc::datastream ds( send_buffer->data(), buffer_size); -// ds.write( header, header_size ); -// fc::raw::pack( ds, msg ); - for (auto &conn: connections) { if (conn->pbft_ready()) { conn->enqueue_pbft(encode_pbft_message(msg), deadline); @@ -2979,17 +2965,6 @@ namespace eosio { void net_plugin_impl::forward_pbft_msg(connection_ptr c, const net_message &msg) { auto deadline = time_point_sec(time_point::now()) + pbft_message_TTL; -// uint32_t payload_size = fc::raw::pack_size( msg ); -// -// char* header = reinterpret_cast(&payload_size); -// size_t header_size = sizeof(payload_size); -// size_t buffer_size = header_size + payload_size; -// -// auto send_buffer = std::make_shared>(buffer_size); -// fc::datastream ds( send_buffer->data(), buffer_size); -// ds.write( header, header_size ); -// fc::raw::pack( ds, msg ); - for (auto &conn: connections) { if (conn != c && conn->pbft_ready()) { conn->enqueue_pbft(encode_pbft_message(msg), deadline); From 1b1681111ebffaa29c2e5b4014bff3aa97e127cc Mon Sep 17 00:00:00 2001 From: oldcold Date: Thu, 18 Apr 2019 12:01:56 +0800 Subject: [PATCH 32/59] remove debug logs. --- plugins/net_plugin/net_plugin.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index cc9c5cd8298..c41f85f505c 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -2081,10 +2081,6 @@ namespace eosio { ds.write( header, header_size ); fc::raw::pack( ds, msg ); - if (msg.contains()) { - wlog("new view chars ${s}", ("s", send_buffer)); - } - return send_buffer; } @@ -3003,7 +2999,6 @@ namespace eosio { } void net_plugin_impl::pbft_outgoing_new_view(const pbft_new_view &msg) { - ilog( "attempt to send new view: ${n}, from ${v}", ("n", msg)("v", msg.public_key)); auto added = maybe_add_pbft_cache(msg.uuid); if (!added) return; @@ -3011,7 +3006,6 @@ namespace eosio { if (!pcc.pbft_db.is_valid_new_view(msg)) return; bcast_pbft_msg(msg); - ilog( "sent new view: ${n}, from ${v}", ("n", msg)("v", msg.public_key)); } void net_plugin_impl::pbft_outgoing_checkpoint(const pbft_checkpoint &msg) { @@ -3096,7 +3090,6 @@ namespace eosio { } void net_plugin_impl::handle_message( connection_ptr c, const pbft_new_view &msg) { - ilog( "received new view: ${n}, from ${v}", ("n", msg)("v", msg.public_key)); if (!is_pbft_msg_valid(msg)) return; @@ -3107,7 +3100,7 @@ namespace eosio { if (!pcc.pbft_db.is_valid_new_view(msg)) return; forward_pbft_msg(c, msg); - ilog( "forwarded new view: ${n}, from ${v}", ("n", msg)("v", msg.public_key)); + fc_ilog( logger, "received new view: ${n}, from ${v}", ("n", msg)("v", msg.public_key)); pbft_incoming_new_view_channel.publish(msg); } From ebf8d3b128f10f9075d69a29773cd5469c19ccd5 Mon Sep 17 00:00:00 2001 From: oldcold Date: Thu, 18 Apr 2019 18:25:31 +0800 Subject: [PATCH 33/59] stop setting head as my prepare during switching fork --- libraries/chain/controller.cpp | 12 +++--------- libraries/chain/pbft_database.cpp | 10 ++++------ plugins/net_plugin/net_plugin.cpp | 2 +- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 5021546bb34..fa4e7950c60 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1275,7 +1275,7 @@ struct controller_impl { const auto& upo = db.get(); const auto upo_upgrade_target_block_num = upo.upgrade_target_block_num; - if (upgrading) { + if (upgrading && !replaying) { ilog("SYSTEM IS UPGRADING, no producer schedule changes will happen until fully upgraded."); if (head->dpos_irreversible_blocknum >= upo_upgrade_target_block_num) { db.modify( upo, [&]( auto& up ) { up.upgrade_complete_block_num.emplace(head->block_num); }); @@ -1332,8 +1332,6 @@ struct controller_impl { if (!upgrading) { // Promote proposed schedule to pending schedule. if (!replaying) { - ilog("bft_irreversible_blocknum ${a}", ("a", pending->_pending_block_state->bft_irreversible_blocknum) ); - ilog("dpos_irreversible_blocknum ${a}", ("a", pending->_pending_block_state->dpos_irreversible_blocknum) ); ilog("promoting proposed schedule (set in block ${proposed_num}) to pending; current block: ${n} lib: ${lib} schedule: ${schedule} ", ("proposed_num", *gpo.proposed_schedule_block_num)("n", pending->_pending_block_state->block_num) @@ -1549,8 +1547,8 @@ struct controller_impl { maybe_switch_forks( s ); } -// // apply stable checkpoint when there is one -// // TODO:// verify required one more time? + // apply stable checkpoint when there is one + // TODO: verify required one more time? for (const auto &extn: b->block_extensions) { if (extn.first == static_cast(block_extension_type::pbft_stable_checkpoint)) { pbft_commit_local(b->id()); @@ -1615,14 +1613,10 @@ struct controller_impl { if (new_version) { if (pbft_prepared) { fork_db.mark_pbft_prepared_fork(pbft_prepared); - } else if (head) { - fork_db.mark_pbft_prepared_fork(head); } if (my_prepare) { fork_db.mark_pbft_my_prepare_fork(my_prepare); - } else if (head) { - fork_db.mark_pbft_my_prepare_fork(head); } } diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index 2b51c33ee7d..324ec8c6528 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -983,11 +983,6 @@ namespace eosio { return is_desired_checkpoint_num; }; -// auto checkpoint = [&](const block_num_type &in) { -// return in % 100 == 1 -// || in == ctrl.last_proposed_schedule_block_num() -// || in == ctrl.last_promoted_proposed_schedule_block_num(); -// }; for (auto i = psp->block_num; i > std::max(ctrl.last_stable_checkpoint_block_num(), static_cast(1)); --i) { @@ -1153,7 +1148,10 @@ namespace eosio { } bool pbft_database::is_valid_stable_checkpoint(const pbft_stable_checkpoint &scp) { - if (scp.block_num <= ctrl.last_stable_checkpoint_block_num()) return true; + if (scp.block_num <= ctrl.last_stable_checkpoint_block_num()) + // the stable checkpoint is way behind lib, no way getting the block state, + // it will not be applied nor saved, thus considered safe. + return true; auto valid = true; for (const auto &c: scp.checkpoints) { diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index c41f85f505c..0fcc47f15c1 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3100,7 +3100,7 @@ namespace eosio { if (!pcc.pbft_db.is_valid_new_view(msg)) return; forward_pbft_msg(c, msg); - fc_ilog( logger, "received new view: ${n}, from ${v}", ("n", msg)("v", msg.public_key)); + fc_dlog( logger, "received new view: ${n}, from ${v}", ("n", msg)("v", msg.public_key)); pbft_incoming_new_view_channel.publish(msg); } From b6382fcd04d87b03697c44a746db2ad0fd664f3f Mon Sep 17 00:00:00 2001 From: oldcold Date: Thu, 18 Apr 2019 21:17:40 +0800 Subject: [PATCH 34/59] mark pbft prepared (my prepared) with previous block's attr upon adding --- libraries/chain/controller.cpp | 35 +++++-------------------------- libraries/chain/fork_database.cpp | 11 ++++++++++ 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index fa4e7950c60..21e8c54324a 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -780,7 +780,6 @@ struct controller_impl { bool is_new_version() { try { const auto& upo = db.get().upgrade_target_block_num; -// dlog("upgrade target block num: ${n}", ("n", upo)); return head->dpos_irreversible_blocknum >= upo && upo > 0; } catch( const boost::exception& e) { wlog("no upo found, regenerating..."); @@ -818,25 +817,9 @@ struct controller_impl { if (add_to_fork_db) { pending->_pending_block_state->validated = true; - auto new_version = is_new_version(); - auto new_bsp = fork_db.add(pending->_pending_block_state, true); emit(self.accepted_block_header, pending->_pending_block_state); - if (new_version) { - if (pbft_prepared) { - fork_db.mark_pbft_prepared_fork(pbft_prepared); - } else if (head) { - fork_db.mark_pbft_prepared_fork(head); - } - - if (my_prepare) { - fork_db.mark_pbft_my_prepare_fork(my_prepare); - } else if (head) { - fork_db.mark_pbft_my_prepare_fork(head); - } - } - head = fork_db.head(); EOS_ASSERT(new_bsp == head, fork_database_exception, "committed block did not become the new head in fork database"); } @@ -1451,6 +1434,11 @@ struct controller_impl { finalize_block(); + if (producer_block_id != pending->_pending_block_state->header.id()) { + ilog("producer block: ${b}", ("b", (*b))); + ilog("pending block: ${p}", ("p", (*(pending->_pending_block_state)))); + } + // this implicitly asserts that all header fields (less the signature) are identical EOS_ASSERT(producer_block_id == pending->_pending_block_state->header.id(), block_validate_exception, "Block ID does not match", @@ -1607,19 +1595,6 @@ struct controller_impl { } void maybe_switch_forks( controller::block_status s ) { - - auto new_version = is_new_version(); - - if (new_version) { - if (pbft_prepared) { - fork_db.mark_pbft_prepared_fork(pbft_prepared); - } - - if (my_prepare) { - fork_db.mark_pbft_my_prepare_fork(my_prepare); - } - } - auto new_head = fork_db.head(); if( new_head->header.previous == head->id ) { diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index edfbb82cedd..aa014088d19 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -186,10 +186,21 @@ namespace eosio { namespace chain { auto inserted = my->index.insert(n); EOS_ASSERT( inserted.second, fork_database_exception, "duplicate block added?" ); + auto prior = my->index.find( n->block->previous ); + + //TODO: to be optimised. + if ((*prior)->pbft_prepared) { + mark_pbft_prepared_fork(*prior); + } + if (((*prior)->pbft_my_prepare)) { + mark_pbft_my_prepare_fork(*prior); + } + my->head = *my->index.get().begin(); auto lib = std::max(my->head->bft_irreversible_blocknum, my->head->dpos_irreversible_blocknum); auto checkpoint = my->head->pbft_stable_checkpoint_blocknum; + auto oldest = *my->index.get().begin(); if( oldest->block_num < lib && oldest->block_num < checkpoint ) { From 06ebc4aeab598a64a605ae4e22cfe053113b105c Mon Sep 17 00:00:00 2001 From: deadlock Date: Fri, 19 Apr 2019 15:25:26 +0800 Subject: [PATCH 35/59] wait a while for edge case when conn reset --- plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp index 436be297d16..ac5d8bb5b53 100644 --- a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp +++ b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp @@ -354,8 +354,8 @@ struct txn_test_gen_plugin_impl { batch = batch_size/2; ilog("Started transaction test plugin; performing ${p} transactions every ${m}ms", ("p", batch_size)("m", period)); - - arm_timer(boost::asio::high_resolution_timer::clock_type::now()); + ilog("wait 3 seconds to spin up"); + arm_timer(boost::asio::high_resolution_timer::clock_type::now() + std::chrono::milliseconds(3000) ); } void arm_timer(boost::asio::high_resolution_timer::time_point s) { From 08b5ba5c8fc1903ce09b7d1b1e65a8c562c663f3 Mon Sep 17 00:00:00 2001 From: oldcold Date: Mon, 22 Apr 2019 17:26:59 +0800 Subject: [PATCH 36/59] resolve conflicts in fork db prune --- libraries/chain/controller.cpp | 7 ++++-- libraries/chain/fork_database.cpp | 24 ++++++++++++------- .../include/eosio/chain/fork_database.hpp | 4 ++-- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 21e8c54324a..e8535eb0626 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -817,7 +817,9 @@ struct controller_impl { if (add_to_fork_db) { pending->_pending_block_state->validated = true; - auto new_bsp = fork_db.add(pending->_pending_block_state, true); + auto new_version = is_new_version(); + + auto new_bsp = fork_db.add(pending->_pending_block_state, true, new_version); emit(self.accepted_block_header, pending->_pending_block_state); head = fork_db.head(); @@ -1494,7 +1496,8 @@ struct controller_impl { auto& b = new_header_state->block; emit( self.pre_accepted_block, b ); - fork_db.add( new_header_state, false); + auto new_version = is_new_version(); + fork_db.add( new_header_state, false, new_version); if (conf.trusted_producers.count(b->producer)) { trusted_producer_light_validation = true; diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index aa014088d19..53ffbfd86fe 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -173,7 +173,7 @@ namespace eosio { namespace chain { } } - block_state_ptr fork_database::add( const block_state_ptr& n, bool skip_validate_previous ) { + block_state_ptr fork_database::add( const block_state_ptr& n, bool skip_validate_previous, bool new_version ) { EOS_ASSERT( n, fork_database_exception, "attempt to add null block state" ); EOS_ASSERT( my->head, fork_db_block_not_found, "no head block set" ); @@ -189,11 +189,13 @@ namespace eosio { namespace chain { auto prior = my->index.find( n->block->previous ); //TODO: to be optimised. - if ((*prior)->pbft_prepared) { - mark_pbft_prepared_fork(*prior); - } - if (((*prior)->pbft_my_prepare)) { - mark_pbft_my_prepare_fork(*prior); + if (new_version) { + if ((*prior)->pbft_prepared) { + mark_pbft_prepared_fork(*prior); + } + if (((*prior)->pbft_my_prepare)) { + mark_pbft_my_prepare_fork(*prior); + } } my->head = *my->index.get().begin(); @@ -203,7 +205,13 @@ namespace eosio { namespace chain { auto oldest = *my->index.get().begin(); - if( oldest->block_num < lib && oldest->block_num < checkpoint ) { + auto should_prune_oldest = oldest->block_num < lib; + + if (new_version) { + should_prune_oldest = should_prune_oldest && oldest->block_num < checkpoint; + } + + if ( should_prune_oldest ) { prune( oldest ); } @@ -222,7 +230,7 @@ namespace eosio { namespace chain { auto result = std::make_shared( **prior, move(b), skip_validate_signee, new_version); EOS_ASSERT( result, fork_database_exception , "fail to add new block state" ); - return add(result, true); + return add(result, true, new_version); } const block_state_ptr& fork_database::head()const { return my->head; } diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 231790c7bda..2842e27276a 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -40,8 +40,8 @@ namespace eosio { namespace chain { * block_state and will return a pointer to the new block state or * throw on error. */ - block_state_ptr add( signed_block_ptr b, bool skip_validate_signee, bool new_version); - block_state_ptr add( const block_state_ptr& next_block, bool skip_validate_previous ); + block_state_ptr add( signed_block_ptr b, bool skip_validate_signee, bool new_version ); + block_state_ptr add( const block_state_ptr& next_block, bool skip_validate_previous, bool new_version ); void remove( const block_id_type& id ); void add( const header_confirmation& c ); From 55a94a7474ff1491d253aedfe0ecd30e0e0cef95 Mon Sep 17 00:00:00 2001 From: oldcold Date: Mon, 22 Apr 2019 18:29:32 +0800 Subject: [PATCH 37/59] attempt switching forks upon new view. --- libraries/chain/controller.cpp | 6 ++++++ libraries/chain/include/eosio/chain/controller.hpp | 2 ++ libraries/chain/include/eosio/chain/pbft_database.hpp | 2 ++ libraries/chain/pbft.cpp | 2 ++ libraries/chain/pbft_database.cpp | 4 ++++ 5 files changed, 16 insertions(+) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index e8535eb0626..3bbec688b2e 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -2649,4 +2649,10 @@ bool controller::is_upgraded() const { bool controller::under_upgrade() const { return my->is_upgrading(); } + +void controller::maybe_switch_forks() { + if ( my->read_mode != db_read_mode::IRREVERSIBLE ) { + my->maybe_switch_forks( controller::block_status::complete ); + } +} } } /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index d0695629835..0907f447f37 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -311,6 +311,8 @@ namespace eosio { namespace chain { bool is_upgraded()const; bool under_upgrade()const; + void maybe_switch_forks(); + /* signal pre_apply_block; signal post_apply_block; diff --git a/libraries/chain/include/eosio/chain/pbft_database.hpp b/libraries/chain/include/eosio/chain/pbft_database.hpp index 9e4a5501faa..07de4c8718c 100644 --- a/libraries/chain/include/eosio/chain/pbft_database.hpp +++ b/libraries/chain/include/eosio/chain/pbft_database.hpp @@ -597,6 +597,8 @@ namespace eosio { bool should_stop_view_change(const pbft_view_change &vc); + void maybe_switch_forks(); + private: controller &ctrl; pbft_state_multi_index_type pbft_state_index; diff --git a/libraries/chain/pbft.cpp b/libraries/chain/pbft.cpp index c63a76bf03c..1e5940cd5b2 100644 --- a/libraries/chain/pbft.cpp +++ b/libraries/chain/pbft.cpp @@ -472,6 +472,7 @@ namespace eosio { for (auto cp :new_view.stable_checkpoint.checkpoints) { try { pbft_db.add_pbft_checkpoint(cp); + pbft_db.maybe_switch_forks(); } catch (...) { wlog("insert checkpoint failed"); } @@ -482,6 +483,7 @@ namespace eosio { for (auto p: new_view.prepared.prepares) { try { pbft_db.add_pbft_prepare(p); + pbft_db.maybe_switch_forks(); } catch (...) { wlog("insert prepare failed"); } diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index 324ec8c6528..95624793208 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -1223,6 +1223,10 @@ namespace eosio { return ctrl.get_chain_id(); } + void pbft_database::maybe_switch_forks() { + ctrl.maybe_switch_forks(); + } + void pbft_database::set(pbft_state_ptr s) { auto result = pbft_state_index.insert(s); From a540740544eb8872ab5be241121ca117eb719308 Mon Sep 17 00:00:00 2001 From: oldcold Date: Mon, 22 Apr 2019 22:26:05 +0800 Subject: [PATCH 38/59] stop switching upon new view --- libraries/chain/pbft.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/chain/pbft.cpp b/libraries/chain/pbft.cpp index 1e5940cd5b2..c63a76bf03c 100644 --- a/libraries/chain/pbft.cpp +++ b/libraries/chain/pbft.cpp @@ -472,7 +472,6 @@ namespace eosio { for (auto cp :new_view.stable_checkpoint.checkpoints) { try { pbft_db.add_pbft_checkpoint(cp); - pbft_db.maybe_switch_forks(); } catch (...) { wlog("insert checkpoint failed"); } @@ -483,7 +482,6 @@ namespace eosio { for (auto p: new_view.prepared.prepares) { try { pbft_db.add_pbft_prepare(p); - pbft_db.maybe_switch_forks(); } catch (...) { wlog("insert prepare failed"); } From 8094eeb72e6c6b4e81e726044af73840329b7cfb Mon Sep 17 00:00:00 2001 From: oldcold Date: Mon, 22 Apr 2019 22:58:30 +0800 Subject: [PATCH 39/59] skip watermark check in new version --- plugins/producer_plugin/producer_plugin.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 0a9b1e99bf4..9eda4982c43 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -1091,7 +1091,9 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { _pending_block_mode = pending_block_mode::speculating; } - if (_pending_block_mode == pending_block_mode::producing) { + auto new_version = chain.is_upgraded(); + + if (_pending_block_mode == pending_block_mode::producing && !new_version) { // determine if our watermark excludes us from producing at this point if (currrent_watermark_itr != _producer_watermarks.end()) { if (currrent_watermark_itr->second >= hbs->block_num + 1) { @@ -1113,7 +1115,7 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { try { uint16_t blocks_to_confirm = 0; - if (_pending_block_mode == pending_block_mode::producing) { + if (_pending_block_mode == pending_block_mode::producing && !new_version) { // determine how many blocks this producer can confirm // 1) if it is not a producer from this node, assume no confirmations (we will discard this block anyway) // 2) if it is a producer on this node that has never produced, the conservative approach is to assume no From 86797659288fe771599a82e23a8693656475eae1 Mon Sep 17 00:00:00 2001 From: oldcold Date: Tue, 23 Apr 2019 13:19:51 +0800 Subject: [PATCH 40/59] remove all my prepare mark upon new view; using transit_to_committed func in new view. --- libraries/chain/controller.cpp | 4 +- libraries/chain/fork_database.cpp | 39 ++++++------------- .../include/eosio/chain/fork_database.hpp | 6 +-- libraries/chain/include/eosio/chain/pbft.hpp | 2 +- libraries/chain/pbft.cpp | 21 +++++----- libraries/chain/pbft_database.cpp | 19 +++++++-- 6 files changed, 44 insertions(+), 47 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 3bbec688b2e..3ec8e292e90 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -2465,8 +2465,8 @@ block_id_type controller::get_pbft_my_prepare() const { } void controller::reset_pbft_my_prepare() const { - if (my->my_prepare) my->fork_db.remove_pbft_my_prepare_fork(my->my_prepare->id); - my->my_prepare.reset(); + my->fork_db.remove_pbft_my_prepare_fork(); + if (my->my_prepare) my->my_prepare.reset(); } db_read_mode controller::get_read_mode()const { diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 53ffbfd86fe..7687dc9fd13 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -199,7 +199,7 @@ namespace eosio { namespace chain { } my->head = *my->index.get().begin(); - +// wlog("head block num: ${h}", ("h", my->head->block_num)); auto lib = std::max(my->head->bft_irreversible_blocknum, my->head->dpos_irreversible_blocknum); auto checkpoint = my->head->pbft_stable_checkpoint_blocknum; @@ -352,7 +352,7 @@ namespace eosio { namespace chain { return block_state_ptr(); } - void fork_database::mark_pbft_prepared_fork(const block_state_ptr& h) const { + void fork_database::mark_pbft_prepared_fork(const block_state_ptr& h) { auto& by_id_idx = my->index.get(); auto itr = by_id_idx.find( h->id ); EOS_ASSERT( itr != by_id_idx.end(), fork_db_block_not_found, "could not find block in fork database" ); @@ -383,7 +383,7 @@ namespace eosio { namespace chain { my->head = *my->index.get().begin(); } - void fork_database::mark_pbft_my_prepare_fork(const block_state_ptr& h) const { + void fork_database::mark_pbft_my_prepare_fork(const block_state_ptr& h) { auto& by_id_idx = my->index.get(); auto itr = by_id_idx.find( h->id ); EOS_ASSERT( itr != by_id_idx.end(), fork_db_block_not_found, "could not find block in fork database" ); @@ -414,35 +414,18 @@ namespace eosio { namespace chain { my->head = *my->index.get().begin(); } - void fork_database::remove_pbft_my_prepare_fork(const block_id_type &id) const { - auto& by_id_idx = my->index.get(); - auto itr = by_id_idx.find( id ); - EOS_ASSERT( itr != by_id_idx.end(), fork_db_block_not_found, "could not find block in fork database" ); - by_id_idx.modify( itr, [&]( auto& bsp ) { bsp->pbft_my_prepare = false; }); - - auto update = [&]( const vector& in ) { - vector updated; + void fork_database::remove_pbft_my_prepare_fork() { + auto& by_num_idx = my->index.get(); + auto itr = by_num_idx.begin(); - for( const auto& i : in ) { - auto& pidx = my->index.get(); - auto pitr = pidx.lower_bound( i ); - auto epitr = pidx.upper_bound( i ); - while( pitr != epitr ) { - pidx.modify( pitr, [&]( auto& bsp ) { - bsp->pbft_my_prepare = false; - updated.push_back( bsp->id ); - }); - ++pitr; - } + while (itr != by_num_idx.end()) { + if ((*itr)->pbft_my_prepare == true) { + by_num_idx.modify( itr, [&]( auto& bsp ) { bsp->pbft_my_prepare = false; }); } - return updated; - }; - - vector queue{id}; - while(!queue.empty()) { - queue = update( queue ); + ++itr; } my->head = *my->index.get().begin(); +// wlog("head block num: ${h}", ("h", my->head->block_num)); } block_state_ptr fork_database::get_block_in_current_chain_by_num( uint32_t n )const { diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 2842e27276a..10eb41a6852 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -74,11 +74,11 @@ namespace eosio { namespace chain { void set_latest_checkpoint( block_id_type id); - void mark_pbft_prepared_fork(const block_state_ptr& h) const; + void mark_pbft_prepared_fork(const block_state_ptr& h); - void mark_pbft_my_prepare_fork(const block_state_ptr& h) const; + void mark_pbft_my_prepare_fork(const block_state_ptr& h); - void remove_pbft_my_prepare_fork(const block_id_type &id) const; + void remove_pbft_my_prepare_fork(); private: unique_ptr my; diff --git a/libraries/chain/include/eosio/chain/pbft.hpp b/libraries/chain/include/eosio/chain/pbft.hpp index 2568d26bb63..bbca6a494ae 100644 --- a/libraries/chain/include/eosio/chain/pbft.hpp +++ b/libraries/chain/include/eosio/chain/pbft.hpp @@ -40,7 +40,7 @@ namespace eosio { void on_new_view(pbft_new_view &e); template - void transit_to_committed_state(T const & s); + void transit_to_committed_state(T const & s, bool to_new_view); template void transit_to_prepared_state(T const & s); diff --git a/libraries/chain/pbft.cpp b/libraries/chain/pbft.cpp index c63a76bf03c..25ee10c9cf0 100644 --- a/libraries/chain/pbft.cpp +++ b/libraries/chain/pbft.cpp @@ -184,7 +184,7 @@ namespace eosio { if (pending_commit_local && !pbft_db.pending_pbft_lib()) { pbft_db.send_pbft_checkpoint(); - m->transit_to_committed_state(this); + m->transit_to_committed_state(this, false); } } @@ -203,7 +203,7 @@ namespace eosio { if (pending_commit_local && !pbft_db.pending_pbft_lib()) { pbft_db.send_pbft_checkpoint(); - m->transit_to_committed_state(this); + m->transit_to_committed_state(this, false); } } @@ -338,7 +338,7 @@ namespace eosio { //skip from view change state if my lib is higher than my view change state height. auto vc = m->get_view_changes_cache(); if (!vc.empty() && pbft_db.should_stop_view_change(vc.front())) { - m->transit_to_committed_state(this); + m->transit_to_committed_state(this, false); return; } @@ -371,7 +371,7 @@ namespace eosio { //skip from view change state if my lib is higher than my view change state height. auto vc = m->get_view_changes_cache(); if (!vc.empty() && pbft_db.should_stop_view_change(vc.front())) { - m->transit_to_committed_state(this); + m->transit_to_committed_state(this, false); return; } @@ -412,11 +412,13 @@ namespace eosio { } template - void psm_machine::transit_to_committed_state(T const & s) { + void psm_machine::transit_to_committed_state(T const & s, bool to_new_view) { - auto nv = pbft_db.get_committed_view(); - if (nv > this->get_current_view()) this->set_current_view(nv); - this->set_target_view(this->get_current_view() + 1); + if (!to_new_view) { + auto nv = pbft_db.get_committed_view(); + if (nv > this->get_current_view()) this->set_current_view(nv); + this->set_target_view(this->get_current_view() + 1); + } auto prepares = this->pbft_db.send_and_add_pbft_prepare(vector{}, this->get_current_view()); set_prepares_cache(prepares); @@ -492,8 +494,7 @@ namespace eosio { } } - this->set_current(new psm_committed_state); - delete s; + transit_to_committed_state(s, true); } void psm_machine::send_pbft_view_change() { diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index 95624793208..123f6356fe4 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -647,9 +647,9 @@ namespace eosio { if (certificate.block_num <= ctrl.last_stable_checkpoint_block_num()) return true; auto valid = true; - valid &= certificate.is_signature_valid(); + valid = valid && certificate.is_signature_valid(); for (auto const &p : certificate.prepares) { - valid &= is_valid_prepare(p); + valid = valid && is_valid_prepare(p); if (!valid) return false; } @@ -1155,12 +1155,25 @@ namespace eosio { auto valid = true; for (const auto &c: scp.checkpoints) { - valid &= is_valid_checkpoint(c) + valid = valid && is_valid_checkpoint(c) && c.block_id == scp.block_id && c.block_num == scp.block_num; if (!valid) return false; } //TODO: check if (2/3 + 1) met + auto bs = ctrl.fetch_block_state_by_number(scp.block_num); + if (bs) { + auto as = bs->active_schedule; + auto cp_count = 0; + for (auto const &sp: as.producers) { + for (auto const &v: scp.checkpoints) { + if (sp.block_signing_key == v.public_key) cp_count += 1; + } + } + valid = valid && cp_count >= as.producers.size() * 2 / 3 + 1; + } else { + return false; + } return valid; } From c8092f71e2294666ea0dd746e3d1107faa04a1a4 Mon Sep 17 00:00:00 2001 From: oldcold Date: Tue, 23 Apr 2019 16:42:05 +0800 Subject: [PATCH 41/59] bug fix: itr bug during removing marks --- libraries/chain/fork_database.cpp | 32 ++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 7687dc9fd13..3b0254888a7 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -415,17 +415,35 @@ namespace eosio { namespace chain { } void fork_database::remove_pbft_my_prepare_fork() { - auto& by_num_idx = my->index.get(); - auto itr = by_num_idx.begin(); + auto oldest = *my->index.get().begin(); - while (itr != by_num_idx.end()) { - if ((*itr)->pbft_my_prepare == true) { - by_num_idx.modify( itr, [&]( auto& bsp ) { bsp->pbft_my_prepare = false; }); + auto& by_id_idx = my->index.get(); + auto itr = by_id_idx.find( oldest->id ); + by_id_idx.modify( itr, [&]( auto& bsp ) { bsp->pbft_my_prepare = false; }); + + auto update = [&]( const vector& in ) { + vector updated; + + for( const auto& i : in ) { + auto& pidx = my->index.get(); + auto pitr = pidx.lower_bound( i ); + auto epitr = pidx.upper_bound( i ); + while( pitr != epitr ) { + pidx.modify( pitr, [&]( auto& bsp ) { + bsp->pbft_my_prepare = false; + updated.push_back( bsp->id ); + }); + ++pitr; + } } - ++itr; + return updated; + }; + + vector queue{ oldest->id }; + while(!queue.empty()) { + queue = update( queue ); } my->head = *my->index.get().begin(); -// wlog("head block num: ${h}", ("h", my->head->block_num)); } block_state_ptr fork_database::get_block_in_current_chain_by_num( uint32_t n )const { From 06aa9798e733a029281cad19a024efb6e1ebfa22 Mon Sep 17 00:00:00 2001 From: deadlock Date: Wed, 24 Apr 2019 22:35:23 +0800 Subject: [PATCH 42/59] fix unit_test --- libraries/chain/controller.cpp | 11 ++++ libraries/chain/fork_database.cpp | 4 +- .../chain/include/eosio/chain/controller.hpp | 1 + unittests/pbft_tests.cpp | 58 ++++++++++++++++--- 4 files changed, 65 insertions(+), 9 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index d5867bc3193..37a99080cd2 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -2684,6 +2684,17 @@ bool controller::under_upgrade() const { return my->is_upgrading(); } +void controller::set_upo(uint32_t target_block_num) { + try { + const auto& upo = my->db.get(); + my->db.modify( upo, [&]( auto& up ) { up.upgrade_target_block_num = (block_num_type)target_block_num;}); + } catch( const boost::exception& e) { + my->db.create([&](auto& up){ + up.upgrade_target_block_num = (block_num_type)target_block_num; + }); + } +} + void controller::maybe_switch_forks() { if ( my->read_mode != db_read_mode::IRREVERSIBLE ) { my->maybe_switch_forks( controller::block_status::complete ); diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 3b0254888a7..b07570d2c37 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -32,13 +32,13 @@ namespace eosio { namespace chain { >, ordered_non_unique< tag, composite_key< block_state, + member, member, -// member, member, member, member >, - composite_key_compare< std::greater, std::greater, std::greater, std::greater > + composite_key_compare< std::greater, std::greater, std::greater, std::greater, std::greater > > > > fork_multi_index_type; diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index a03425869f3..d45dd6584ba 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -308,6 +308,7 @@ namespace eosio { namespace chain { const upgrade_property_object& get_upgrade_properties()const; bool is_upgraded()const; bool under_upgrade()const; + void set_upo(uint32_t target_block_num); void maybe_switch_forks(); diff --git a/unittests/pbft_tests.cpp b/unittests/pbft_tests.cpp index 55c2049bf3a..a9e3fb0bf67 100644 --- a/unittests/pbft_tests.cpp +++ b/unittests/pbft_tests.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -27,7 +28,7 @@ BOOST_AUTO_TEST_SUITE(pbft_tests) BOOST_CHECK(!p); } - BOOST_AUTO_TEST_CASE(can_raise_lib) { + BOOST_AUTO_TEST_CASE(can_advance_lib_in_old_version) { tester tester; controller &ctrl = *tester.control.get(); pbft_controller pbft_ctrl{ctrl}; @@ -44,16 +45,59 @@ BOOST_AUTO_TEST_SUITE(pbft_tests) tester.produce_block();//produce block num 2 BOOST_REQUIRE_EQUAL(ctrl.last_irreversible_block_num(), 0); BOOST_REQUIRE_EQUAL(ctrl.head_block_num(), 2); - - pbft_ctrl.maybe_pbft_prepare(); - pbft_ctrl.maybe_pbft_commit(); - BOOST_REQUIRE_EQUAL(ctrl.pending_pbft_lib(), true); - tester.produce_block();//produce block num 3 - BOOST_REQUIRE_EQUAL(ctrl.pending_pbft_lib(), false); + tester.produce_block(); BOOST_REQUIRE_EQUAL(ctrl.last_irreversible_block_num(), 2); BOOST_REQUIRE_EQUAL(ctrl.head_block_num(), 3); } +BOOST_AUTO_TEST_CASE(can_advance_lib_after_upgrade) { + tester tester; + controller &ctrl = *tester.control.get(); + pbft_controller pbft_ctrl{ctrl}; + ctrl.set_upo(150); + + const auto& upo = ctrl.db().get(); + const auto upo_upgrade_target_block_num = upo.upgrade_target_block_num; + BOOST_CHECK_EQUAL(upo_upgrade_target_block_num, 150); + + + auto privkey = tester::get_private_key( N(eosio), "active" ); + auto pubkey = tester::get_public_key( N(eosio), "active"); + auto sp = [privkey]( const eosio::chain::digest_type& digest ) { + return privkey.sign(digest); + }; + std::map msp; + msp[pubkey]=sp; + ctrl.set_my_signature_providers(msp); + + auto is_upgraded = ctrl.is_upgraded(); + + BOOST_CHECK_EQUAL(is_upgraded, false); + + tester.produce_block();//produce block num 2 + BOOST_CHECK_EQUAL(ctrl.last_irreversible_block_num(), 0); + BOOST_CHECK_EQUAL(ctrl.head_block_num(), 2); + tester.produce_blocks(150); + BOOST_CHECK_EQUAL(ctrl.last_irreversible_block_num(), 150); + BOOST_CHECK_EQUAL(ctrl.head_block_num(), 152); + + is_upgraded = ctrl.is_upgraded(); + BOOST_CHECK_EQUAL(is_upgraded, true); + + tester.produce_blocks(10); + BOOST_CHECK_EQUAL(ctrl.pending_pbft_lib(), false); + BOOST_CHECK_EQUAL(ctrl.last_irreversible_block_num(), 150); + BOOST_CHECK_EQUAL(ctrl.head_block_num(), 162); + + pbft_ctrl.maybe_pbft_prepare(); + pbft_ctrl.maybe_pbft_commit(); + + BOOST_CHECK_EQUAL(ctrl.pending_pbft_lib(), true); + tester.produce_block(); //set lib using pending pbft lib + + BOOST_CHECK_EQUAL(ctrl.last_irreversible_block_num(), 162); + BOOST_CHECK_EQUAL(ctrl.head_block_num(), 163); +} BOOST_AUTO_TEST_SUITE_END() From de03c3dcdaf1f832524dbbbdff64bc8a52560976 Mon Sep 17 00:00:00 2001 From: oldcold Date: Thu, 25 Apr 2019 17:46:22 +0800 Subject: [PATCH 43/59] bug fix: only request checkpoints in new version --- plugins/net_plugin/net_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 90f4ace79ea..7321c5de820 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -1668,8 +1668,8 @@ namespace eosio { uint32_t head = cc.fork_db_head_block_num(); block_id_type head_id = cc.fork_db_head_block_id(); - - if (peer_lib > lscb_num) { + auto upgraded = cc.is_upgraded(); + if (peer_lib > lscb_num && upgraded) { //there might be a better way to sync checkpoints, yet we do not want to modify the existing handshake msg. fc_dlog(logger, "request sync checkpoints"); c->request_sync_checkpoints(lscb_num, peer_lib); From ef0ccfaa17aa591da76c5fe03e5ea44aa30f2f84 Mon Sep 17 00:00:00 2001 From: oldcold Date: Fri, 26 Apr 2019 12:53:20 +0800 Subject: [PATCH 44/59] resolve inconsistency in `is_upgrading` calculation. --- libraries/chain/controller.cpp | 87 +++++++++++++++++-------------- libraries/chain/pbft_database.cpp | 11 ++-- 2 files changed, 51 insertions(+), 47 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 457538a2e99..50e1e809e74 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -828,28 +828,43 @@ struct controller_impl { } // "bos end" + optional upgrade_target_block() { + try { + return optional{db.get().upgrade_target_block_num}; + } catch( const boost::exception& e) { + wlog("no upo found, regenerating..."); + db.create([](auto&){}); + return optional{}; + } + } + + optional upgrade_complete_block() { + try { + const auto& upo = db.get(); + return db.get().upgrade_target_block_num; + } catch( const boost::exception& e) { + db.create([](auto&){}); + return optional{}; + } + } + bool is_new_version() { - try { - const auto& upo = db.get().upgrade_target_block_num; - return head->dpos_irreversible_blocknum >= upo && upo > 0; - } catch( const boost::exception& e) { - wlog("no upo found, regenerating..."); - db.create([](auto&){}); - return false; - } + auto ucb = upgrade_complete_block(); + if (ucb) return head->block_num > *ucb; + return false; } bool is_upgrading() { - try { - const auto& upo = db.get(); - const auto upo_upgrade_target_block_num = upo.upgrade_target_block_num; - return upo_upgrade_target_block_num > 0 - &&(head->block_num + 1 >= upo_upgrade_target_block_num) - && std::max(head->dpos_irreversible_blocknum, head->bft_irreversible_blocknum) <= upo_upgrade_target_block_num + 12; - } catch( const boost::exception& e) { - db.create([](auto&){}); - return false; - } + auto utb = upgrade_target_block(); + auto ucb = upgrade_complete_block(); + auto is_upgrading = false; + if (utb) { + is_upgrading = head->block_num > *utb; + } + if (ucb) { + is_upgrading = head->block_num < *ucb; + } + return is_upgrading; } /** @@ -1305,23 +1320,20 @@ struct controller_impl { pending.emplace(maybe_session()); } - auto new_version = is_new_version(); - auto upgrading = is_upgrading(); - - try { - const auto& upo = db.get(); - const auto upo_upgrade_target_block_num = upo.upgrade_target_block_num; - - if (upgrading && !replaying) { - ilog("SYSTEM IS UPGRADING, no producer schedule changes will happen until fully upgraded."); - if (head->dpos_irreversible_blocknum >= upo_upgrade_target_block_num) { - db.modify( upo, [&]( auto& up ) { up.upgrade_complete_block_num.emplace(head->block_num); }); - } - } - } catch( const boost::exception& e) { - db.create([](auto&){}); + auto utb = upgrade_target_block(); + auto ucb = upgrade_complete_block(); + if (utb) { + if (head->dpos_irreversible_blocknum >= *utb && !ucb) { + const auto& upo = db.get(); + db.modify( upo, [&]( auto& up ) { + up.upgrade_complete_block_num.reset(); + up.upgrade_complete_block_num.emplace( head->block_num); + }); + } } + auto new_version = is_new_version(); + auto upgrading = is_upgrading(); pending->_block_status = s; pending->_producer_block_id = producer_block_id; @@ -1355,13 +1367,6 @@ struct controller_impl { bool should_promote_pending_schedule = false; -// if (new_version && (gpo.proposed_schedule_block_num.valid() && pending->_pending_block_state->bft_irreversible_blocknum > *gpo.proposed_schedule_block_num)) { -// db.modify( gpo, [&]( auto& gp ) { -// gp.proposed_schedule_block_num = optional(); -// gp.proposed_schedule.clear(); -// }); -// } - should_promote_pending_schedule = gpo.proposed_schedule_block_num.valid() // if there is a proposed schedule that was proposed in a block ... && pending->_pending_block_state->pending_schedule.producers.size() == 0 // ... and there is room for a new pending schedule ... && !was_pending_promoted; // ... and not just because it was promoted to active at the start of this block, then: @@ -1397,6 +1402,8 @@ struct controller_impl { } } } + } else { + ilog("system is upgrading, no producer schedule changes will happen until fully upgraded."); } db.modify( gpo, [&]( auto& gp ) { gp.proposed_schedule_block_num = optional(); diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index 7f783ac5d34..b96fb8e9e35 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -998,13 +998,10 @@ namespace eosio { auto checkpoint = [&](const block_num_type &in) { - const auto& upo = ctrl.get_upgrade_properties(); - auto is_desired_checkpoint_num = in % 100 == 1 - || std::find(prepare_watermarks.begin(), prepare_watermarks.end(), in) != prepare_watermarks.end(); - if (upo.upgrade_complete_block_num) { - is_desired_checkpoint_num = is_desired_checkpoint_num && in > upo.upgrade_complete_block_num; - } - return is_desired_checkpoint_num; + const auto& ucb = ctrl.get_upgrade_properties().upgrade_complete_block_num; + if (!ucb) return false; + return in > *ucb + && (in % 100 == 1 || std::find(prepare_watermarks.begin(), prepare_watermarks.end(), in) != prepare_watermarks.end()); }; From 1231a2b8332427f5912e84033fccf0c271b5d6ac Mon Sep 17 00:00:00 2001 From: oldcold Date: Fri, 26 Apr 2019 14:25:38 +0800 Subject: [PATCH 45/59] bug fix: return empty if upgrade target block equals to default. --- libraries/chain/controller.cpp | 39 +++++++++------------ plugins/producer_plugin/producer_plugin.cpp | 19 +++++++--- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 50e1e809e74..6bbe65d995e 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -828,23 +828,28 @@ struct controller_impl { } // "bos end" - optional upgrade_target_block() { + optional upgrade_target_block() { try { - return optional{db.get().upgrade_target_block_num}; + const auto& upo = db.get(); + if (upo.upgrade_target_block_num > 0) { + return upo.upgrade_target_block_num; + } else { + return optional{}; + } } catch( const boost::exception& e) { wlog("no upo found, regenerating..."); db.create([](auto&){}); - return optional{}; + return optional{}; } } - optional upgrade_complete_block() { + optional upgrade_complete_block() { try { - const auto& upo = db.get(); - return db.get().upgrade_target_block_num; + const auto& upo = db.get(); + return upo.upgrade_complete_block_num; } catch( const boost::exception& e) { db.create([](auto&){}); - return optional{}; + return optional{}; } } @@ -858,12 +863,8 @@ struct controller_impl { auto utb = upgrade_target_block(); auto ucb = upgrade_complete_block(); auto is_upgrading = false; - if (utb) { - is_upgrading = head->block_num > *utb; - } - if (ucb) { - is_upgrading = head->block_num < *ucb; - } + if (utb) is_upgrading = head->block_num > *utb; + if (ucb) is_upgrading = is_upgrading && head->block_num < *ucb; return is_upgrading; } @@ -1379,6 +1380,8 @@ struct controller_impl { && ( *gpo.proposed_schedule_block_num <= pending->_pending_block_state->dpos_irreversible_blocknum ); } + if ( upgrading && !replaying) wlog("system is upgrading, no producer schedule promotion will happen until fully upgraded."); + if ( should_promote_pending_schedule ) { if (!upgrading) { @@ -1402,8 +1405,6 @@ struct controller_impl { } } } - } else { - ilog("system is upgrading, no producer schedule changes will happen until fully upgraded."); } db.modify( gpo, [&]( auto& gp ) { gp.proposed_schedule_block_num = optional(); @@ -2169,10 +2170,6 @@ void controller::set_pbft_latest_checkpoint( const block_id_type& id ) { my->set_pbft_latest_checkpoint(id); } -//void controller::set_pbft_prepared_block_id(optional bid){ -// my->pbft_prepared_block_id = bid; -//} - transaction_trace_ptr controller::push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline, uint32_t billed_cpu_time_us ) { validate_db_available_size(); EOS_ASSERT( get_read_mode() != chain::db_read_mode::READ_ONLY, transaction_type_exception, "push transaction not allowed in read-only mode" ); @@ -2508,8 +2505,6 @@ void controller::set_pbft_prepared(const block_id_type& id) const { my->pbft_prepared = bs; my->fork_db.mark_pbft_prepared_fork(bs); } -// dlog( "fork_db head ${h}", ("h", fork_db().head()->id)); -// dlog( "prepared block id ${b}", ("b", id)); } void controller::set_pbft_my_prepare(const block_id_type& id) const { @@ -2519,8 +2514,6 @@ void controller::set_pbft_my_prepare(const block_id_type& id) const { my->my_prepare = bs; my->fork_db.mark_pbft_my_prepare_fork(bs); } -// dlog( "fork_db head ${h}", ("h", fork_db().head()->id)); -// dlog( "my prepare block id ${b}", ("b", id)); } block_id_type controller::get_pbft_my_prepare() const { diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 9eda4982c43..b3a46fddf11 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -341,11 +341,22 @@ class producer_plugin_impl : public std::enable_shared_from_thistimestamp < fc::minutes(5) || (block->block_num() % 1000 == 0) ) { - ilog("Received block ${id}... #${n} @ ${t} signed by ${p} [trxs: ${count}, lib: ${lib}, lscb: ${lscb}, latency: ${latency} ms]", - ("p",block->producer)("id",fc::variant(block->id()).as_string().substr(8,16)) - ("n",block_header::num_from_id(block->id()))("t",block->timestamp) - ("count",block->transactions.size())("lib",chain.last_irreversible_block_num())("lscb", chain.last_stable_checkpoint_block_num())("latency", (fc::time_point::now() - block->timestamp).count()/1000 ) ); + if (chain.is_upgraded()) { + ilog("Received block ${id}... #${n} @ ${t} signed by ${p} [trxs: ${count}, lib: ${lib}, lscb: ${lscb}, latency: ${latency} ms]", + ("p", block->producer)("id", fc::variant(block->id()).as_string().substr(8, 16)) + ("n", block_header::num_from_id(block->id()))("t", block->timestamp) + ("count", block->transactions.size())("lib", chain.last_irreversible_block_num()) + ("lscb", chain.last_stable_checkpoint_block_num()) + ("latency", (fc::time_point::now() - block->timestamp).count() / 1000)); + } else { + ilog("Received block ${id}... #${n} @ ${t} signed by ${p} [trxs: ${count}, lib: ${lib}, conf: ${confs}, latency: ${latency} ms]", + ("p",block->producer)("id",fc::variant(block->id()).as_string().substr(8,16)) + ("n",block_header::num_from_id(block->id()))("t",block->timestamp) + ("count",block->transactions.size())("lib",chain.last_irreversible_block_num()) + ("confs", block->confirmed)("latency", (fc::time_point::now() - block->timestamp).count()/1000 ) ); + } } } From 4a637f69f8a8529f80bff67d183cc9eac82b8d26 Mon Sep 17 00:00:00 2001 From: oldcold Date: Fri, 26 Apr 2019 19:16:29 +0800 Subject: [PATCH 46/59] bug fix: compare schedule between pending checkpoint & lscb (instead of lib). --- libraries/chain/controller.cpp | 6 ++--- libraries/chain/pbft_database.cpp | 37 ++++++++++++++++++------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 6bbe65d995e..3f216e2f566 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -863,7 +863,7 @@ struct controller_impl { auto utb = upgrade_target_block(); auto ucb = upgrade_complete_block(); auto is_upgrading = false; - if (utb) is_upgrading = head->block_num > *utb; + if (utb) is_upgrading = head->block_num >= *utb; if (ucb) is_upgrading = is_upgrading && head->block_num < *ucb; return is_upgrading; } @@ -1356,7 +1356,7 @@ struct controller_impl { auto lscb_num = pending->_pending_block_state->pbft_stable_checkpoint_blocknum; if (new_version && gpo.proposed_schedule_block_num) { - proposed_schedule_blocks.push_back(*gpo.proposed_schedule_block_num); + proposed_schedule_blocks.emplace_back(*gpo.proposed_schedule_block_num); for ( auto itr = proposed_schedule_blocks.begin(); itr != proposed_schedule_blocks.end();) { if ((*itr) < lscb_num) { itr = proposed_schedule_blocks.erase(itr); @@ -1396,7 +1396,7 @@ struct controller_impl { pending->_pending_block_state->set_new_producers(gpo.proposed_schedule); if (new_version) { - promoted_schedule_blocks.push_back(pending->_pending_block_state->block_num); + promoted_schedule_blocks.emplace_back(pending->_pending_block_state->block_num); for ( auto itr = promoted_schedule_blocks.begin(); itr != promoted_schedule_blocks.end();) { if ((*itr) < lscb_num) { itr = promoted_schedule_blocks.erase(itr); diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index b96fb8e9e35..c1b777692db 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -40,7 +40,7 @@ namespace eosio { for (uint32_t i = 0, n = watermarks_size.value; i < n; ++i) { block_num_type h; fc::raw::unpack(ds, h); - prepare_watermarks.push_back(h); + prepare_watermarks.emplace_back(h); } sort(prepare_watermarks.begin(), prepare_watermarks.end()); @@ -618,7 +618,7 @@ namespace eosio { for (const auto &pre: prepares) { if (prepare_count.find(pre.view) == prepare_count.end()) prepare_count[pre.view] = 0; - prepare_msg[pre.view].push_back(pre); + prepare_msg[pre.view].emplace_back(pre); } for (auto const &sp: as) { @@ -716,7 +716,7 @@ namespace eosio { if (p.block_num <= lscb) { ++non_fork_bp_count; } else { - prepare_infos.push_back(block_info{p.block_id, p.block_num}); + prepare_infos.emplace_back(block_info{p.block_id, p.block_num}); } } @@ -949,6 +949,7 @@ namespace eosio { block_info pbft_database::cal_pending_stable_checkpoint() const { + //TODO: maybe use watermarks instead? auto lscb_num = ctrl.last_stable_checkpoint_block_num(); auto lscb_id = ctrl.last_stable_checkpoint_block_id(); auto lscb_info = block_info{lscb_id, lscb_num}; @@ -959,17 +960,26 @@ namespace eosio { while (itr != by_blk_num.end()) { if ((*itr)->is_stable && ctrl.fetch_block_state_by_id((*itr)->block_id)) { - auto lib = ctrl.fetch_block_state_by_number(ctrl.last_irreversible_block_num()); + auto lscb = ctrl.fetch_block_state_by_number(ctrl.last_stable_checkpoint_block_num()); auto head_checkpoint_schedule = ctrl.fetch_block_state_by_id( (*itr)->block_id)->active_schedule; - auto current_schedule = lib_active_producers(); - auto new_schedule = lib_active_producers(); - - if (lib) { - current_schedule = lib->active_schedule; - new_schedule = lib->pending_schedule; + producer_schedule_type current_schedule; + producer_schedule_type new_schedule; + + if (lscb_num == 0) { + const auto& ucb = ctrl.get_upgrade_properties().upgrade_complete_block_num; + if (!ucb) return lscb_info; + auto bs = ctrl.fetch_block_state_by_number(*ucb); + if (!bs) return lscb_info; + current_schedule = bs->active_schedule; + new_schedule = bs->pending_schedule; + } else if (lscb) { + current_schedule = lscb->active_schedule; + new_schedule = lscb->pending_schedule; + } else { + return lscb_info; } if ((*itr)->is_stable @@ -996,15 +1006,13 @@ namespace eosio { block_num_type my_latest_checkpoint = 0; - auto checkpoint = [&](const block_num_type &in) { const auto& ucb = ctrl.get_upgrade_properties().upgrade_complete_block_num; if (!ucb) return false; - return in > *ucb + return in >= *ucb && (in % 100 == 1 || std::find(prepare_watermarks.begin(), prepare_watermarks.end(), in) != prepare_watermarks.end()); }; - for (auto i = psp->block_num; i > std::max(ctrl.last_stable_checkpoint_block_num(), static_cast(1)); --i) { if (checkpoint(i)) { @@ -1029,6 +1037,7 @@ namespace eosio { } if (!pending_checkpoint_block_num.empty()) { + std::sort(pending_checkpoint_block_num.begin(), pending_checkpoint_block_num.begin()); for (auto h: pending_checkpoint_block_num) { for (auto const &my_sp : ctrl.my_signature_providers()) { auto uuid = boost::uuids::to_string(uuid_generator()); @@ -1289,8 +1298,6 @@ namespace eosio { auto cw = *std::upper_bound(prepare_watermarks.begin(), prepare_watermarks.end(), lib); -// wlog("watermarks: ${w}", ("w",prepare_watermarks)); - if (cw > lib) return cw; else return 0; } From 8f305f56ab0ffe85cac85d900847f7052c5d5975 Mon Sep 17 00:00:00 2001 From: oldcold Date: Sat, 27 Apr 2019 19:45:05 +0800 Subject: [PATCH 47/59] bug fix: modified the start of new version --- libraries/chain/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 3f216e2f566..03cf5f57167 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -855,7 +855,7 @@ struct controller_impl { bool is_new_version() { auto ucb = upgrade_complete_block(); - if (ucb) return head->block_num > *ucb; + if (ucb) return head->block_num >= *ucb; return false; } From 9f44c2ebbe5b82751c16c25fa35207ae906d8c56 Mon Sep 17 00:00:00 2001 From: oldcold Date: Sat, 27 Apr 2019 20:39:35 +0800 Subject: [PATCH 48/59] bug fix: add null ptr check during fetching scp from blk extn. --- libraries/chain/pbft_database.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index c1b777692db..e119c23e151 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -904,24 +904,25 @@ namespace eosio { pbft_stable_checkpoint pbft_database::fetch_stable_checkpoint_from_blk_extn(const signed_block_ptr &b) { try { - auto &ext = b->block_extensions; + if (b) { + auto &ext = b->block_extensions; - for (auto it = ext.begin(); it != ext.end();) { - if (it->first == static_cast(block_extension_type::pbft_stable_checkpoint)) - { - auto scp_v = it->second; - fc::datastream ds_decode(scp_v.data(), scp_v.size()); + for (auto it = ext.begin(); it != ext.end();) { + if (it->first == static_cast(block_extension_type::pbft_stable_checkpoint)) { + auto scp_v = it->second; + fc::datastream ds_decode(scp_v.data(), scp_v.size()); - pbft_stable_checkpoint scp_decode; - fc::raw::unpack(ds_decode, scp_decode); + pbft_stable_checkpoint scp_decode; + fc::raw::unpack(ds_decode, scp_decode); - if (is_valid_stable_checkpoint(scp_decode)) { - return scp_decode; + if (is_valid_stable_checkpoint(scp_decode)) { + return scp_decode; + } else { + it = ext.erase(it); + } } else { - it = ext.erase(it); + it++; } - } else { - it++; } } } catch(...) { From d9c9a5481f895f24fb6e8f9e4d3316c248f15d85 Mon Sep 17 00:00:00 2001 From: oldcold Date: Sat, 27 Apr 2019 22:22:40 +0800 Subject: [PATCH 49/59] add upgrade related log. --- libraries/chain/controller.cpp | 10 ++++++++++ plugins/pbft_plugin/pbft_plugin.cpp | 1 + 2 files changed, 11 insertions(+) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 03cf5f57167..a2d4fde14f9 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1330,12 +1330,22 @@ struct controller_impl { up.upgrade_complete_block_num.reset(); up.upgrade_complete_block_num.emplace( head->block_num); }); + wlog("setting upgrade complete block num to ${b}", ("b", head->block_num)); } } auto new_version = is_new_version(); auto upgrading = is_upgrading(); + uint32_t utb_num = 0; + if (utb) utb_num = *utb; + + uint32_t ucb_num = 0; + if (ucb) ucb_num = *utb; + + ilog("head block num is ${h}, new version is ${nv}, upgrading is ${u}, target block is ${utb}, complete block is ${ucb}", + ("h", head->block_num)("nv", new_version)("u", upgrading)("utb", utb_num)("ucb", ucb_num)); + pending->_block_status = s; pending->_producer_block_id = producer_block_id; pending->_signer = signer; diff --git a/plugins/pbft_plugin/pbft_plugin.cpp b/plugins/pbft_plugin/pbft_plugin.cpp index 3b1762ba744..5091eb1089a 100644 --- a/plugins/pbft_plugin/pbft_plugin.cpp +++ b/plugins/pbft_plugin/pbft_plugin.cpp @@ -147,6 +147,7 @@ namespace eosio { "* *\n" "************************************\n" ); upgraded = true; + ilog("new version is ${nv}, upgrading is ${u}",("nv", chain.is_upgraded())("u", chain.under_upgrade())); } return (new_version && (!is_syncing() && !is_replaying())); From d1a90096966bc31fc3764f9def3ef1070af1e22c Mon Sep 17 00:00:00 2001 From: deadlock Date: Sun, 28 Apr 2019 01:02:21 +0800 Subject: [PATCH 50/59] migration support for snapshot --- libraries/chain/controller.cpp | 59 +++++++++++++------ .../include/eosio/chain/chain_snapshot.hpp | 7 ++- .../chain/include/eosio/chain/snapshot.hpp | 50 ++++++++++++++++ 3 files changed, 96 insertions(+), 20 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 03cf5f57167..29a8fcd4bd8 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -504,6 +504,10 @@ struct controller_impl { section.add_row(conf.genesis, db); }); + snapshot->write_section([this]( auto §ion ){ + section.add_row(batch_pbft_snapshot_migration{}, db); + }); + snapshot->write_section([this]( auto §ion ){ section.template add_row(*fork_db.head(), db); }); @@ -536,18 +540,33 @@ struct controller_impl { header.validate(); }); + bool migrated = snapshot->has_section(); + if(migrated) { + snapshot->read_section([this](auto §ion) { + block_header_state head_header_state; + section.read_row(head_header_state, db); + + auto head_state = std::make_shared(head_header_state); + fork_db.set(head_state); + fork_db.set_validity(head_state, true); + fork_db.mark_in_current_chain(head_state, true); + head = head_state; + snapshot_head_block = head->block_num; + }); + }else{ + snapshot->read_section([this](snapshot_reader::section_reader §ion) { + block_header_state head_header_state; + section.read_pbft_migrate_row(head_header_state, db); + + auto head_state = std::make_shared(head_header_state); + fork_db.set(head_state); + fork_db.set_validity(head_state, true); + fork_db.mark_in_current_chain(head_state, true); + head = head_state; + snapshot_head_block = head->block_num; + }); - snapshot->read_section([this]( auto §ion ){ - block_header_state head_header_state; - section.read_row(head_header_state, db); - - auto head_state = std::make_shared(head_header_state); - fork_db.set(head_state); - fork_db.set_validity(head_state, true); - fork_db.mark_in_current_chain(head_state, true); - head = head_state; - snapshot_head_block = head->block_num; - }); + } controller_index_set::walk_indices([this, &snapshot]( auto utils ){ using value_t = typename decltype(utils)::index_t::value_type; @@ -557,14 +576,16 @@ struct controller_impl { return; } - snapshot->read_section([this]( auto& section ) { - bool more = !section.empty(); - while(more) { - decltype(utils)::create(db, [this, §ion, &more]( auto &row ) { - more = section.read_row(row, db); - }); - } - }); + if(snapshot->has_section()){ + snapshot->read_section([this]( auto& section ) { + bool more = !section.empty(); + while(more) { + decltype(utils)::create(db, [this, §ion, &more]( auto &row ) { + more = section.read_row(row, db); + }); + } + }); + } }); read_contract_tables_from_snapshot(snapshot); diff --git a/libraries/chain/include/eosio/chain/chain_snapshot.hpp b/libraries/chain/include/eosio/chain/chain_snapshot.hpp index 3b3e64f264f..884293360a5 100644 --- a/libraries/chain/include/eosio/chain/chain_snapshot.hpp +++ b/libraries/chain/include/eosio/chain/chain_snapshot.hpp @@ -29,6 +29,11 @@ struct chain_snapshot_header { } }; +struct batch_pbft_snapshot_migration{ + bool migrated = true; +}; + } } -FC_REFLECT(eosio::chain::chain_snapshot_header,(version)) \ No newline at end of file +FC_REFLECT(eosio::chain::chain_snapshot_header,(version)) +FC_REFLECT(eosio::chain::batch_pbft_snapshot_migration,(migrated)) diff --git a/libraries/chain/include/eosio/chain/snapshot.hpp b/libraries/chain/include/eosio/chain/snapshot.hpp index 499fbe29960..45470549e85 100644 --- a/libraries/chain/include/eosio/chain/snapshot.hpp +++ b/libraries/chain/include/eosio/chain/snapshot.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -219,10 +220,53 @@ namespace eosio { namespace chain { T& data; }; + template + struct snapshot_pbft_migrate_row_reader : abstract_snapshot_row_reader { + explicit snapshot_pbft_migrate_row_reader( T& data ) + :data(data) {} + + + void provide(std::istream& in) const override { + row_validation_helper::apply(data, [&in,this](){ + if(typeid(T)== typeid(eosio::chain::block_header_state)){ + std::ostringstream sstream; + sstream << in.rdbuf(); + std::string str(sstream.str()); + //prepend uint32_t 0 + str = "\0\0\0\0" + str; + const char* ptr = str.c_str(); + fc::datastream tmp(ptr, str.size()); + fc::raw::unpack(tmp, data); + auto original_data_length = tmp.tellp() - 4; + in.seekg(original_data_length); + }else{ + fc::raw::unpack(in, data); + } + }); + } + + void provide(const fc::variant& var) const override { + row_validation_helper::apply(data, [&var,this]() { + fc::from_variant(var, data); + }); + } + + std::string row_type_name() const override { + return boost::core::demangle( typeid( T ).name() ); + } + + T& data; + }; + template snapshot_row_reader make_row_reader( T& data ) { return snapshot_row_reader(data); } + + template + snapshot_pbft_migrate_row_reader make_pbft_migrate_row_reader( T& data ) { + return snapshot_pbft_migrate_row_reader(data); + } } class snapshot_reader { @@ -249,6 +293,12 @@ namespace eosio { namespace chain { return result; } + template + auto read_pbft_migrate_row( T& out, chainbase::database& db ) -> std::enable_if_t, typename detail::snapshot_row_traits::snapshot_type>::value,bool> { + auto reader = detail::make_pbft_migrate_row_reader(out); + return _reader.read_row(reader); + } + bool empty() { return _reader.empty(); } From 66e7de271b018b0b7ff5de7ef23a7c88c366971a Mon Sep 17 00:00:00 2001 From: oldcold Date: Sun, 28 Apr 2019 02:06:54 +0800 Subject: [PATCH 51/59] upgrade log formatted --- libraries/chain/controller.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 79dc88aa0b0..25534e5def0 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1358,14 +1358,17 @@ struct controller_impl { auto new_version = is_new_version(); auto upgrading = is_upgrading(); + uint32_t utb_num = 0; - if (utb) utb_num = *utb; + if (upgrade_target_block()) utb_num = *upgrade_target_block(); uint32_t ucb_num = 0; - if (ucb) ucb_num = *utb; + if (upgrade_complete_block()) ucb_num = *upgrade_complete_block(); - ilog("head block num is ${h}, new version is ${nv}, upgrading is ${u}, target block is ${utb}, complete block is ${ucb}", - ("h", head->block_num)("nv", new_version)("u", upgrading)("utb", utb_num)("ucb", ucb_num)); + if (utb && head->bft_irreversible_blocknum < utb + 100) { + ilog("head block num is ${h}, new version is ${nv}, upgrading is ${u}, target block is ${utb}, complete block is ${ucb}", + ("h", head->block_num)("nv", new_version)("u", upgrading)("utb", utb_num)("ucb", ucb_num)); + } pending->_block_status = s; pending->_producer_block_id = producer_block_id; From b85b91e4af53a1d228bdfefb31a61779b98ad895 Mon Sep 17 00:00:00 2001 From: oldcold Date: Sun, 28 Apr 2019 16:23:18 +0800 Subject: [PATCH 52/59] change type of ucb in upo. --- libraries/chain/controller.cpp | 17 ++++++++++------- .../eosio/chain/global_property_object.hpp | 2 +- libraries/chain/pbft_database.cpp | 8 ++++---- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 25534e5def0..3a408e813ad 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -853,7 +853,7 @@ struct controller_impl { try { const auto& upo = db.get(); if (upo.upgrade_target_block_num > 0) { - return upo.upgrade_target_block_num; + return optional{upo.upgrade_target_block_num}; } else { return optional{}; } @@ -867,7 +867,11 @@ struct controller_impl { optional upgrade_complete_block() { try { const auto& upo = db.get(); - return upo.upgrade_complete_block_num; + if (upo.upgrade_complete_block_num > 0) { + return optional{upo.upgrade_complete_block_num}; + } else { + return optional{}; + }; } catch( const boost::exception& e) { db.create([](auto&){}); return optional{}; @@ -1344,12 +1348,11 @@ struct controller_impl { auto utb = upgrade_target_block(); auto ucb = upgrade_complete_block(); - if (utb) { - if (head->dpos_irreversible_blocknum >= *utb && !ucb) { + if (utb && !ucb) { + if (head->dpos_irreversible_blocknum >= *utb) { const auto& upo = db.get(); db.modify( upo, [&]( auto& up ) { - up.upgrade_complete_block_num.reset(); - up.upgrade_complete_block_num.emplace( head->block_num); + up.upgrade_complete_block_num = head->block_num; }); wlog("setting upgrade complete block num to ${b}", ("b", head->block_num)); } @@ -1365,7 +1368,7 @@ struct controller_impl { uint32_t ucb_num = 0; if (upgrade_complete_block()) ucb_num = *upgrade_complete_block(); - if (utb && head->bft_irreversible_blocknum < utb + 100) { + if (utb && head->bft_irreversible_blocknum < utb_num + 100) { ilog("head block num is ${h}, new version is ${nv}, upgrading is ${u}, target block is ${utb}, complete block is ${ucb}", ("h", head->block_num)("nv", new_version)("u", upgrading)("utb", utb_num)("ucb", ucb_num)); } diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index 7627a40adb5..98f86939ad6 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -51,7 +51,7 @@ namespace eosio { namespace chain { id_type id; block_num_type upgrade_target_block_num = 0; - optional upgrade_complete_block_num; + block_num_type upgrade_complete_block_num = 0; }; diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index e119c23e151..14f97cf49ac 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -971,8 +971,8 @@ namespace eosio { if (lscb_num == 0) { const auto& ucb = ctrl.get_upgrade_properties().upgrade_complete_block_num; - if (!ucb) return lscb_info; - auto bs = ctrl.fetch_block_state_by_number(*ucb); + if (ucb == 0) return lscb_info; + auto bs = ctrl.fetch_block_state_by_number(ucb); if (!bs) return lscb_info; current_schedule = bs->active_schedule; new_schedule = bs->pending_schedule; @@ -1009,8 +1009,8 @@ namespace eosio { auto checkpoint = [&](const block_num_type &in) { const auto& ucb = ctrl.get_upgrade_properties().upgrade_complete_block_num; - if (!ucb) return false; - return in >= *ucb + if (ucb == 0) return false; + return in >= ucb && (in % 100 == 1 || std::find(prepare_watermarks.begin(), prepare_watermarks.end(), in) != prepare_watermarks.end()); }; From 4c24d234e47181004d235218f176322cb814f1d5 Mon Sep 17 00:00:00 2001 From: deadlock Date: Sun, 28 Apr 2019 16:54:44 +0800 Subject: [PATCH 53/59] bugfix: snapshot migration prefer vector to string --- libraries/chain/include/eosio/chain/snapshot.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/chain/include/eosio/chain/snapshot.hpp b/libraries/chain/include/eosio/chain/snapshot.hpp index 45470549e85..70486b747f9 100644 --- a/libraries/chain/include/eosio/chain/snapshot.hpp +++ b/libraries/chain/include/eosio/chain/snapshot.hpp @@ -233,11 +233,11 @@ namespace eosio { namespace chain { sstream << in.rdbuf(); std::string str(sstream.str()); //prepend uint32_t 0 - str = "\0\0\0\0" + str; - const char* ptr = str.c_str(); - fc::datastream tmp(ptr, str.size()); - fc::raw::unpack(tmp, data); - auto original_data_length = tmp.tellp() - 4; + std::vector tmp(str.begin(), str.end()); + tmp.insert(tmp.begin(), {0,0,0,0}); + fc::datastream tmp_ds(tmp.data(), tmp.size()); + fc::raw::unpack(tmp_ds, data); + auto original_data_length = tmp_ds.tellp() - 4; in.seekg(original_data_length); }else{ fc::raw::unpack(in, data); From 630154ae7c4cab105dcd095c2aa909769ac8c2fd Mon Sep 17 00:00:00 2001 From: oldcold Date: Sun, 28 Apr 2019 19:16:26 +0800 Subject: [PATCH 54/59] add new version debug log --- libraries/chain/controller.cpp | 25 ++++++++++++++++++- .../include/eosio/chain/pbft_database.hpp | 3 +-- libraries/chain/pbft.cpp | 6 +++++ plugins/producer_plugin/producer_plugin.cpp | 6 ++++- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 3a408e813ad..96a323c2103 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -904,6 +904,9 @@ struct controller_impl { }); try { + if (upgrade_target_block()) { + ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); + } set_pbft_lib(); set_pbft_lscb(); if (add_to_fork_db) { @@ -935,6 +938,9 @@ struct controller_impl { // push the state for pending. pending->push(); + if (upgrade_target_block()) { + ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); + } } // The returned scoped_exit should not exceed the lifetime of the pending which existed when make_block_restore_point was called. @@ -1331,6 +1337,9 @@ struct controller_impl { void start_block( block_timestamp_type when, uint16_t confirm_block_count, controller::block_status s, const optional& producer_block_id , std::function signer = nullptr) { + if (upgrade_target_block()) { + ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); + } EOS_ASSERT( !pending, block_validate_exception, "pending block already exists" ); auto guard_pending = fc::make_scoped_exit([this](){ @@ -1471,6 +1480,9 @@ struct controller_impl { } guard_pending.cancel(); + if (upgrade_target_block()) { + ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); + } } // start_block @@ -1486,6 +1498,9 @@ struct controller_impl { void apply_block( const signed_block_ptr& b, controller::block_status s ) { try { try { // EOS_ASSERT( b->block_extensions.size() == 0, block_validate_exception, "no supported extensions" ); + if (upgrade_target_block()) { + ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); + } auto producer_block_id = b->id(); start_block( b->timestamp, b->confirmed, s , producer_block_id); @@ -1576,6 +1591,9 @@ struct controller_impl { abort_block(); throw; } + if (upgrade_target_block()) { + ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); + } } FC_CAPTURE_AND_RETHROW() } /// apply_block std::future create_block_state_future( const signed_block_ptr& b ) { @@ -1599,6 +1617,9 @@ struct controller_impl { } void push_block( std::future& block_state_future ) { + if (upgrade_target_block()) { + ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); + } controller::block_status s = controller::block_status::complete; EOS_ASSERT(!pending, block_validate_exception, "it is not valid to push a block when there is a pending block"); auto reset_prod_light_validation = fc::make_scoped_exit([old_value=trusted_producer_light_validation, this]() { @@ -1624,7 +1645,9 @@ struct controller_impl { } set_pbft_lscb(); - + if (upgrade_target_block()) { + ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); + } } FC_LOG_AND_RETHROW( ) } diff --git a/libraries/chain/include/eosio/chain/pbft_database.hpp b/libraries/chain/include/eosio/chain/pbft_database.hpp index 13808e00330..fbd3f7adb46 100644 --- a/libraries/chain/include/eosio/chain/pbft_database.hpp +++ b/libraries/chain/include/eosio/chain/pbft_database.hpp @@ -598,9 +598,8 @@ namespace eosio { bool should_stop_view_change(const pbft_view_change &vc); block_num_type get_current_pbft_watermark(); - - private: controller &ctrl; + private: pbft_state_multi_index_type pbft_state_index; pbft_view_state_multi_index_type view_state_index; pbft_checkpoint_state_multi_index_type checkpoint_index; diff --git a/libraries/chain/pbft.cpp b/libraries/chain/pbft.cpp index 25ee10c9cf0..020e714ef7c 100644 --- a/libraries/chain/pbft.cpp +++ b/libraries/chain/pbft.cpp @@ -191,6 +191,7 @@ namespace eosio { void psm_prepared_state::send_commit(psm_machine *m, pbft_database &pbft_db) { auto commits = pbft_db.send_and_add_pbft_commit(m->get_commits_cache(), m->get_current_view()); + ilog("new version is ${nv}, upgrading is ${u}", ("nv", pbft_db.ctrl.is_upgraded())("u", pbft_db.ctrl.under_upgrade())); if (!commits.empty()) { m->set_commits_cache(commits); @@ -205,6 +206,7 @@ namespace eosio { pbft_db.send_pbft_checkpoint(); m->transit_to_committed_state(this, false); } + ilog("new version is ${nv}, upgrading is ${u}", ("nv", pbft_db.ctrl.is_upgraded())("u", pbft_db.ctrl.under_upgrade())); } void psm_prepared_state::on_view_change(psm_machine *m, pbft_view_change &e, pbft_database &pbft_db) { @@ -259,7 +261,10 @@ namespace eosio { } void psm_committed_state::send_prepare(psm_machine *m, pbft_database &pbft_db) { + ilog("new version is ${nv}, upgrading is ${u}", ("nv", pbft_db.ctrl.is_upgraded())("u", pbft_db.ctrl.under_upgrade())); + auto prepares = pbft_db.send_and_add_pbft_prepare(m->get_prepares_cache(), m->get_current_view()); + ilog("new version is ${nv}, upgrading is ${u}", ("nv", pbft_db.ctrl.is_upgraded())("u", pbft_db.ctrl.under_upgrade())); if (!prepares.empty()) { m->set_prepares_cache(prepares); @@ -267,6 +272,7 @@ namespace eosio { //if prepare >= 2f+1, transit to prepared if (pbft_db.should_prepared()) m->transit_to_prepared_state(this); + ilog("new version is ${nv}, upgrading is ${u}", ("nv", pbft_db.ctrl.is_upgraded())("u", pbft_db.ctrl.under_upgrade())); } void psm_committed_state::on_commit(psm_machine *m, pbft_commit &e, pbft_database &pbft_db) { diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index b3a46fddf11..4022ad53794 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -301,7 +301,8 @@ class producer_plugin_impl : public std::enable_shared_from_thischain(); - /* de-dupe here... no point in aborting block if we already know the block */ + + /* de-dupe here... no point in aborting block if we already know the block */ auto existing = chain.fetch_block_by_id( id ); if( existing ) { return; } @@ -356,6 +357,9 @@ class producer_plugin_impl : public std::enable_shared_from_thisid()))("t",block->timestamp) ("count",block->transactions.size())("lib",chain.last_irreversible_block_num()) ("confs", block->confirmed)("latency", (fc::time_point::now() - block->timestamp).count()/1000 ) ); + if (chain.under_upgrade()) { + wlog("upgrading..."); + } } } } From 0490bff82d54041887cd6bd117a91ea5a7012e9e Mon Sep 17 00:00:00 2001 From: oldcold Date: Mon, 29 Apr 2019 11:38:39 +0800 Subject: [PATCH 55/59] mark fork_db regardless of the version; new version now is when head greater than ucb (not including). --- libraries/chain/controller.cpp | 12 ++++++------ libraries/chain/fork_database.cpp | 10 ++-------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 96a323c2103..06a525e2279 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -853,13 +853,13 @@ struct controller_impl { try { const auto& upo = db.get(); if (upo.upgrade_target_block_num > 0) { - return optional{upo.upgrade_target_block_num}; + return upo.upgrade_target_block_num; } else { return optional{}; } } catch( const boost::exception& e) { wlog("no upo found, regenerating..."); - db.create([](auto&){}); +// db.create([](auto&){}); return optional{}; } } @@ -868,19 +868,19 @@ struct controller_impl { try { const auto& upo = db.get(); if (upo.upgrade_complete_block_num > 0) { - return optional{upo.upgrade_complete_block_num}; + return upo.upgrade_complete_block_num; } else { return optional{}; }; } catch( const boost::exception& e) { - db.create([](auto&){}); +// db.create([](auto&){}); return optional{}; } } bool is_new_version() { auto ucb = upgrade_complete_block(); - if (ucb) return head->block_num >= *ucb; + if (ucb) return head->block_num > *ucb; return false; } @@ -889,7 +889,7 @@ struct controller_impl { auto ucb = upgrade_complete_block(); auto is_upgrading = false; if (utb) is_upgrading = head->block_num >= *utb; - if (ucb) is_upgrading = is_upgrading && head->block_num < *ucb; + if (ucb) is_upgrading = is_upgrading && head->block_num <= *ucb; return is_upgrading; } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index cc7e0e18b3d..5e268d91d92 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -189,14 +189,8 @@ namespace eosio { namespace chain { auto prior = my->index.find( n->block->previous ); //TODO: to be optimised. - if (new_version) { - if ((*prior)->pbft_prepared) { - mark_pbft_prepared_fork(*prior); - } - if (((*prior)->pbft_my_prepare)) { - mark_pbft_my_prepare_fork(*prior); - } - } + if ((*prior)->pbft_prepared) mark_pbft_prepared_fork(*prior); + if (((*prior)->pbft_my_prepare)) mark_pbft_my_prepare_fork(*prior); my->head = *my->index.get().begin(); From be6c4f507af41d4d08c745d08b4aca433b40523c Mon Sep 17 00:00:00 2001 From: deadlock Date: Mon, 29 Apr 2019 17:11:39 +0800 Subject: [PATCH 56/59] add more log , to be revert soon --- libraries/chain/controller.cpp | 3 +++ libraries/chain/fork_database.cpp | 6 ++++-- plugins/producer_plugin/producer_plugin.cpp | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 06a525e2279..6865851ec3c 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -880,6 +880,9 @@ struct controller_impl { bool is_new_version() { auto ucb = upgrade_complete_block(); + block_num_type tmp = 0; + if(ucb) tmp = *ucb; + ilog("in is_new_version : ucb is ${ucb}, head is ${head}",("ucb",tmp)("head",head->block_num)); if (ucb) return head->block_num > *ucb; return false; } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 5e268d91d92..4f603795c0a 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -189,8 +189,10 @@ namespace eosio { namespace chain { auto prior = my->index.find( n->block->previous ); //TODO: to be optimised. - if ((*prior)->pbft_prepared) mark_pbft_prepared_fork(*prior); - if (((*prior)->pbft_my_prepare)) mark_pbft_my_prepare_fork(*prior); + if( prior != my->index.end()){ + if ((*prior)->pbft_prepared) mark_pbft_prepared_fork(*prior); + if ((*prior)->pbft_my_prepare) mark_pbft_my_prepare_fork(*prior); + } my->head = *my->index.get().begin(); diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 4022ad53794..8525d4d5d72 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -1107,8 +1107,10 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { } auto new_version = chain.is_upgraded(); + ilog("producer plugin before abort_block: new version is ${nv}, upgrading is ${u}", ("nv", chain.is_upgraded())("u", chain.under_upgrade())); - if (_pending_block_mode == pending_block_mode::producing && !new_version) { + + if (_pending_block_mode == pending_block_mode::producing && !new_version) { // determine if our watermark excludes us from producing at this point if (currrent_watermark_itr != _producer_watermarks.end()) { if (currrent_watermark_itr->second >= hbs->block_num + 1) { @@ -1151,6 +1153,7 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { } chain.abort_block(); + ilog("producer plugin after abort_block: new version is ${nv}, upgrading is ${u}", ("nv", chain.is_upgraded())("u", chain.under_upgrade())); chain.start_block(block_time, blocks_to_confirm, signature_provider); } FC_LOG_AND_DROP(); From bff69909a6722a6366c11392c6f42b68b6a09fb2 Mon Sep 17 00:00:00 2001 From: oldcold Date: Mon, 29 Apr 2019 17:58:40 +0800 Subject: [PATCH 57/59] bug fix: force generate upo if not found. --- libraries/chain/controller.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 6865851ec3c..5a26c0a61af 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -687,8 +687,14 @@ struct controller_impl { // *bos end* + //if not upo found, generate a new one. + try { + db.get(); + } catch( const boost::exception& e) { + wlog("no upo found, generating..."); + db.create([](auto&){}); + } - db.create([](auto&){}); authorization.initialize_database(); resource_limits.initialize_database(); @@ -858,8 +864,8 @@ struct controller_impl { return optional{}; } } catch( const boost::exception& e) { - wlog("no upo found, regenerating..."); -// db.create([](auto&){}); + wlog("no upo found, generating..."); + db.create([](auto&){}); return optional{}; } } @@ -871,9 +877,10 @@ struct controller_impl { return upo.upgrade_complete_block_num; } else { return optional{}; - }; + } } catch( const boost::exception& e) { -// db.create([](auto&){}); + wlog("no upo found, generating..."); + db.create([](auto&){}); return optional{}; } } From 4eebe6a6a0a13e554e66335b683c7123c108b724 Mon Sep 17 00:00:00 2001 From: deadlock Date: Mon, 29 Apr 2019 20:26:22 +0800 Subject: [PATCH 58/59] fix unit test --- unittests/pbft_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unittests/pbft_tests.cpp b/unittests/pbft_tests.cpp index a9e3fb0bf67..50d8e573fe4 100644 --- a/unittests/pbft_tests.cpp +++ b/unittests/pbft_tests.cpp @@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE(can_advance_lib_after_upgrade) { BOOST_CHECK_EQUAL(ctrl.last_irreversible_block_num(), 0); BOOST_CHECK_EQUAL(ctrl.head_block_num(), 2); tester.produce_blocks(150); - BOOST_CHECK_EQUAL(ctrl.last_irreversible_block_num(), 150); + BOOST_CHECK_EQUAL(ctrl.last_irreversible_block_num(), 151); BOOST_CHECK_EQUAL(ctrl.head_block_num(), 152); is_upgraded = ctrl.is_upgraded(); @@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE(can_advance_lib_after_upgrade) { tester.produce_blocks(10); BOOST_CHECK_EQUAL(ctrl.pending_pbft_lib(), false); - BOOST_CHECK_EQUAL(ctrl.last_irreversible_block_num(), 150); + BOOST_CHECK_EQUAL(ctrl.last_irreversible_block_num(), 151); BOOST_CHECK_EQUAL(ctrl.head_block_num(), 162); pbft_ctrl.maybe_pbft_prepare(); From 9f0cde3b016d1b186ea941d4d744243415c78f2f Mon Sep 17 00:00:00 2001 From: oldcold Date: Tue, 30 Apr 2019 19:14:25 +0800 Subject: [PATCH 59/59] remove debug log; reformat new view validation; revert txn_test_gen_plugin to the original. --- libraries/chain/controller.cpp | 114 +++++----------- libraries/chain/fork_database.cpp | 2 +- .../chain/include/eosio/chain/controller.hpp | 2 - .../include/eosio/chain/pbft_database.hpp | 1 - libraries/chain/pbft.cpp | 51 +++++-- libraries/chain/pbft_database.cpp | 93 ++++++------- plugins/pbft_plugin/pbft_plugin.cpp | 1 - plugins/producer_plugin/producer_plugin.cpp | 7 +- plugins/txn_test_gen_plugin/CMakeLists.txt | 2 +- plugins/txn_test_gen_plugin/README.md | 2 +- .../txn_test_gen_plugin.cpp | 126 ++++-------------- 11 files changed, 137 insertions(+), 264 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 5a26c0a61af..93c5b0c63fb 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -381,6 +381,9 @@ struct controller_impl { } } + //do upgrade migration if necessary; + migrate_upgrade(); + if( shutdown() ) return; const auto& ubi = reversible_blocks.get_index(); @@ -418,6 +421,17 @@ struct controller_impl { //*bos end* } + void migrate_upgrade() { + //generate upo. + try { + db.get(); + } catch( const boost::exception& e) { + wlog("no upo found, generating..."); + db.create([](auto&){}); + } + + } + ~controller_impl() { pending.reset(); @@ -687,14 +701,6 @@ struct controller_impl { // *bos end* - //if not upo found, generate a new one. - try { - db.get(); - } catch( const boost::exception& e) { - wlog("no upo found, generating..."); - db.create([](auto&){}); - } - authorization.initialize_database(); resource_limits.initialize_database(); @@ -856,40 +862,28 @@ struct controller_impl { // "bos end" optional upgrade_target_block() { - try { - const auto& upo = db.get(); - if (upo.upgrade_target_block_num > 0) { - return upo.upgrade_target_block_num; - } else { - return optional{}; - } - } catch( const boost::exception& e) { - wlog("no upo found, generating..."); - db.create([](auto&){}); - return optional{}; - } + + const auto& upo = db.get(); + if (upo.upgrade_target_block_num > 0) { + return upo.upgrade_target_block_num; + } else { + return optional{}; + } } optional upgrade_complete_block() { - try { - const auto& upo = db.get(); - if (upo.upgrade_complete_block_num > 0) { - return upo.upgrade_complete_block_num; - } else { - return optional{}; - } - } catch( const boost::exception& e) { - wlog("no upo found, generating..."); - db.create([](auto&){}); - return optional{}; - } + + const auto& upo = db.get(); + if (upo.upgrade_complete_block_num > 0) { + return upo.upgrade_complete_block_num; + } else { + return optional{}; + } } bool is_new_version() { auto ucb = upgrade_complete_block(); - block_num_type tmp = 0; - if(ucb) tmp = *ucb; - ilog("in is_new_version : ucb is ${ucb}, head is ${head}",("ucb",tmp)("head",head->block_num)); + //new version starts from the next block of ucb, this is to avoid inconsistency after pre calculation inside schedule loop. if (ucb) return head->block_num > *ucb; return false; } @@ -914,9 +908,6 @@ struct controller_impl { }); try { - if (upgrade_target_block()) { - ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); - } set_pbft_lib(); set_pbft_lscb(); if (add_to_fork_db) { @@ -948,9 +939,6 @@ struct controller_impl { // push the state for pending. pending->push(); - if (upgrade_target_block()) { - ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); - } } // The returned scoped_exit should not exceed the lifetime of the pending which existed when make_block_restore_point was called. @@ -1347,9 +1335,6 @@ struct controller_impl { void start_block( block_timestamp_type when, uint16_t confirm_block_count, controller::block_status s, const optional& producer_block_id , std::function signer = nullptr) { - if (upgrade_target_block()) { - ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); - } EOS_ASSERT( !pending, block_validate_exception, "pending block already exists" ); auto guard_pending = fc::make_scoped_exit([this](){ @@ -1373,25 +1358,13 @@ struct controller_impl { db.modify( upo, [&]( auto& up ) { up.upgrade_complete_block_num = head->block_num; }); - wlog("setting upgrade complete block num to ${b}", ("b", head->block_num)); + wlog("system is going to be new version after the block ${b}", ("b", head->block_num)); } } auto new_version = is_new_version(); auto upgrading = is_upgrading(); - - uint32_t utb_num = 0; - if (upgrade_target_block()) utb_num = *upgrade_target_block(); - - uint32_t ucb_num = 0; - if (upgrade_complete_block()) ucb_num = *upgrade_complete_block(); - - if (utb && head->bft_irreversible_blocknum < utb_num + 100) { - ilog("head block num is ${h}, new version is ${nv}, upgrading is ${u}, target block is ${utb}, complete block is ${ucb}", - ("h", head->block_num)("nv", new_version)("u", upgrading)("utb", utb_num)("ucb", ucb_num)); - } - pending->_block_status = s; pending->_producer_block_id = producer_block_id; pending->_signer = signer; @@ -1447,7 +1420,6 @@ struct controller_impl { ("proposed_num", *gpo.proposed_schedule_block_num)("n", pending->_pending_block_state->block_num) ("lib", lib_num) ("schedule", static_cast(gpo.proposed_schedule))); - } pending->_pending_block_state->set_new_producers(gpo.proposed_schedule); @@ -1490,9 +1462,6 @@ struct controller_impl { } guard_pending.cancel(); - if (upgrade_target_block()) { - ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); - } } // start_block @@ -1508,9 +1477,6 @@ struct controller_impl { void apply_block( const signed_block_ptr& b, controller::block_status s ) { try { try { // EOS_ASSERT( b->block_extensions.size() == 0, block_validate_exception, "no supported extensions" ); - if (upgrade_target_block()) { - ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); - } auto producer_block_id = b->id(); start_block( b->timestamp, b->confirmed, s , producer_block_id); @@ -1574,11 +1540,6 @@ struct controller_impl { finalize_block(); - if (producer_block_id != pending->_pending_block_state->header.id()) { - ilog("producer block: ${b}", ("b", (*b))); - ilog("pending block: ${p}", ("p", (*(pending->_pending_block_state)))); - } - // this implicitly asserts that all header fields (less the signature) are identical EOS_ASSERT(producer_block_id == pending->_pending_block_state->header.id(), block_validate_exception, "Block ID does not match", @@ -1601,9 +1562,6 @@ struct controller_impl { abort_block(); throw; } - if (upgrade_target_block()) { - ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); - } } FC_CAPTURE_AND_RETHROW() } /// apply_block std::future create_block_state_future( const signed_block_ptr& b ) { @@ -1627,9 +1585,6 @@ struct controller_impl { } void push_block( std::future& block_state_future ) { - if (upgrade_target_block()) { - ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); - } controller::block_status s = controller::block_status::complete; EOS_ASSERT(!pending, block_validate_exception, "it is not valid to push a block when there is a pending block"); auto reset_prod_light_validation = fc::make_scoped_exit([old_value=trusted_producer_light_validation, this]() { @@ -1655,9 +1610,6 @@ struct controller_impl { } set_pbft_lscb(); - if (upgrade_target_block()) { - ilog("new version is ${nv}, upgrading is ${u}", ("nv", is_new_version())("u", is_upgrading())); - } } FC_LOG_AND_RETHROW( ) } @@ -2747,11 +2699,6 @@ void controller::set_name_list(int64_t list, int64_t action, std::vectorset_pbft_lib(); - my->set_pbft_lscb(); -} - const upgrade_property_object& controller::get_upgrade_properties()const { return my->db.get(); } @@ -2764,6 +2711,7 @@ bool controller::under_upgrade() const { return my->is_upgrading(); } +// this will be used in unit_test only, should not be called anywhere else. void controller::set_upo(uint32_t target_block_num) { try { const auto& upo = my->db.get(); diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 4f603795c0a..5509ff631b2 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -189,7 +189,7 @@ namespace eosio { namespace chain { auto prior = my->index.find( n->block->previous ); //TODO: to be optimised. - if( prior != my->index.end()){ + if (prior != my->index.end()) { if ((*prior)->pbft_prepared) mark_pbft_prepared_fork(*prior); if ((*prior)->pbft_my_prepare) mark_pbft_my_prepare_fork(*prior); } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 0bce6409896..392a052819d 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -303,8 +303,6 @@ namespace eosio { namespace chain { signal accepted_confirmation; signal bad_alloc; - void set_lib()const; - const upgrade_property_object& get_upgrade_properties()const; bool is_upgraded()const; bool under_upgrade()const; diff --git a/libraries/chain/include/eosio/chain/pbft_database.hpp b/libraries/chain/include/eosio/chain/pbft_database.hpp index fbd3f7adb46..01c36132a6b 100644 --- a/libraries/chain/include/eosio/chain/pbft_database.hpp +++ b/libraries/chain/include/eosio/chain/pbft_database.hpp @@ -485,7 +485,6 @@ namespace eosio { tag, composite_key< pbft_checkpoint_state, -// member, member >, composite_key_compare> diff --git a/libraries/chain/pbft.cpp b/libraries/chain/pbft.cpp index 020e714ef7c..08c52087fd4 100644 --- a/libraries/chain/pbft.cpp +++ b/libraries/chain/pbft.cpp @@ -191,7 +191,6 @@ namespace eosio { void psm_prepared_state::send_commit(psm_machine *m, pbft_database &pbft_db) { auto commits = pbft_db.send_and_add_pbft_commit(m->get_commits_cache(), m->get_current_view()); - ilog("new version is ${nv}, upgrading is ${u}", ("nv", pbft_db.ctrl.is_upgraded())("u", pbft_db.ctrl.under_upgrade())); if (!commits.empty()) { m->set_commits_cache(commits); @@ -206,7 +205,6 @@ namespace eosio { pbft_db.send_pbft_checkpoint(); m->transit_to_committed_state(this, false); } - ilog("new version is ${nv}, upgrading is ${u}", ("nv", pbft_db.ctrl.is_upgraded())("u", pbft_db.ctrl.under_upgrade())); } void psm_prepared_state::on_view_change(psm_machine *m, pbft_view_change &e, pbft_database &pbft_db) { @@ -231,7 +229,11 @@ namespace eosio { if (e.view <= m->get_current_view()) return; - if (pbft_db.is_valid_new_view(e)) m->transit_to_new_view(e, this); + try { + m->transit_to_new_view(e, this); + } catch(const fc::exception& ex) { + wlog("bad new view, ${s} ", ("s",ex.to_string())); + } } void psm_prepared_state::manually_set_view(psm_machine *m, const uint32_t ¤t_view) { @@ -261,10 +263,8 @@ namespace eosio { } void psm_committed_state::send_prepare(psm_machine *m, pbft_database &pbft_db) { - ilog("new version is ${nv}, upgrading is ${u}", ("nv", pbft_db.ctrl.is_upgraded())("u", pbft_db.ctrl.under_upgrade())); auto prepares = pbft_db.send_and_add_pbft_prepare(m->get_prepares_cache(), m->get_current_view()); - ilog("new version is ${nv}, upgrading is ${u}", ("nv", pbft_db.ctrl.is_upgraded())("u", pbft_db.ctrl.under_upgrade())); if (!prepares.empty()) { m->set_prepares_cache(prepares); @@ -272,7 +272,6 @@ namespace eosio { //if prepare >= 2f+1, transit to prepared if (pbft_db.should_prepared()) m->transit_to_prepared_state(this); - ilog("new version is ${nv}, upgrading is ${u}", ("nv", pbft_db.ctrl.is_upgraded())("u", pbft_db.ctrl.under_upgrade())); } void psm_committed_state::on_commit(psm_machine *m, pbft_commit &e, pbft_database &pbft_db) { @@ -311,7 +310,11 @@ namespace eosio { if (e.view <= m->get_current_view()) return; - if (pbft_db.is_valid_new_view(e)) m->transit_to_new_view(e, this); + try { + m->transit_to_new_view(e, this); + } catch(const fc::exception& ex) { + wlog("bad new view, ${s} ", ("s",ex.to_string())); + } } void psm_committed_state::manually_set_view(psm_machine *m, const uint32_t ¤t_view) { @@ -365,9 +368,13 @@ namespace eosio { m->get_view_changed_certificate(), new_view); - if (nv_msg == pbft_new_view{} || !pbft_db.is_valid_new_view(nv_msg)) return; + if (nv_msg == pbft_new_view{}) return; - m->transit_to_new_view(nv_msg, this); + try { + m->transit_to_new_view(nv_msg, this); + } catch(const fc::exception& ex) { + wlog("bad new view, ${s} ", ("s",ex.to_string())); + } return; } } @@ -396,9 +403,13 @@ namespace eosio { m->get_view_changed_certificate(), new_view); - if (nv_msg == pbft_new_view{} || !pbft_db.is_valid_new_view(nv_msg)) return; + if (nv_msg == pbft_new_view{}) return; - m->transit_to_new_view(nv_msg, this); + try { + m->transit_to_new_view(nv_msg, this); + } catch(const fc::exception& ex) { + wlog("bad new view, ${s} ", ("s",ex.to_string())); + } return; } } @@ -408,7 +419,11 @@ namespace eosio { if (e.view <= m->get_current_view()) return; - if (pbft_db.is_valid_new_view(e)) m->transit_to_new_view(e, this); + try { + m->transit_to_new_view(e, this); + } catch(const fc::exception& ex) { + wlog("bad new view, ${s} ", ("s",ex.to_string())); + } } void psm_view_change_state::manually_set_view(psm_machine *m, const uint32_t ¤t_view) { @@ -466,6 +481,14 @@ namespace eosio { template void psm_machine::transit_to_new_view(const pbft_new_view &new_view, T const &s) { + auto valid_nv = false; + try { + valid_nv = pbft_db.is_valid_new_view(new_view); + } catch (const fc::exception& ex) { + throw; + } + EOS_ASSERT(valid_nv, pbft_exception, "new view is not valid, waiting for next round.."); + this->set_current_view(new_view.view); this->set_target_view(new_view.view + 1); @@ -481,7 +504,7 @@ namespace eosio { try { pbft_db.add_pbft_checkpoint(cp); } catch (...) { - wlog("insert checkpoint failed"); + wlog( "checkpoint insertion failure: ${cp}", ("cp", cp)); } } } @@ -491,7 +514,7 @@ namespace eosio { try { pbft_db.add_pbft_prepare(p); } catch (...) { - wlog("insert prepare failed"); + wlog("prepare insertion failure: ${p}", ("p", p)); } } if (pbft_db.should_prepared()) { diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index 14f97cf49ac..6d6f7b4e575 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -44,8 +44,6 @@ namespace eosio { } sort(prepare_watermarks.begin(), prepare_watermarks.end()); - ilog("pbft index size: ${s}", ("s", pbft_state_index.size())); - ilog("pbft prepare watermarks size: ${p}", ("p", prepare_watermarks.size())); } else { pbft_state_index = pbft_state_multi_index_type{}; } @@ -131,7 +129,7 @@ namespace eosio { auto curr_psp = make_shared(curr_ps); pbft_state_index.insert(curr_psp); } catch (...) { - EOS_ASSERT(false, pbft_exception, "prepare insert failure: ${p}", ("p", p)); + wlog( "prepare insert failure: ${p}", ("p", p)); } } else { auto prepares = (*curr_itr)->prepares; @@ -280,7 +278,7 @@ namespace eosio { auto curr_psp = make_shared(curr_ps); pbft_state_index.insert(curr_psp); } catch (...) { - EOS_ASSERT(false, pbft_exception, "commit insert failure: ${c}", ("c", c)); + wlog("commit insertion failure: ${c}", ("c", c)); } } else { auto commits = (*curr_itr)->commits; @@ -591,7 +589,9 @@ namespace eosio { } auto uuid = boost::uuids::to_string(uuid_generator()); + auto nv = pbft_new_view{uuid, current_view, highest_ppc, highest_sc, *vcc_ptr, sp_itr->first, chain_id()}; + nv.producer_signature = sp_itr->second(nv.digest()); emit(pbft_outgoing_new_view, nv); return nv; @@ -755,53 +755,44 @@ namespace eosio { bool pbft_database::is_valid_new_view(const pbft_new_view &nv) { //all signatures should be valid - if (nv.chain_id != chain_id()) { - wlog("wrong chain id in new view msg"); - return false; - } - if (!is_valid_prepared_certificate(nv.prepared)) { - wlog("prepared certificate invalid in new view msg"); - return false; - } + EOS_ASSERT(nv.chain_id == chain_id(), pbft_exception, "wrong chain."); - if (!is_valid_stable_checkpoint(nv.stable_checkpoint)) { - wlog("stable checkpoint invalid in new view msg"); - return false; - } + EOS_ASSERT(is_valid_prepared_certificate(nv.prepared), pbft_exception, + "bad prepared certificate: ${pc}", ("pc", nv.prepared)); - if (!nv.view_changed.is_signature_valid()) { - wlog("view changed sig invalid in new view msg"); - return false; - } + EOS_ASSERT(is_valid_stable_checkpoint(nv.stable_checkpoint), pbft_exception, + "bad stable checkpoint: ${scp}", ("scp", nv.stable_checkpoint)); - if (!nv.is_signature_valid()) { - wlog("new view sig invalid in new view msg"); - return false; - } + EOS_ASSERT(nv.view_changed.is_signature_valid(), pbft_exception, "bad view changed signature"); - if (nv.view_changed.view != nv.view) { - wlog("target view not match"); - return false; - } - auto schedule_threshold = lib_active_producers().producers.size() * 2 / 3 + 1; + EOS_ASSERT(nv.is_signature_valid(), pbft_exception, "bad new view signature"); - if (nv.view_changed.view_changes.size() < schedule_threshold) { - wlog("view change count not enough"); - return false; + EOS_ASSERT(nv.view_changed.view == nv.view, pbft_exception, "target view not match"); + + vector lib_producers; + for (const auto& pk: lib_active_producers().producers) { + lib_producers.emplace_back(pk.block_signing_key); } + auto schedule_threshold = lib_producers.size() * 2 / 3 + 1; + + vector view_change_producers; + for (auto vc: nv.view_changed.view_changes) { - if (!is_valid_view_change(vc)) { - wlog("invalid view change msg ${m}", ("m", vc)); - return false; + if (is_valid_view_change(vc)) { + add_pbft_view_change(vc); + view_change_producers.emplace_back(vc.public_key); } - add_pbft_view_change(vc); } - if (!should_new_view(nv.view)) { - wlog("should not new view"); - return false; - } + vector intersection; + std::set_intersection(lib_producers.begin(),lib_producers.end(), + view_change_producers.begin(),view_change_producers.end(), + back_inserter(intersection)); + + EOS_ASSERT(intersection.size() >= schedule_threshold, pbft_exception, "view changes count not enough"); + + EOS_ASSERT(should_new_view(nv.view), pbft_exception, "should not enter new view: ${nv}", ("nv", nv.view)); auto highest_ppc = pbft_prepared_certificate{}; auto highest_scp = pbft_stable_checkpoint{}; @@ -818,15 +809,13 @@ namespace eosio { } } - if (highest_ppc != nv.prepared) { - wlog("prepared num not match"); - return false; - } + EOS_ASSERT(highest_ppc == nv.prepared, pbft_exception, + "prepared certificate not match, should be ${hpcc} but ${pc} given", + ("hpcc",highest_ppc)("pc", nv.prepared)); - if (highest_scp != nv.stable_checkpoint) { - wlog("stable checkpoint not match"); - return false; - } + EOS_ASSERT(highest_scp == nv.stable_checkpoint, pbft_exception, + "stable checkpoint not match, should be ${hscp} but ${scp} given", + ("hpcc",highest_scp)("pc", nv.stable_checkpoint)); return true; } @@ -1189,7 +1178,7 @@ namespace eosio { && c.block_num == scp.block_num; if (!valid) return false; } - //TODO: check if (2/3 + 1) met + auto bs = ctrl.fetch_block_state_by_number(scp.block_num); if (bs) { auto as = bs->active_schedule; @@ -1305,15 +1294,13 @@ namespace eosio { void pbft_database::set(pbft_state_ptr s) { auto result = pbft_state_index.insert(s); - EOS_ASSERT(result.second, pbft_exception, - "unable to insert pbft state, duplicate state detected"); + EOS_ASSERT(result.second, pbft_exception, "unable to insert pbft state, duplicate state detected"); } void pbft_database::set(pbft_checkpoint_state_ptr s) { auto result = checkpoint_index.insert(s); - EOS_ASSERT(result.second, pbft_exception, - "unable to insert pbft checkpoint index, duplicate state detected"); + EOS_ASSERT(result.second, pbft_exception, "unable to insert pbft checkpoint index, duplicate state detected"); } void pbft_database::prune(const pbft_state_ptr &h) { diff --git a/plugins/pbft_plugin/pbft_plugin.cpp b/plugins/pbft_plugin/pbft_plugin.cpp index 5091eb1089a..3b1762ba744 100644 --- a/plugins/pbft_plugin/pbft_plugin.cpp +++ b/plugins/pbft_plugin/pbft_plugin.cpp @@ -147,7 +147,6 @@ namespace eosio { "* *\n" "************************************\n" ); upgraded = true; - ilog("new version is ${nv}, upgrading is ${u}",("nv", chain.is_upgraded())("u", chain.under_upgrade())); } return (new_version && (!is_syncing() && !is_replaying())); diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 8525d4d5d72..61f6ac15f12 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -357,9 +357,6 @@ class producer_plugin_impl : public std::enable_shared_from_thisid()))("t",block->timestamp) ("count",block->transactions.size())("lib",chain.last_irreversible_block_num()) ("confs", block->confirmed)("latency", (fc::time_point::now() - block->timestamp).count()/1000 ) ); - if (chain.under_upgrade()) { - wlog("upgrading..."); - } } } } @@ -983,6 +980,7 @@ producer_plugin::snapshot_information producer_plugin::create_snapshot() const { } void producer_plugin::set_pbft_current_view(const uint32_t view) { + //this is used to recover from a disaster, do not set this unless you have to do so. pbft_controller& pbft_ctrl = app().get_plugin().pbft_ctrl(); pbft_ctrl.state_machine.manually_set_current_view(view); } @@ -1107,8 +1105,6 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { } auto new_version = chain.is_upgraded(); - ilog("producer plugin before abort_block: new version is ${nv}, upgrading is ${u}", ("nv", chain.is_upgraded())("u", chain.under_upgrade())); - if (_pending_block_mode == pending_block_mode::producing && !new_version) { // determine if our watermark excludes us from producing at this point @@ -1153,7 +1149,6 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { } chain.abort_block(); - ilog("producer plugin after abort_block: new version is ${nv}, upgrading is ${u}", ("nv", chain.is_upgraded())("u", chain.under_upgrade())); chain.start_block(block_time, blocks_to_confirm, signature_provider); } FC_LOG_AND_DROP(); diff --git a/plugins/txn_test_gen_plugin/CMakeLists.txt b/plugins/txn_test_gen_plugin/CMakeLists.txt index 286066d6149..e765f3478e6 100644 --- a/plugins/txn_test_gen_plugin/CMakeLists.txt +++ b/plugins/txn_test_gen_plugin/CMakeLists.txt @@ -5,6 +5,6 @@ add_library( txn_test_gen_plugin add_dependencies(txn_test_gen_plugin eosio.token) -target_link_libraries( txn_test_gen_plugin appbase fc http_plugin chain_plugin net_plugin) +target_link_libraries( txn_test_gen_plugin appbase fc http_plugin chain_plugin ) target_include_directories( txn_test_gen_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) target_include_directories( txn_test_gen_plugin PUBLIC ${CMAKE_BINARY_DIR}/contracts ) diff --git a/plugins/txn_test_gen_plugin/README.md b/plugins/txn_test_gen_plugin/README.md index 3547a342eda..8d74e6a0412 100644 --- a/plugins/txn_test_gen_plugin/README.md +++ b/plugins/txn_test_gen_plugin/README.md @@ -68,7 +68,7 @@ $ ./cleos set contract eosio ~/eos/build.release/contracts/eosio.bios/ ### Initialize the accounts txn_test_gen_plugin uses ```bash -$ curl --data-binary '["eosio", "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3", "EOS"]' http://127.0.0.1:8888/v1/txn_test_gen/create_test_accounts +$ curl --data-binary '["eosio", "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"]' http://127.0.0.1:8888/v1/txn_test_gen/create_test_accounts ``` ### Start transaction generation, this will submit 20 transactions evey 20ms (total of 1000TPS) diff --git a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp index ac5d8bb5b53..3a2c4fed5fb 100644 --- a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp +++ b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp @@ -4,7 +4,6 @@ */ #include #include -#include #include #include @@ -25,8 +24,6 @@ #include #include -#include -#include namespace eosio { namespace detail { struct txn_test_gen_empty {}; @@ -85,9 +82,9 @@ using namespace eosio::chain; }\ } -#define INVOKE_ASYNC_R_R(api_handle, call_name, in_param0, in_param1, in_param2) \ +#define INVOKE_ASYNC_R_R(api_handle, call_name, in_param0, in_param1) \ const auto& vs = fc::json::json::from_string(body).as(); \ - api_handle->call_name(vs.at(0).as(), vs.at(1).as(), vs.at(2).as(), result_handler); + api_handle->call_name(vs.at(0).as(), vs.at(1).as(), result_handler); struct txn_test_gen_plugin_impl { @@ -96,10 +93,6 @@ struct txn_test_gen_plugin_impl { int _remain = 0; - std::string cached_salt; - uint64_t cached_period; - uint64_t cached_batch_size; - void push_next_transaction(const std::shared_ptr>& trxs, size_t index, const std::function& next ) { chain_plugin& cp = app().get_plugin(); @@ -133,16 +126,14 @@ struct txn_test_gen_plugin_impl { push_next_transaction(trxs_copy, 0, next); } - void create_test_accounts(const std::string& init_name, const std::string& init_priv_key, - const std::string& core_symbol, - const std::function& next) { + void create_test_accounts(const std::string& init_name, const std::string& init_priv_key, const std::function& next) { std::vector trxs; trxs.reserve(2); try { - name newaccountA("aaaaaaaaaaaa"); - name newaccountB("bbbbbbbbbbbb"); - name newaccountC("cccccccccccc"); + name newaccountA("txn.test.a"); + name newaccountB("txn.test.b"); + name newaccountC("txn.test.t"); name creator(init_name); abi_def currency_abi_def = fc::json::from_string(eosio_token_abi).as(); @@ -161,10 +152,6 @@ struct txn_test_gen_plugin_impl { fc::crypto::public_key txn_text_receiver_C_pub_key = txn_test_receiver_C_priv_key.get_public_key(); fc::crypto::private_key creator_priv_key = fc::crypto::private_key(init_priv_key); - eosio::chain::asset net{1000000, symbol(4,core_symbol.c_str())}; - eosio::chain::asset cpu{1000000, symbol(4,core_symbol.c_str())}; - eosio::chain::asset ram{1000000, symbol(4,core_symbol.c_str())}; - //create some test accounts { signed_transaction trx; @@ -175,14 +162,6 @@ struct txn_test_gen_plugin_impl { auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_A_pub_key, 1}}, {}}; trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountA, owner_auth, active_auth}); - - //delegate cpu net and buyram - auto act_delegatebw = create_action_delegatebw(creator, newaccountA,net,cpu,abi_serializer_max_time); - auto act_buyram = create_action_buyram(creator, newaccountA, ram, abi_serializer_max_time); - - trx.actions.emplace_back(act_delegatebw); - trx.actions.emplace_back(act_buyram); - } //create "B" account { @@ -190,27 +169,13 @@ struct txn_test_gen_plugin_impl { auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_B_pub_key, 1}}, {}}; trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountB, owner_auth, active_auth}); - - //delegate cpu net and buyram - auto act_delegatebw = create_action_delegatebw(creator, newaccountB,net,cpu,abi_serializer_max_time); - auto act_buyram = create_action_buyram(creator, newaccountB, ram, abi_serializer_max_time); - - trx.actions.emplace_back(act_delegatebw); - trx.actions.emplace_back(act_buyram); } - //create "cccccccccccc" account + //create "txn.test.t" account { auto owner_auth = eosio::chain::authority{1, {{txn_text_receiver_C_pub_key, 1}}, {}}; auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_C_pub_key, 1}}, {}}; trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountC, owner_auth, active_auth}); - - //delegate cpu net and buyram - auto act_delegatebw = create_action_delegatebw(creator, newaccountC,net,cpu,abi_serializer_max_time); - auto act_buyram = create_action_buyram(creator, newaccountC, ram, abi_serializer_max_time); - - trx.actions.emplace_back(act_delegatebw); - trx.actions.emplace_back(act_buyram); } trx.expiration = cc.head_block_time() + fc::seconds(30); @@ -219,7 +184,7 @@ struct txn_test_gen_plugin_impl { trxs.emplace_back(std::move(trx)); } - //set cccccccccccc contract to eosio.token & initialize it + //set txn.test.t contract to eosio.token & initialize it { signed_transaction trx; @@ -240,34 +205,34 @@ struct txn_test_gen_plugin_impl { { action act; - act.account = N(cccccccccccc); + act.account = N(txn.test.t); act.name = N(create); act.authorization = vector{{newaccountC,config::active_name}}; - act.data = eosio_token_serializer.variant_to_binary("create", fc::json::from_string("{\"issuer\":\"cccccccccccc\",\"maximum_supply\":\"1000000000.0000 CUR\"}}"), abi_serializer_max_time); + act.data = eosio_token_serializer.variant_to_binary("create", fc::json::from_string("{\"issuer\":\"txn.test.t\",\"maximum_supply\":\"1000000000.0000 CUR\"}}"), abi_serializer_max_time); trx.actions.push_back(act); } { action act; - act.account = N(cccccccccccc); + act.account = N(txn.test.t); act.name = N(issue); act.authorization = vector{{newaccountC,config::active_name}}; - act.data = eosio_token_serializer.variant_to_binary("issue", fc::json::from_string("{\"to\":\"cccccccccccc\",\"quantity\":\"600.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); + act.data = eosio_token_serializer.variant_to_binary("issue", fc::json::from_string("{\"to\":\"txn.test.t\",\"quantity\":\"600.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); trx.actions.push_back(act); } { action act; - act.account = N(cccccccccccc); + act.account = N(txn.test.t); act.name = N(transfer); act.authorization = vector{{newaccountC,config::active_name}}; - act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"cccccccccccc\",\"to\":\"aaaaaaaaaaaa\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); + act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"txn.test.t\",\"to\":\"txn.test.a\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); trx.actions.push_back(act); } { action act; - act.account = N(cccccccccccc); + act.account = N(txn.test.t); act.name = N(transfer); act.authorization = vector{{newaccountC,config::active_name}}; - act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"cccccccccccc\",\"to\":\"bbbbbbbbbbbb\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); + act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"txn.test.t\",\"to\":\"txn.test.b\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}"), abi_serializer_max_time); trx.actions.push_back(act); } @@ -285,36 +250,6 @@ struct txn_test_gen_plugin_impl { push_transactions(std::move(trxs), next); } - eosio::chain::action create_action_delegatebw(const name &from, const name &to, const asset &net, const asset &cpu, const fc::microseconds &abi_serializer_max_time){ - fc::variant variant_delegate = fc::mutable_variant_object() - ("from", from.to_string()) - ("receiver", to.to_string()) - ("stake_net_quantity", net.to_string()) - ("stake_cpu_quantity", cpu.to_string()) - ("transfer", true); - abi_serializer eosio_system_serializer{fc::json::from_string(eosio_system_abi).as(), abi_serializer_max_time}; - - auto payload_delegate = eosio_system_serializer.variant_to_binary( "delegatebw", variant_delegate, abi_serializer_max_time); - eosio::chain::action act_delegate{vector{{from,"active"}}, - config::system_account_name, N(delegatebw), payload_delegate}; - - return act_delegate; - } - - eosio::chain::action create_action_buyram(const name &from, const name &to, const asset &quant, const fc::microseconds &abi_serializer_max_time){ - fc::variant variant_buyram = fc::mutable_variant_object() - ("payer", from.to_string()) - ("receiver", to.to_string()) - ("quant", quant.to_string()); - abi_serializer eosio_system_serializer{fc::json::from_string(eosio_system_abi).as(), abi_serializer_max_time}; - - auto payload_buyram = eosio_system_serializer.variant_to_binary( "buyram", variant_buyram, abi_serializer_max_time); - eosio::chain::action act_buyram{vector{{from,"active"}}, - config::system_account_name, N(buyram), payload_buyram}; - - return act_buyram; - } - void start_generation(const std::string& salt, const uint64_t& period, const uint64_t& batch_size) { if(running) throw fc::exception(fc::invalid_operation_exception_code); @@ -326,27 +261,24 @@ struct txn_test_gen_plugin_impl { throw fc::exception(fc::invalid_operation_exception_code); running = true; - cached_salt = salt; - cached_period = period; - cached_batch_size = batch_size; controller& cc = app().get_plugin().chain(); auto abi_serializer_max_time = app().get_plugin().get_abi_serializer_max_time(); abi_serializer eosio_token_serializer{fc::json::from_string(eosio_token_abi).as(), abi_serializer_max_time}; //create the actions here - act_a_to_b.account = N(cccccccccccc); + act_a_to_b.account = N(txn.test.t); act_a_to_b.name = N(transfer); - act_a_to_b.authorization = vector{{name("aaaaaaaaaaaa"),config::active_name}}; + act_a_to_b.authorization = vector{{name("txn.test.a"),config::active_name}}; act_a_to_b.data = eosio_token_serializer.variant_to_binary("transfer", - fc::json::from_string(fc::format_string("{\"from\":\"aaaaaaaaaaaa\",\"to\":\"bbbbbbbbbbbb\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", + fc::json::from_string(fc::format_string("{\"from\":\"txn.test.a\",\"to\":\"txn.test.b\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", fc::mutable_variant_object()("l", salt))), abi_serializer_max_time); - act_b_to_a.account = N(cccccccccccc); + act_b_to_a.account = N(txn.test.t); act_b_to_a.name = N(transfer); - act_b_to_a.authorization = vector{{name("bbbbbbbbbbbb"),config::active_name}}; + act_b_to_a.authorization = vector{{name("txn.test.b"),config::active_name}}; act_b_to_a.data = eosio_token_serializer.variant_to_binary("transfer", - fc::json::from_string(fc::format_string("{\"from\":\"bbbbbbbbbbbb\",\"to\":\"aaaaaaaaaaaa\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", + fc::json::from_string(fc::format_string("{\"from\":\"txn.test.b\",\"to\":\"txn.test.a\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", fc::mutable_variant_object()("l", salt))), abi_serializer_max_time); @@ -354,8 +286,8 @@ struct txn_test_gen_plugin_impl { batch = batch_size/2; ilog("Started transaction test plugin; performing ${p} transactions every ${m}ms", ("p", batch_size)("m", period)); - ilog("wait 3 seconds to spin up"); - arm_timer(boost::asio::high_resolution_timer::clock_type::now() + std::chrono::milliseconds(3000) ); + + arm_timer(boost::asio::high_resolution_timer::clock_type::now()); } void arm_timer(boost::asio::high_resolution_timer::time_point s) { @@ -368,14 +300,6 @@ struct txn_test_gen_plugin_impl { if (e) { elog("pushing transaction failed: ${e}", ("e", e->to_detail_string())); stop_generation(); - auto peers_conn = app().get_plugin().connections(); - for(const auto c : peers_conn){ - app().get_plugin().disconnect(c.peer); - } - for(const auto c : peers_conn){ - app().get_plugin().connect(c.peer); - } - start_generation(cached_salt,cached_period,cached_batch_size); } else { arm_timer(timer.expires_at()); } @@ -481,7 +405,7 @@ void txn_test_gen_plugin::plugin_initialize(const variables_map& options) { void txn_test_gen_plugin::plugin_startup() { app().get_plugin().add_api({ - CALL_ASYNC(txn_test_gen, my, create_test_accounts, INVOKE_ASYNC_R_R(my, create_test_accounts, std::string, std::string, std::string), 200), + CALL_ASYNC(txn_test_gen, my, create_test_accounts, INVOKE_ASYNC_R_R(my, create_test_accounts, std::string, std::string), 200), CALL(txn_test_gen, my, stop_generation, INVOKE_V_V(my, stop_generation), 200), CALL(txn_test_gen, my, start_generation, INVOKE_V_R_R_R(my, start_generation, std::string, uint64_t, uint64_t), 200) });