From 1e95f1548d69ad5da10263704b452c0c7d18ccf4 Mon Sep 17 00:00:00 2001 From: oldcold Date: Tue, 19 Mar 2019 16:22:01 +0800 Subject: [PATCH 001/145] 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 002/145] 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 003/145] 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 004/145] 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 005/145] 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 006/145] 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 007/145] 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 008/145] 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 009/145] 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 010/145] 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 011/145] 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 012/145] 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 013/145] 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 014/145] 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 015/145] 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 016/145] 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 017/145] 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 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 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 018/145] 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 019/145] 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 020/145] 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 021/145] 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 022/145] 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 023/145] 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 024/145] 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 025/145] 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 026/145] 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 027/145] 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 028/145] 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 029/145] 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 030/145] 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 031/145] 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 032/145] 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 033/145] 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 034/145] 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 94c454134ff4693fa7ec8820f31015e5eae8fb68 Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Thu, 18 Apr 2019 17:36:38 -0400 Subject: [PATCH 035/145] Removed centralized pipelines --- .buildkite/coverage.yml | 29 ---- .buildkite/debug.yml | 230 ------------------------------ .buildkite/docker.yml | 74 ---------- .buildkite/long_running_tests.yml | 212 --------------------------- .buildkite/sanitizers.yml | 131 ----------------- 5 files changed, 676 deletions(-) delete mode 100644 .buildkite/coverage.yml delete mode 100644 .buildkite/debug.yml delete mode 100644 .buildkite/docker.yml delete mode 100644 .buildkite/long_running_tests.yml delete mode 100644 .buildkite/sanitizers.yml diff --git a/.buildkite/coverage.yml b/.buildkite/coverage.yml deleted file mode 100644 index c5a50bc64f4..00000000000 --- a/.buildkite/coverage.yml +++ /dev/null @@ -1,29 +0,0 @@ -steps: - - command: | - echo "--- :hammer: Building" && \ - /usr/bin/cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++-4.0 -DCMAKE_C_COMPILER=clang-4.0 -DBOOST_ROOT="${BOOST_ROOT}" -DWASM_ROOT="${WASM_ROOT}" -DOPENSSL_ROOT_DIR="${OPENSSL_ROOT_DIR}" -DBUILD_MONGO_DB_PLUGIN=true -DENABLE_COVERAGE_TESTING=true -DBUILD_DOXYGEN=false && \ - /usr/bin/ninja - echo "--- :spiral_note_pad: Generating Code Coverage Report" && \ - /usr/bin/ninja EOSIO_ut_coverage && \ - echo "--- :arrow_up: Publishing Code Coverage Report" && \ - buildkite-agent artifact upload "EOSIO_ut_coverage/**/*" s3://eos-coverage/$BUILDKITE_JOB_ID && \ - cp /config/.coveralls.yml . && \ - /usr/local/bin/coveralls-lcov EOSIO_ut_coverage_filtered.info && \ - echo "+++ View Report" && \ - printf "\033]1339;url=https://eos-coverage.s3-us-west-2.amazonaws.com/$BUILDKITE_JOB_ID/EOSIO_ut_coverage/index.html;content=View Full Coverage Report\a\n" - label: ":spiral_note_pad: Generate Report" - agents: - queue: "automation-large-builder-fleet" - plugins: - docker#v1.4.0: - image: "eosio/ci:ubuntu18" - workdir: /data/job - mounts: - - /etc/buildkite-agent/config:/config - environment: - - BOOST_ROOT=/root/opt/boost - - OPENSSL_ROOT_DIR=/usr/include/openssl - - WASM_ROOT=/root/opt/wasm - - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/opt/wasm/bin - - CI=true - timeout: 60 diff --git a/.buildkite/debug.yml b/.buildkite/debug.yml deleted file mode 100644 index 28576d56195..00000000000 --- a/.buildkite/debug.yml +++ /dev/null @@ -1,230 +0,0 @@ -steps: - - command: | - echo "--- Creating symbolic link to job directory :file_folder:" && \ - sleep 5 && ln -s "$(pwd)" /data/job && cd /data/job && \ - echo "+++ Building :hammer:" && \ - echo 1 | ./eosio_build.sh -o Debug && \ - echo "--- Compressing build directory :compression:" && \ - tar -pczf build.tar.gz build/ - label: ":darwin: Build" - agents: - - "role=macos-builder" - artifact_paths: "build.tar.gz" - timeout: 60 - - - command: | - echo "+++ :hammer: Building" && \ - echo 1 | ./eosio_build.sh -o Debug && \ - echo "--- :compression: Compressing build directory" && \ - tar -pczf build.tar.gz build/ - label: ":ubuntu: Build" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: "build.tar.gz" - plugins: - docker#v2.0.0: - image: "eosio/ci:ubuntu" - workdir: /data/job - timeout: 60 - - - command: | - echo "+++ :hammer: Building" && \ - echo 1 | ./eosio_build.sh -o Debug && \ - echo "--- :compression: Compressing build directory" && \ - tar -pczf build.tar.gz build/ - label: ":ubuntu: 18.04 Build" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: "build.tar.gz" - plugins: - docker#v2.0.0: - image: "eosio/ci:ubuntu18" - workdir: /data/job - timeout: 60 - - - command: | - echo "+++ :hammer: Building" && \ - echo 1 | ./eosio_build.sh -o Debug && \ - echo "--- :compression: Compressing build directory" && \ - tar -pczf build.tar.gz build/ - label: ":fedora: Build" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: "build.tar.gz" - plugins: - docker#v2.0.0: - image: "eosio/ci:fedora" - workdir: /data/job - timeout: 60 - - - command: | - echo "+++ :hammer: Building" && \ - echo 1 | ./eosio_build.sh -o Debug && \ - echo "--- :compression: Compressing build directory" && \ - tar -pczf build.tar.gz build/ - label: ":centos: Build" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: "build.tar.gz" - plugins: - docker#v2.0.0: - image: "eosio/ci:centos" - workdir: /data/job - timeout: 60 - - - command: | - echo "+++ :hammer: Building" && \ - echo 1 | ./eosio_build.sh -o Debug && \ - echo "--- :compression: Compressing build directory" && \ - tar -pczf build.tar.gz build/ - label: ":aws: Build" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: "build.tar.gz" - plugins: - docker#v2.0.0: - image: "eosio/ci:amazonlinux" - workdir: /data/job - timeout: 60 - - - wait - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":darwin: Build" && \ - tar -zxf build.tar.gz && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - ln -s "$(pwd)" /data/job && cd /data/job/build && ctest -LE long_running_tests --output-on-failure - retry: - automatic: - limit: 1 - label: ":darwin: Tests" - agents: - - "role=macos-tester" - artifact_paths: - - "mongod.log" - - "build/genesis.json" - - "build/config.ini" - timeout: 60 - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":ubuntu: Build" && \ - tar -zxf build.tar.gz && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - cd /data/job/build && ctest -LE long_running_tests --output-on-failure - retry: - automatic: - limit: 1 - label: ":ubuntu: Tests" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "mongod.log" - - "build/genesis.json" - - "build/config.ini" - plugins: - docker#v2.0.0: - image: "eosio/ci:ubuntu" - workdir: /data/job - timeout: 60 - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":ubuntu: 18.04 Build" && \ - tar -zxf build.tar.gz && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - cd /data/job/build && ctest -LE long_running_tests --output-on-failure - retry: - automatic: - limit: 1 - label: ":ubuntu: 18.04 Tests" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "mongod.log" - - "build/genesis.json" - - "build/config.ini" - plugins: - docker#v2.0.0: - image: "eosio/ci:ubuntu18" - workdir: /data/job - timeout: 60 - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":fedora: Build" && \ - tar -zxf build.tar.gz && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - cd /data/job/build && ctest -LE long_running_tests --output-on-failure - retry: - automatic: - limit: 1 - label: ":fedora: Tests" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "mongod.log" - - "build/genesis.json" - - "build/config.ini" - plugins: - docker#v2.0.0: - image: "eosio/ci:fedora" - workdir: /data/job - timeout: 60 - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":centos: Build" && \ - tar -zxf build.tar.gz && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - cd /data/job/build && ctest -LE long_running_tests --output-on-failure - retry: - automatic: - limit: 1 - label: ":centos: Tests" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "mongod.log" - - "build/genesis.json" - - "build/config.ini" - plugins: - docker#v2.0.0: - image: "eosio/ci:centos" - workdir: /data/job - timeout: 60 - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":aws: Build" && \ - tar -zxf build.tar.gz && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - cd /data/job/build && ctest -LE long_running_tests --output-on-failure - retry: - automatic: - limit: 1 - label: ":aws: Tests" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "mongod.log" - - "build/genesis.json" - - "build/config.ini" - plugins: - docker#v2.0.0: - image: "eosio/ci:amazonlinux" - workdir: /data/job - timeout: 60 diff --git a/.buildkite/docker.yml b/.buildkite/docker.yml deleted file mode 100644 index f8f6d8e0a12..00000000000 --- a/.buildkite/docker.yml +++ /dev/null @@ -1,74 +0,0 @@ -steps: - - command: | - echo "AUTHENTICATING GOOGLE SERVICE ACCOUNT" && \ - gcloud --quiet auth activate-service-account b1-automation-svc@b1-automation-dev.iam.gserviceaccount.com --key-file=/etc/gcp-service-account.json && \ - docker-credential-gcr configure-docker && \ - echo "BUILDING BUILD IMAGE" && \ - cd Docker/builder && \ - docker build -t eosio/builder:latest -t eosio/builder:$BUILDKITE_COMMIT . --build-arg branch=$BUILDKITE_COMMIT && \ - docker tag eosio/builder:$BUILDKITE_COMMIT gcr.io/b1-automation-dev/eosio/builder:$BUILDKITE_COMMIT && \ - docker tag eosio/builder:latest gcr.io/b1-automation-dev/eosio/builder:latest && \ - echo "PUSHING DOCKER IMAGES" && \ - docker push gcr.io/b1-automation-dev/eosio/builder:$BUILDKITE_COMMIT && \ - docker push gcr.io/b1-automation-dev/eosio/builder:latest && \ - echo "TRASHING OLD IMAGES" && \ - docker rmi eosio/builder:$BUILDKITE_COMMIT && \ - docker rmi eosio/builder:latest && \ - docker rmi gcr.io/b1-automation-dev/eosio/builder:$BUILDKITE_COMMIT && \ - docker rmi gcr.io/b1-automation-dev/eosio/builder:latest - label: "Docker build builder" - agents: - queue: "automation-docker-builder-fleet" - timeout: 300 - - - wait - - - command: | - echo "AUTHENTICATING GOOGLE SERVICE ACCOUNT" && \ - gcloud --quiet auth activate-service-account b1-automation-svc@b1-automation-dev.iam.gserviceaccount.com --key-file=/etc/gcp-service-account.json && \ - docker-credential-gcr configure-docker && \ - echo "BUILDING EOS IMAGE" && \ - docker pull gcr.io/b1-automation-dev/eosio/builder:$BUILDKITE_COMMIT && \ - cd Docker && \ - docker build -t eosio/eos:latest -t eosio/eos:$BUILDKITE_COMMIT . --build-arg branch=$BUILDKITE_BRANCH && \ - docker tag eosio/eos:$BUILDKITE_COMMIT gcr.io/b1-automation-dev/eosio/eos:$BUILDKITE_COMMIT && \ - docker tag eosio/eos:latest gcr.io/b1-automation-dev/eosio/eos:latest && \ - echo "PUSHING DOCKER IMAGES" && \ - docker push gcr.io/b1-automation-dev/eosio/eos:$BUILDKITE_COMMIT && \ - docker push gcr.io/b1-automation-dev/eosio/eos:latest && \ - echo "TRASHING OLD IMAGES" && \ - docker rmi eosio/eos:$BUILDKITE_COMMIT && \ - docker rmi eosio/eos:latest && \ - docker rmi gcr.io/b1-automation-dev/eosio/eos:$BUILDKITE_COMMIT && \ - docker rmi gcr.io/b1-automation-dev/eosio/eos:latest && \ - docker rmi gcr.io/b1-automation-dev/eosio/builder:$BUILDKITE_COMMIT - label: "Docker build eos" - agents: - queue: "automation-docker-builder-fleet" - timeout: 300 - - - command: | - echo "AUTHENTICATING GOOGLE SERVICE ACCOUNT" && \ - gcloud --quiet auth activate-service-account b1-automation-svc@b1-automation-dev.iam.gserviceaccount.com --key-file=/etc/gcp-service-account.json && \ - docker-credential-gcr configure-docker && \ - echo "BUILDING EOS DEV IMAGE" && \ - docker pull gcr.io/b1-automation-dev/eosio/builder:$BUILDKITE_COMMIT && \ - cd Docker/dev && \ - docker build -t eosio/eos-dev:latest -t eosio/eos-dev:$BUILDKITE_COMMIT . --build-arg branch=$BUILDKITE_BRANCH && \ - docker tag eosio/eos-dev:$BUILDKITE_COMMIT gcr.io/b1-automation-dev/eosio/eos-dev:$BUILDKITE_COMMIT && \ - docker tag eosio/eos-dev:latest gcr.io/b1-automation-dev/eosio/eos-dev:latest && \ - echo "PUSHING DOCKER IMAGES" && \ - docker push gcr.io/b1-automation-dev/eosio/eos-dev:$BUILDKITE_COMMIT && \ - docker push gcr.io/b1-automation-dev/eosio/eos-dev:latest && \ - echo "TRASHING OLD IMAGES" && \ - docker rmi eosio/eos-dev:$BUILDKITE_COMMIT && \ - docker rmi eosio/eos-dev:latest && \ - docker rmi gcr.io/b1-automation-dev/eosio/eos-dev:$BUILDKITE_COMMIT && \ - docker rmi gcr.io/b1-automation-dev/eosio/eos-dev:latest && \ - docker rmi gcr.io/b1-automation-dev/eosio/builder:$BUILDKITE_COMMIT - label: "Docker build eos-dev" - agents: - queue: "automation-docker-builder-fleet" - timeout: 300 - - - wait diff --git a/.buildkite/long_running_tests.yml b/.buildkite/long_running_tests.yml deleted file mode 100644 index e22016c4de4..00000000000 --- a/.buildkite/long_running_tests.yml +++ /dev/null @@ -1,212 +0,0 @@ -steps: - - command: | - echo "--- Creating symbolic link to job directory :file_folder:" && \ - sleep 5 && ln -s "$(pwd)" /data/job && cd /data/job && \ - echo "+++ Building :hammer:" && \ - echo 1 | ./eosio_build.sh && \ - echo "--- Compressing build directory :compression:" && \ - tar -pczf build.tar.gz build/ - label: ":darwin: Build" - agents: - - "role=macos-builder" - artifact_paths: "build.tar.gz" - timeout: 60 - - - command: | - echo "+++ :hammer: Building" && \ - echo 1 | ./eosio_build.sh && \ - echo "--- :compression: Compressing build directory" && \ - tar -pczf build.tar.gz build/ - label: ":ubuntu: Build" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: "build.tar.gz" - plugins: - docker#v1.4.0: - image: "eosio/ci:ubuntu" - workdir: /data/job - timeout: 60 - - - command: | - echo "+++ :hammer: Building" && \ - echo 1 | ./eosio_build.sh && \ - echo "--- :compression: Compressing build directory" && \ - tar -pczf build.tar.gz build/ - label: ":ubuntu: 18.04 Build" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: "build.tar.gz" - plugins: - docker#v1.4.0: - image: "eosio/ci:ubuntu18" - workdir: /data/job - timeout: 60 - - - command: | - echo "+++ :hammer: Building" && \ - echo 1 | ./eosio_build.sh && \ - echo "--- :compression: Compressing build directory" && \ - tar -pczf build.tar.gz build/ - label: ":fedora: Build" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: "build.tar.gz" - plugins: - docker#v1.4.0: - image: "eosio/ci:fedora" - workdir: /data/job - timeout: 60 - - - command: | - echo "+++ :hammer: Building" && \ - echo 1 | ./eosio_build.sh && \ - echo "--- :compression: Compressing build directory" && \ - tar -pczf build.tar.gz build/ - label: ":centos: Build" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: "build.tar.gz" - plugins: - docker#v1.4.0: - image: "eosio/ci:centos" - workdir: /data/job - timeout: 60 - - - command: | - echo "+++ :hammer: Building" && \ - echo 1 | ./eosio_build.sh && \ - echo "--- :compression: Compressing build directory" && \ - tar -pczf build.tar.gz build/ - label: ":aws: Build" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: "build.tar.gz" - plugins: - docker#v1.4.0: - image: "eosio/ci:amazonlinux" - workdir: /data/job - timeout: 60 - - - wait - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":darwin: Build" && \ - tar -zxf build.tar.gz && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - ln -s "$(pwd)" /data/job && cd /data/job/build && ctest -L long_running_tests --output-on-failure - label: ":darwin: Tests" - agents: - - "role=macos-tester" - artifact_paths: - - "mongod.log" - - "build/genesis.json" - - "build/config.ini" - timeout: 100 - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":ubuntu: Build" && \ - tar -zxf build.tar.gz && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - cd /data/job/build && ctest -L long_running_tests --output-on-failure - label: ":ubuntu: Tests" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "mongod.log" - - "build/genesis.json" - - "build/config.ini" - plugins: - docker#v1.4.0: - image: "eosio/ci:ubuntu" - workdir: /data/job - timeout: 100 - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":ubuntu: 18.04 Build" && \ - tar -zxf build.tar.gz && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - cd /data/job/build && ctest -L long_running_tests --output-on-failure - label: ":ubuntu: 18.04 Tests" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "mongod.log" - - "build/genesis.json" - - "build/config.ini" - plugins: - docker#v1.4.0: - image: "eosio/ci:ubuntu18" - workdir: /data/job - timeout: 100 - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":fedora: Build" && \ - tar -zxf build.tar.gz && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - cd /data/job/build && ctest -L long_running_tests --output-on-failure - label: ":fedora: Tests" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "mongod.log" - - "build/genesis.json" - - "build/config.ini" - plugins: - docker#v1.4.0: - image: "eosio/ci:fedora" - workdir: /data/job - timeout: 100 - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":centos: Build" && \ - tar -zxf build.tar.gz && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - cd /data/job/build && ctest -L long_running_tests --output-on-failure - label: ":centos: Tests" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "mongod.log" - - "build/genesis.json" - - "build/config.ini" - plugins: - docker#v1.4.0: - image: "eosio/ci:centos" - workdir: /data/job - timeout: 100 - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":aws: Build" && \ - tar -zxf build.tar.gz && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - cd /data/job/build && ctest -L long_running_tests --output-on-failure - label: ":aws: Tests" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "mongod.log" - - "build/genesis.json" - - "build/config.ini" - plugins: - docker#v1.4.0: - image: "eosio/ci:amazonlinux" - workdir: /data/job - timeout: 100 diff --git a/.buildkite/sanitizers.yml b/.buildkite/sanitizers.yml deleted file mode 100644 index b8588135610..00000000000 --- a/.buildkite/sanitizers.yml +++ /dev/null @@ -1,131 +0,0 @@ -steps: - - command: | - echo "--- :hammer: Building with Undefined Sanitizer" && \ - /usr/bin/cmake -GNinja \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_CXX_COMPILER=clang++-4.0 \ - -DCMAKE_C_COMPILER=clang-4.0 \ - -DBOOST_ROOT="${BOOST_ROOT}" \ - -DWASM_ROOT="${WASM_ROOT}" \ - -DOPENSSL_ROOT_DIR="${OPENSSL_ROOT_DIR}" \ - -DBUILD_MONGO_DB_PLUGIN=true \ - -DENABLE_COVERAGE_TESTING=true\ - -DBUILD_DOXYGEN=false -DCMAKE_CXX_FLAGS="-fsanitize=undefined -fsanitize-recover=all -g -fno-omit-frame-pointer" \ - -DCMAKE_C_FLAGS="-fsanitize=undefined -fsanitize-recover=all -g -fno-omit-frame-pointer" \ - -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=undefined -fsanitize-recover=all -rtlib=compiler-rt -lgcc_s -pthread" \ - -DCMAKE_MODULE_LINKER_FLAGS="-fsanitize=undefined -fsanitize-recover=all -rtlib=compiler-rt -lgcc_s -pthread" && \ - echo "--- :shinto_shrine: Running ninja" && \ - /usr/bin/ninja | tee ninja.log && \ - echo "--- :compression: Compressing build directory" && \ - tar -pczf build.tar.gz * - echo "--- :beers: Done" - label: ":_: Undefined Sanitizer" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "build.tar.gz" - - "ninja.log" - plugins: - docker#v1.4.0: - image: "eosio/ci:ubuntu18" - command: ["--privileged"] - workdir: /data/job - mounts: - - /etc/buildkite-agent/config:/config - environment: - - BOOST_ROOT=/root/opt/boost - - OPENSSL_ROOT_DIR=/usr/include/openssl - - WASM_ROOT=/root/opt/wasm - - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/opt/wasm/bin - - CI=true - - UBSAN_OPTIONS=print_stacktrace=1 - timeout: 60 - - - command: | - echo "--- :hammer: Building with Address Sanitizer" && \ - /usr/bin/cmake -GNinja \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_CXX_COMPILER=clang++-4.0 \ - -DCMAKE_C_COMPILER=clang-4.0 \ - -DBOOST_ROOT="${BOOST_ROOT}" \ - -DWASM_ROOT="${WASM_ROOT}" \ - -DOPENSSL_ROOT_DIR="${OPENSSL_ROOT_DIR}" \ - -DBUILD_MONGO_DB_PLUGIN=true \ - -DENABLE_COVERAGE_TESTING=true \ - -DBUILD_DOXYGEN=false \ - -DCMAKE_CXX_FLAGS="-fsanitize=address -fsanitize-recover=all -O1 -g -fno-omit-frame-pointer" \ - -DCMAKE_C_FLAGS="-fsanitize=address -fsanitize-recover=all -O1 -g -fno-omit-frame-pointer" \ - -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address -fsanitize-recover=all -rtlib=compiler-rt -lgcc_s" \ - -DCMAKE_MODULE_LINKER_FLAGS="-fsanitize=address -fsanitize-recover=all -rtlib=compiler-rt -lgcc_s" - echo "--- :shinto_shrine: Running ninja" && \ - /usr/bin/ninja | tee ninja.log && \ - echo "--- :compression: Compressing build directory" && \ - tar -pczf build.tar.gz * - echo "--- :beers: Done" - label: ":_: Address Sanitizer" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "build.tar.gz" - - "ninja.log" - plugins: - docker#v1.4.0: - image: "eosio/ci:ubuntu18" - command: ["--privileged"] - workdir: /data/job - mounts: - - /etc/buildkite-agent/config:/config - environment: - - BOOST_ROOT=/root/opt/boost - - OPENSSL_ROOT_DIR=/usr/include/openssl - - WASM_ROOT=/root/opt/wasm - - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/opt/wasm/bin - - CI=true - - ASAN_OPTIONS=fast_unwind_on_malloc=0:halt_on_error=0:detect_odr_violation=0:detect_leaks=0:symbolize=1:verbosity=1 - timeout: 60 - - - wait - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":_: Undefined Sanitizer" && \ - tar -zxf build.tar.gz --no-same-owner && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - ctest -j8 -LE _tests -V -O sanitizer.log || true - label: ":_: Undefined Sanitizer Tests" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "mongod.log" - - "sanitizer.log" - plugins: - docker#v1.4.0: - image: "eosio/ci:ubuntu18" - workdir: /data/job - mounts: - - /etc/buildkite-agent/config:/config - timeout: 120 - - - command: | - echo "--- :arrow_down: Downloading build directory" && \ - buildkite-agent artifact download "build.tar.gz" . --step ":_: Address Sanitizer" && \ - tar -zxf build.tar.gz --no-same-owner && \ - echo "--- :m: Starting MongoDB" && \ - $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ - echo "+++ :microscope: Running tests" && \ - ctest -j8 -LE _tests -V -O sanitizer.log || true - label: ":_: Address Sanitizer Tests" - agents: - queue: "automation-large-builder-fleet" - artifact_paths: - - "mongod.log" - - "sanitizer.log" - plugins: - docker#v1.4.0: - image: "eosio/ci:ubuntu18" - workdir: /data/job - mounts: - - /etc/buildkite-agent/config:/config - timeout: 120 \ No newline at end of file From 06ebc4aeab598a64a605ae4e22cfe053113b105c Mon Sep 17 00:00:00 2001 From: deadlock Date: Fri, 19 Apr 2019 15:25:26 +0800 Subject: [PATCH 036/145] 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 c41244250cb331de5e3c1e4bf7b93d4bbabec84d Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 19 Apr 2019 12:20:30 -0500 Subject: [PATCH 037/145] Reduce logging of complete object when unable to serialize --- libraries/chain/include/eosio/chain/abi_serializer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/include/eosio/chain/abi_serializer.hpp b/libraries/chain/include/eosio/chain/abi_serializer.hpp index 8f8fca4cdeb..096e1d4c1c8 100644 --- a/libraries/chain/include/eosio/chain/abi_serializer.hpp +++ b/libraries/chain/include/eosio/chain/abi_serializer.hpp @@ -680,7 +680,7 @@ void abi_serializer::to_variant( const T& o, variant& vo, Resolver resolver, con impl::abi_traverse_context ctx(max_serialization_time); impl::abi_to_variant::add(mvo, "_", o, resolver, ctx); vo = std::move(mvo["_"]); -} FC_RETHROW_EXCEPTIONS(error, "Failed to serialize type", ("object",o)) +} FC_RETHROW_EXCEPTIONS(error, "Failed to serialize type", ("type", typeid(o).name() )) template void abi_serializer::from_variant( const variant& v, T& o, Resolver resolver, const fc::microseconds& max_serialization_time ) try { From af8ed7104bdd2f42a1b7240d300e6bc5fc2f2c23 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 19 Apr 2019 13:15:32 -0500 Subject: [PATCH 038/145] Add demangle of type --- libraries/chain/include/eosio/chain/abi_serializer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/include/eosio/chain/abi_serializer.hpp b/libraries/chain/include/eosio/chain/abi_serializer.hpp index 096e1d4c1c8..06b9d2bb761 100644 --- a/libraries/chain/include/eosio/chain/abi_serializer.hpp +++ b/libraries/chain/include/eosio/chain/abi_serializer.hpp @@ -680,7 +680,7 @@ void abi_serializer::to_variant( const T& o, variant& vo, Resolver resolver, con impl::abi_traverse_context ctx(max_serialization_time); impl::abi_to_variant::add(mvo, "_", o, resolver, ctx); vo = std::move(mvo["_"]); -} FC_RETHROW_EXCEPTIONS(error, "Failed to serialize type", ("type", typeid(o).name() )) +} FC_RETHROW_EXCEPTIONS(error, "Failed to serialize: ${type}", ("type", boost::core::demangle( typeid(o).name() ) )) template void abi_serializer::from_variant( const variant& v, T& o, Resolver resolver, const fc::microseconds& max_serialization_time ) try { From 08b5ba5c8fc1903ce09b7d1b1e65a8c562c663f3 Mon Sep 17 00:00:00 2001 From: oldcold Date: Mon, 22 Apr 2019 17:26:59 +0800 Subject: [PATCH 039/145] 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 040/145] 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 041/145] 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 042/145] 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 043/145] 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 044/145] 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 045/145] 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 d45f20ce4fac9c844acc0856f167b94d3d2e110d Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 23 Apr 2019 12:51:38 -0500 Subject: [PATCH 046/145] Keep block log open to minimize open/close of file --- libraries/chain/block_log.cpp | 115 ++++++++++++++-------------------- 1 file changed, 46 insertions(+), 69 deletions(-) diff --git a/libraries/chain/block_log.cpp b/libraries/chain/block_log.cpp index 41e9756483e..42505a69909 100644 --- a/libraries/chain/block_log.cpp +++ b/libraries/chain/block_log.cpp @@ -9,6 +9,7 @@ #define LOG_READ (std::ios::in | std::ios::binary) #define LOG_WRITE (std::ios::out | std::ios::binary | std::ios::app) +#define LOG_RW ( std::ios::in | std::ios::out | std::ios::binary ) namespace eosio { namespace chain { @@ -31,47 +32,42 @@ namespace eosio { namespace chain { std::fstream index_stream; fc::path block_file; fc::path index_file; - bool block_write; - bool index_write; + bool open_files = false; bool genesis_written_to_block_log = false; uint32_t version = 0; uint32_t first_block_num = 0; - inline void check_block_read() { - if (block_write) { - block_stream.close(); - block_stream.open(block_file.generic_string().c_str(), LOG_READ); - block_write = false; + inline void check_open_files() { + if( !open_files ) { + reopen(); } } + void reopen(); - inline void check_block_write() { - if (!block_write) { + void close() { + if( block_stream.is_open() ) block_stream.close(); - block_stream.open(block_file.generic_string().c_str(), LOG_WRITE); - block_write = true; - } - } - - inline void check_index_read() { - try { - if (index_write) { - index_stream.close(); - index_stream.open(index_file.generic_string().c_str(), LOG_READ); - index_write = false; - } - } - FC_LOG_AND_RETHROW() - } - - inline void check_index_write() { - if (!index_write) { + if( index_stream.is_open() ) index_stream.close(); - index_stream.open(index_file.generic_string().c_str(), LOG_WRITE); - index_write = true; - } + open_files = false; } }; + + void block_log_impl::reopen() { + close(); + + // open to create files if they don't exist + //ilog("Opening block log at ${path}", ("path", my->block_file.generic_string())); + block_stream.open(block_file.generic_string().c_str(), LOG_WRITE); + index_stream.open(index_file.generic_string().c_str(), LOG_WRITE); + + close(); + + block_stream.open(block_file.generic_string().c_str(), LOG_RW); + index_stream.open(index_file.generic_string().c_str(), LOG_RW); + + open_files = true; + } } block_log::block_log(const fc::path& data_dir) @@ -88,26 +84,21 @@ namespace eosio { namespace chain { block_log::~block_log() { if (my) { flush(); + my->close(); my.reset(); } } void block_log::open(const fc::path& data_dir) { - if (my->block_stream.is_open()) - my->block_stream.close(); - if (my->index_stream.is_open()) - my->index_stream.close(); + my->close(); if (!fc::is_directory(data_dir)) fc::create_directories(data_dir); + my->block_file = data_dir / "blocks.log"; my->index_file = data_dir / "blocks.index"; - //ilog("Opening block log at ${path}", ("path", my->block_file.generic_string())); - my->block_stream.open(my->block_file.generic_string().c_str(), LOG_WRITE); - my->index_stream.open(my->index_file.generic_string().c_str(), LOG_WRITE); - my->block_write = true; - my->index_write = true; + my->reopen(); /* On startup of the block log, there are several states the log file and the index file can be * in relation to each other. @@ -132,7 +123,6 @@ namespace eosio { namespace chain { if (log_size) { ilog("Log is nonempty"); - my->check_block_read(); my->block_stream.seekg( 0 ); my->version = 0; my->block_stream.read( (char*)&my->version, sizeof(my->version) ); @@ -155,9 +145,6 @@ namespace eosio { namespace chain { my->head_id = my->head->id(); if (index_size) { - my->check_block_read(); - my->check_index_read(); - ilog("Index is nonempty"); uint64_t block_pos; my->block_stream.seekg(-sizeof(uint64_t), std::ios::end); @@ -180,10 +167,9 @@ namespace eosio { namespace chain { } } else if (index_size) { ilog("Index is nonempty, remove and recreate it"); - my->index_stream.close(); + my->close(); fc::remove_all(my->index_file); - my->index_stream.open(my->index_file.generic_string().c_str(), LOG_WRITE); - my->index_write = true; + my->reopen(); } } @@ -191,9 +177,10 @@ namespace eosio { namespace chain { try { EOS_ASSERT( my->genesis_written_to_block_log, block_log_append_fail, "Cannot append to block log until the genesis is first written" ); - my->check_block_write(); - my->check_index_write(); + my->check_open_files(); + my->block_stream.seekp(0, std::ios::end); + my->index_stream.seekp(0, std::ios::end); uint64_t pos = my->block_stream.tellp(); EOS_ASSERT(my->index_stream.tellp() == sizeof(uint64_t) * (b->block_num() - my->first_block_num), block_log_append_fail, @@ -220,22 +207,17 @@ namespace eosio { namespace chain { } void block_log::reset( const genesis_state& gs, const signed_block_ptr& first_block, uint32_t first_block_num ) { - if (my->block_stream.is_open()) - my->block_stream.close(); - if (my->index_stream.is_open()) - my->index_stream.close(); + my->close(); fc::remove_all(my->block_file); fc::remove_all(my->index_file); - my->block_stream.open(my->block_file.generic_string().c_str(), LOG_WRITE); - my->index_stream.open(my->index_file.generic_string().c_str(), LOG_WRITE); - my->block_write = true; - my->index_write = true; + my->reopen(); auto data = fc::raw::pack(gs); my->version = 0; // version of 0 is invalid; it indicates that the genesis was not properly written to the block log my->first_block_num = first_block_num; + my->block_stream.seekp(0, std::ios::end); my->block_stream.write((char*)&my->version, sizeof(my->version)); my->block_stream.write((char*)&my->first_block_num, sizeof(my->first_block_num)); my->block_stream.write(data.data(), data.size()); @@ -251,22 +233,16 @@ namespace eosio { namespace chain { auto pos = my->block_stream.tellp(); - my->block_stream.close(); - my->block_stream.open(my->block_file.generic_string().c_str(), std::ios::in | std::ios::out | std::ios::binary ); // Bypass append-only writing just once - static_assert( block_log::max_supported_version > 0, "a version number of zero is not supported" ); my->version = block_log::max_supported_version; my->block_stream.seekp( 0 ); my->block_stream.write( (char*)&my->version, sizeof(my->version) ); my->block_stream.seekp( pos ); flush(); - - my->block_write = false; - my->check_block_write(); // Reset to append-only writing. } std::pair block_log::read_block(uint64_t pos)const { - my->check_block_read(); + my->check_open_files(); my->block_stream.seekg(pos); std::pair result; @@ -290,7 +266,7 @@ namespace eosio { namespace chain { } uint64_t block_log::get_block_pos(uint32_t block_num) const { - my->check_index_read(); + my->check_open_files(); if (!(my->head && block_num <= block_header::num_from_id(my->head_id) && block_num >= my->first_block_num)) return npos; my->index_stream.seekg(sizeof(uint64_t) * (block_num - my->first_block_num)); @@ -300,7 +276,7 @@ namespace eosio { namespace chain { } signed_block_ptr block_log::read_head()const { - my->check_block_read(); + my->check_open_files(); uint64_t pos; @@ -328,13 +304,13 @@ namespace eosio { namespace chain { void block_log::construct_index() { ilog("Reconstructing Block Log Index..."); - my->index_stream.close(); + my->close(); + fc::remove_all(my->index_file); - my->index_stream.open(my->index_file.generic_string().c_str(), LOG_WRITE); - my->index_write = true; + + my->reopen(); uint64_t end_pos; - my->check_block_read(); my->block_stream.seekg(-sizeof( uint64_t), std::ios::end); my->block_stream.read((char*)&end_pos, sizeof(end_pos)); @@ -357,6 +333,7 @@ namespace eosio { namespace chain { my->block_stream.read((char*) &totem, sizeof(totem)); } + my->index_stream.seekp(0, std::ios::end); while( pos < end_pos ) { fc::raw::unpack(my->block_stream, tmp); my->block_stream.read((char*)&pos, sizeof(pos)); From 0c085d23075c07785c2e99e7786d910d4f048adf Mon Sep 17 00:00:00 2001 From: arhag Date: Tue, 23 Apr 2019 16:50:42 -0400 Subject: [PATCH 047/145] allow opening block log with no blocks (fixes undefined behavior bug); contruct_index should leave index file empty if block log contains no blocks --- libraries/chain/block_log.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libraries/chain/block_log.cpp b/libraries/chain/block_log.cpp index 42505a69909..1c06422c0a7 100644 --- a/libraries/chain/block_log.cpp +++ b/libraries/chain/block_log.cpp @@ -142,7 +142,11 @@ namespace eosio { namespace chain { } my->head = read_head(); - my->head_id = my->head->id(); + if( my->head ) { + my->head_id = my->head->id(); + } else { + my->head_id = {}; + } if (index_size) { ilog("Index is nonempty"); @@ -314,6 +318,12 @@ namespace eosio { namespace chain { my->block_stream.seekg(-sizeof( uint64_t), std::ios::end); my->block_stream.read((char*)&end_pos, sizeof(end_pos)); + + if( end_pos == npos ) { + ilog( "Block log contains no blocks. No need to construct index." ); + return; + } + signed_block tmp; uint64_t pos = 0; From de03c3dcdaf1f832524dbbbdff64bc8a52560976 Mon Sep 17 00:00:00 2001 From: oldcold Date: Thu, 25 Apr 2019 17:46:22 +0800 Subject: [PATCH 048/145] 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 12f934cade9fe9d60bc38d77995eb9073104190a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 25 Apr 2019 08:00:04 -0500 Subject: [PATCH 049/145] Bump to 1.6.5 --- CMakeLists.txt | 2 +- Docker/README.md | 4 ++-- README.md | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c73c0eff83c..a4111a579c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,7 @@ set( CXX_STANDARD_REQUIRED ON) set(VERSION_MAJOR 1) set(VERSION_MINOR 6) -set(VERSION_PATCH 4) +set(VERSION_PATCH 5) if(VERSION_SUFFIX) set(VERSION_FULL "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-${VERSION_SUFFIX}") diff --git a/Docker/README.md b/Docker/README.md index 38fc82cf43b..ca0de7e3e06 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -20,10 +20,10 @@ cd eos/Docker docker build . -t eosio/eos ``` -The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the 1.6.4 tag, you could do the following: +The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the 1.6.5 tag, you could do the following: ```bash -docker build -t eosio/eos:v1.6.4 --build-arg branch=v1.6.4 . +docker build -t eosio/eos:v1.6.5 --build-arg branch=v1.6.5 . ``` By default, the symbol in eosio.system is set to SYS. You can override this using the symbol argument while building the docker image. diff --git a/README.md b/README.md index 49b8a7554b0..2516e98c890 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,13 @@ $ brew remove eosio ``` #### Ubuntu 18.04 Debian Package Install ```sh -$ wget https://github.com/eosio/eos/releases/download/v1.6.4/eosio_1.6.4-1-ubuntu-18.04_amd64.deb -$ sudo apt install ./eosio_1.6.4-1-ubuntu-18.04_amd64.deb +$ wget https://github.com/eosio/eos/releases/download/v1.6.5/eosio_1.6.5-1-ubuntu-18.04_amd64.deb +$ sudo apt install ./eosio_1.6.5-1-ubuntu-18.04_amd64.deb ``` #### Ubuntu 16.04 Debian Package Install ```sh -$ wget https://github.com/eosio/eos/releases/download/v1.6.4/eosio_1.6.4-1-ubuntu-16.04_amd64.deb -$ sudo apt install ./eosio_1.6.4-1-ubuntu-16.04_amd64.deb +$ wget https://github.com/eosio/eos/releases/download/v1.6.5/eosio_1.6.5-1-ubuntu-16.04_amd64.deb +$ sudo apt install ./eosio_1.6.5-1-ubuntu-16.04_amd64.deb ``` #### Debian Package Uninstall ```sh @@ -53,8 +53,8 @@ $ sudo apt remove eosio ``` #### Centos RPM Package Install ```sh -$ wget https://github.com/eosio/eos/releases/download/v1.6.4/eosio-1.6.4-1.el7.x86_64.rpm -$ sudo yum install ./eosio-1.6.4-1.el7.x86_64.rpm +$ wget https://github.com/eosio/eos/releases/download/v1.6.5/eosio-1.6.5-1.el7.x86_64.rpm +$ sudo yum install ./eosio-1.6.5-1.el7.x86_64.rpm ``` #### Centos RPM Package Uninstall ```sh @@ -62,8 +62,8 @@ $ sudo yum remove eosio.cdt ``` #### Fedora RPM Package Install ```sh -$ wget https://github.com/eosio/eos/releases/download/v1.6.4/eosio-1.6.4-1.fc27.x86_64.rpm -$ sudo yum install ./eosio-1.6.4-1.fc27.x86_64.rpm +$ wget https://github.com/eosio/eos/releases/download/v1.6.5/eosio-1.6.5-1.fc27.x86_64.rpm +$ sudo yum install ./eosio-1.6.5-1.fc27.x86_64.rpm ``` #### Fedora RPM Package Uninstall ```sh From ef0ccfaa17aa591da76c5fe03e5ea44aa30f2f84 Mon Sep 17 00:00:00 2001 From: oldcold Date: Fri, 26 Apr 2019 12:53:20 +0800 Subject: [PATCH 050/145] 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 051/145] 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 052/145] 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 295092fbf2a5a8b1c62b807f723057a8b7851400 Mon Sep 17 00:00:00 2001 From: pursonchen <185297167@qq.com> Date: Sat, 27 Apr 2019 01:33:42 +0800 Subject: [PATCH 053/145] update the actiondemo --- unittests/actiondemo/actiondemo.abi | 119 ------------- .../actiondemo/actiondemo/actiondemo.abi | 159 ++++++++++++++++++ .../{ => actiondemo}/actiondemo.cpp | 75 ++++++--- .../{ => actiondemo}/actiondemo.hpp | 38 +++-- unittests/actiondemo/docker-compose.yml | 11 ++ unittests/actiondemo/wallet.txt | 1 + 6 files changed, 242 insertions(+), 161 deletions(-) delete mode 100644 unittests/actiondemo/actiondemo.abi create mode 100644 unittests/actiondemo/actiondemo/actiondemo.abi rename unittests/actiondemo/{ => actiondemo}/actiondemo.cpp (51%) rename unittests/actiondemo/{ => actiondemo}/actiondemo.hpp (52%) create mode 100644 unittests/actiondemo/docker-compose.yml create mode 100644 unittests/actiondemo/wallet.txt diff --git a/unittests/actiondemo/actiondemo.abi b/unittests/actiondemo/actiondemo.abi deleted file mode 100644 index eb3c15c7c13..00000000000 --- a/unittests/actiondemo/actiondemo.abi +++ /dev/null @@ -1,119 +0,0 @@ -{ - "____comment": "This file was generated by eosio-abigen. DO NOT EDIT - 2019-01-07T10:42:22", - "version": "eosio::abi/1.0", - "types": [], - "structs": [{ - "name": "seedobj", - "base": "", - "fields": [{ - "name": "id", - "type": "uint64" - },{ - "name": "create", - "type": "time_point" - },{ - "name": "seedstr", - "type": "string" - },{ - "name": "txid", - "type": "string" - },{ - "name": "action", - "type": "uint64" - } - ] - },{ - "name": "args", - "base": "", - "fields": [{ - "name": "loop", - "type": "uint64" - },{ - "name": "num", - "type": "uint64" - } - ] - },{ - "name": "generate", - "base": "", - "fields": [{ - "name": "t", - "type": "args" - } - ] - },{ - "name": "clear", - "base": "", - "fields": [] - },{ - "name": "args_name", - "base": "", - "fields": [{ - "name": "name", - "type": "name" - } - ] - },{ - "name": "hascontract", - "base": "", - "fields": [{ - "name": "t", - "type": "args_name" - } - ] - },{ - "name": "args_inline", - "base": "", - "fields": [{ - "name": "payer", - "type": "name" - },{ - "name": "in", - "type": "name" - } - ] - },{ - "name": "inlineact", - "base": "", - "fields": [{ - "name": "t", - "type": "args_inline" - } - ] - } - ], - "actions": [{ - "name": "generate", - "type": "generate", - "ricardian_contract": "" - },{ - "name": "clear", - "type": "clear", - "ricardian_contract": "" - },{ - "name": "hascontract", - "type": "hascontract", - "ricardian_contract": "" - },{ - "name": "inlineact", - "type": "inlineact", - "ricardian_contract": "" - } - ], - "tables": [{ - "name": "seedobjs", - "index_type": "i64", - "key_names": [ - "id" - ], - "key_types": [ - "uint64" - ], - "type": "seedobj" - } - ], - "ricardian_clauses": [], - "error_messages": [], - "abi_extensions": [], - "variants": [] -} \ No newline at end of file diff --git a/unittests/actiondemo/actiondemo/actiondemo.abi b/unittests/actiondemo/actiondemo/actiondemo.abi new file mode 100644 index 00000000000..8caa8e2467c --- /dev/null +++ b/unittests/actiondemo/actiondemo/actiondemo.abi @@ -0,0 +1,159 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT Fri Apr 26 17:07:28 2019", + "version": "eosio::abi/1.1", + "structs": [ + { + "name": "apply", + "base": "", + "fields": [ + { + "name": "contract", + "type": "name" + }, + { + "name": "act", + "type": "name" + } + ] + }, + { + "name": "args", + "base": "", + "fields": [ + { + "name": "loop", + "type": "uint64" + }, + { + "name": "num", + "type": "uint64" + } + ] + }, + { + "name": "args_name", + "base": "", + "fields": [ + { + "name": "name", + "type": "name" + } + ] + }, + { + "name": "argsinline", + "base": "", + "fields": [ + { + "name": "payer", + "type": "name" + }, + { + "name": "in", + "type": "name" + } + ] + }, + { + "name": "clear", + "base": "", + "fields": [] + }, + { + "name": "generate", + "base": "", + "fields": [ + { + "name": "t", + "type": "args" + } + ] + }, + { + "name": "hascontract", + "base": "", + "fields": [ + { + "name": "t", + "type": "args_name" + } + ] + }, + { + "name": "inlineact", + "base": "", + "fields": [ + { + "name": "t", + "type": "argsinline" + } + ] + }, + { + "name": "seedobj", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "create", + "type": "time_point" + }, + { + "name": "seedstr", + "type": "string" + }, + { + "name": "txid", + "type": "string" + }, + { + "name": "action", + "type": "uint64" + } + ] + } + ], + "types": [], + "actions": [ + { + "name": "apply", + "type": "apply", + "ricardian_contract": "" + }, + { + "name": "clear", + "type": "clear", + "ricardian_contract": "" + }, + { + "name": "generate", + "type": "generate", + "ricardian_contract": "" + }, + { + "name": "hascontract", + "type": "hascontract", + "ricardian_contract": "" + }, + { + "name": "inlineact", + "type": "inlineact", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "seedobjs", + "type": "seedobj", + "index_type": "i64", + "key_names": [], + "key_types": [] + } + ], + "ricardian_clauses": [], + "variants": [], + "abi_extensions": [] +} \ No newline at end of file diff --git a/unittests/actiondemo/actiondemo.cpp b/unittests/actiondemo/actiondemo/actiondemo.cpp similarity index 51% rename from unittests/actiondemo/actiondemo.cpp rename to unittests/actiondemo/actiondemo/actiondemo.cpp index 88fb113800e..363d771bcbf 100644 --- a/unittests/actiondemo/actiondemo.cpp +++ b/unittests/actiondemo/actiondemo/actiondemo.cpp @@ -1,25 +1,27 @@ #include "actiondemo.hpp" -#include "../../contracts/eosiolib/print.hpp" -#include "../../contracts/eosiolib/types.hpp" -#include "../../contracts/eosiolib/transaction.h" +// #include "../../contracts/eosiolib/print.hpp" +// #include "../../contracts/eosiolib/types.hpp" +// #include "../../contracts/eosiolib/transaction.h" +#include +#include +#include namespace spaceaction { - void actiondemo::apply( account_name code, account_name act ) { - + void actiondemo::apply( name code, name act ) { if( code != _self ) return; switch( act ) { - case N(generate): + case "generate"_n: generate(unpack_action_data()); return; - case N(inlineact): - inlineact(unpack_action_data()); - case N(clear): + case "inlineact"_n: + inlineact(unpack_action_data()); + case "clear"_n: clear(); return; - case N(hascontract): + case "hascontract"_n: hascontract(unpack_action_data()); return; } @@ -27,7 +29,7 @@ namespace spaceaction { void actiondemo::clear(){ //require_auth(_self); - seedobjs table(_self, _self); + seedobjs table{_self, _self.value}; auto iter = table.begin(); while (iter != table.end()) { @@ -54,7 +56,7 @@ namespace spaceaction { checksum256 code; get_contract_code(t.name, &code); - std::string s = to_hex((char*)&code.hash, 32); + std::string s = to_hex((char*)&code, 32); print_f("% contract_code:%", name{t.name}.to_string(),s); // } @@ -62,9 +64,9 @@ namespace spaceaction { void actiondemo::generate(const args& t){ for (int i = 0; i < t.loop; ++i) { - transaction_id_type txid; - get_transaction_id(&txid); - std::string tx = to_hex((char*)&txid.hash, 32); + size_t txid; + // size_t(&txid); + std::string tx = to_hex((char*)&txid, 32); uint64_t seq = 0; get_action_sequence(&seq); @@ -78,7 +80,7 @@ namespace spaceaction { std::string seedstr = to_hex(buf,size); - seedobjs table(_self, _self); + seedobjs table(_self, _self.value); uint64_t count = 0; for (auto itr = table.begin(); itr != table.end(); ++itr) { ++count; @@ -91,11 +93,11 @@ namespace spaceaction { a.txid = tx; a.action = seq; }); - print_f("self:%, loop:%, count:%, seedstr:%", name{_self}.to_string(), t.loop, count, r->seedstr); + print("self:%, loop:%, count:%, seedstr:%", name{_self}.to_string(), t.loop, count, r->seedstr); } } - void actiondemo::inlineact(const args_inline& t){ + void actiondemo::inlineact(const argsinline& t){ auto& payer = t.payer; args gen; gen.loop = 1; @@ -103,21 +105,38 @@ namespace spaceaction { generate(gen); - if(t.in != 0) + if(t.in != ""_n) { - INLINE_ACTION_SENDER(spaceaction::actiondemo, generate)( t.in, {payer,N(active)}, + INLINE_ACTION_SENDER(spaceaction::actiondemo, generate)( t.in, {payer,"active"_n}, { gen}); - INLINE_ACTION_SENDER(spaceaction::actiondemo, generate)( t.in, {payer,N(active)}, + INLINE_ACTION_SENDER(spaceaction::actiondemo, generate)( t.in, {payer,"active"_n}, { gen}); } } } -extern "C" { -[[noreturn]] void apply(uint64_t receiver, uint64_t code, uint64_t action) { - spaceaction::actiondemo obj(receiver); - obj.apply(code, action); - eosio_exit(0); -} -} \ No newline at end of file +// extern "C" { +// [[noreturn]] void apply(uint64_t receiver, uint64_t code, uint64_t action) { +// spaceaction::actiondemo obj(receiver); +// obj.apply(code, action); +// eosio_exit(0); +// } +// } + +#define EOSIO_DISPATCH_CUSTOM(TYPE, MEMBERS) \ + extern "C" \ + { \ + void apply(uint64_t receiver, uint64_t code, uint64_t action) \ + { \ + \ + switch (action) \ + { \ + EOSIO_DISPATCH_HELPER(TYPE, MEMBERS) \ + } \ + /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \ + \ + } \ + } + +EOSIO_DISPATCH_CUSTOM(spaceaction::actiondemo, (apply)(generate)(clear)(hascontract)(inlineact)) diff --git a/unittests/actiondemo/actiondemo.hpp b/unittests/actiondemo/actiondemo/actiondemo.hpp similarity index 52% rename from unittests/actiondemo/actiondemo.hpp rename to unittests/actiondemo/actiondemo/actiondemo.hpp index 2008eb17e6c..7b5e61572df 100644 --- a/unittests/actiondemo/actiondemo.hpp +++ b/unittests/actiondemo/actiondemo/actiondemo.hpp @@ -5,40 +5,50 @@ namespace spaceaction { using namespace eosio; - class actiondemo : public contract { + + class [[eosio::contract]] actiondemo : public contract + { typedef std::chrono::milliseconds duration; public: - actiondemo( account_name self ):contract(self){} + using contract::contract; + + // actiondemo( name self ):contract(self){} - void apply( account_name contract, account_name act ); + ACTION apply( name contract, name act ); struct args{ uint64_t loop; uint64_t num; + + }; //@abi action - void generate(const args& t); + ACTION generate(const args& t); //@abi action - void clear(); + ACTION clear(); - struct args_name{ - account_name name; + struct args_name + { + name name; }; //@abi action - void hascontract(const args_name& t); + ACTION hascontract(const args_name& t); + struct argsinline + { + name payer; + name in; - struct args_inline{ - account_name payer; - account_name in; + }; //@abi action - void inlineact(const args_inline& t); + ACTION inlineact(const argsinline& t); public: // @abi table seedobjs i64 - struct seedobj { + TABLE seedobj + { uint64_t id; time_point create; std::string seedstr; @@ -48,7 +58,7 @@ namespace spaceaction { uint64_t primary_key()const { return id; } EOSLIB_SERIALIZE(seedobj,(id)(create)(seedstr)(txid)(action)) }; - typedef eosio::multi_index< N(seedobjs), seedobj> seedobjs; + typedef eosio::multi_index< "seedobjs"_n, seedobj> seedobjs; }; diff --git a/unittests/actiondemo/docker-compose.yml b/unittests/actiondemo/docker-compose.yml new file mode 100644 index 00000000000..4412afaeace --- /dev/null +++ b/unittests/actiondemo/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3" + +services: + boscdt: + image: boscore/cdt:latest + command: /bin/bash + hostname: boscdt + stdin_open: true + tty: true + volumes: + - .:/data diff --git a/unittests/actiondemo/wallet.txt b/unittests/actiondemo/wallet.txt new file mode 100644 index 00000000000..3955796ec25 --- /dev/null +++ b/unittests/actiondemo/wallet.txt @@ -0,0 +1 @@ +PW5KBvkVfkUHqp7S7oDeesvPbk4BMe4pwDtN14HygMHxYiTLftrve \ No newline at end of file From 39c2182f6b45c60bcb688cec23e098ad979438c1 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 26 Apr 2019 15:27:56 -0500 Subject: [PATCH 054/145] Update to fc with gcc7 fix --- libraries/fc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fc b/libraries/fc index 456b588290d..16aa1495402 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 456b588290d0e109a155b0dd9fdf7ec01163380a +Subproject commit 16aa1495402083f1f1e923cb8806157784991437 From 33d102cbe805364cfd4dedac9b70f76db5138669 Mon Sep 17 00:00:00 2001 From: pursonchen <185297167@qq.com> Date: Sat, 27 Apr 2019 17:28:19 +0800 Subject: [PATCH 055/145] update actiondemo script for random testing --- .../actiondemo/{actiondemo => }/actiondemo.abi | 2 +- .../actiondemo/{actiondemo => }/actiondemo.cpp | 16 +++++++--------- .../actiondemo/{actiondemo => }/actiondemo.hpp | 0 unittests/actiondemo/docker-compose.yml | 11 ----------- unittests/actiondemo/wallet.txt | 1 - 5 files changed, 8 insertions(+), 22 deletions(-) rename unittests/actiondemo/{actiondemo => }/actiondemo.abi (98%) rename unittests/actiondemo/{actiondemo => }/actiondemo.cpp (92%) rename unittests/actiondemo/{actiondemo => }/actiondemo.hpp (100%) delete mode 100644 unittests/actiondemo/docker-compose.yml delete mode 100644 unittests/actiondemo/wallet.txt diff --git a/unittests/actiondemo/actiondemo/actiondemo.abi b/unittests/actiondemo/actiondemo.abi similarity index 98% rename from unittests/actiondemo/actiondemo/actiondemo.abi rename to unittests/actiondemo/actiondemo.abi index 8caa8e2467c..b02aa55371d 100644 --- a/unittests/actiondemo/actiondemo/actiondemo.abi +++ b/unittests/actiondemo/actiondemo.abi @@ -1,5 +1,5 @@ { - "____comment": "This file was generated with eosio-abigen. DO NOT EDIT Fri Apr 26 17:07:28 2019", + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT Sat Apr 27 08:00:01 2019", "version": "eosio::abi/1.1", "structs": [ { diff --git a/unittests/actiondemo/actiondemo/actiondemo.cpp b/unittests/actiondemo/actiondemo.cpp similarity index 92% rename from unittests/actiondemo/actiondemo/actiondemo.cpp rename to unittests/actiondemo/actiondemo.cpp index 363d771bcbf..0d668686bd0 100644 --- a/unittests/actiondemo/actiondemo/actiondemo.cpp +++ b/unittests/actiondemo/actiondemo.cpp @@ -1,7 +1,4 @@ #include "actiondemo.hpp" -// #include "../../contracts/eosiolib/print.hpp" -// #include "../../contracts/eosiolib/types.hpp" -// #include "../../contracts/eosiolib/transaction.h" #include #include #include @@ -11,7 +8,7 @@ namespace spaceaction { void actiondemo::apply( name code, name act ) { if( code != _self ) return; - + switch( act ) { case "generate"_n: generate(unpack_action_data()); @@ -63,9 +60,10 @@ namespace spaceaction { } void actiondemo::generate(const args& t){ - for (int i = 0; i < t.loop; ++i) { - size_t txid; - // size_t(&txid); + // for (int i = 0; i < 1; ++i) + // { + checksum256 txid; + get_transaction_id(&txid); std::string tx = to_hex((char*)&txid, 32); uint64_t seq = 0; @@ -93,8 +91,8 @@ namespace spaceaction { a.txid = tx; a.action = seq; }); - print("self:%, loop:%, count:%, seedstr:%", name{_self}.to_string(), t.loop, count, r->seedstr); - } + print_f("self:%, loop:%, count:%, seedstr:%", name{_self}.to_string(), t.loop, count, r->seedstr); + // } } void actiondemo::inlineact(const argsinline& t){ diff --git a/unittests/actiondemo/actiondemo/actiondemo.hpp b/unittests/actiondemo/actiondemo.hpp similarity index 100% rename from unittests/actiondemo/actiondemo/actiondemo.hpp rename to unittests/actiondemo/actiondemo.hpp diff --git a/unittests/actiondemo/docker-compose.yml b/unittests/actiondemo/docker-compose.yml deleted file mode 100644 index 4412afaeace..00000000000 --- a/unittests/actiondemo/docker-compose.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: "3" - -services: - boscdt: - image: boscore/cdt:latest - command: /bin/bash - hostname: boscdt - stdin_open: true - tty: true - volumes: - - .:/data diff --git a/unittests/actiondemo/wallet.txt b/unittests/actiondemo/wallet.txt deleted file mode 100644 index 3955796ec25..00000000000 --- a/unittests/actiondemo/wallet.txt +++ /dev/null @@ -1 +0,0 @@ -PW5KBvkVfkUHqp7S7oDeesvPbk4BMe4pwDtN14HygMHxYiTLftrve \ No newline at end of file From 8f305f56ab0ffe85cac85d900847f7052c5d5975 Mon Sep 17 00:00:00 2001 From: oldcold Date: Sat, 27 Apr 2019 19:45:05 +0800 Subject: [PATCH 056/145] 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 057/145] 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 058/145] 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 059/145] 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 060/145] 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 061/145] 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 062/145] 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 063/145] 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 064/145] 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 065/145] 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 066/145] 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 067/145] 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 068/145] 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) }); From 96b3e81a930d95c3d8bb656393eb18e07954ac28 Mon Sep 17 00:00:00 2001 From: thaipandada Date: Tue, 30 Apr 2019 21:20:06 +0800 Subject: [PATCH 069/145] prepare v3.0.0 --- CMakeLists.txt | 4 ++-- Docker/README.md | 4 ++-- README.md | 12 +----------- README_CN.md | 12 +----------- 4 files changed, 6 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e49f6dd0907..6ec500711d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,9 +33,9 @@ set( CMAKE_CXX_STANDARD 14 ) set( CMAKE_CXX_EXTENSIONS ON ) set( CXX_STANDARD_REQUIRED ON) -set(VERSION_MAJOR 2) +set(VERSION_MAJOR 3) set(VERSION_MINOR 0) -set(VERSION_PATCH 3) +set(VERSION_PATCH 0) if(VERSION_SUFFIX) set(VERSION_FULL "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-${VERSION_SUFFIX}") diff --git a/Docker/README.md b/Docker/README.md index b5228e17bbc..02eabc07b1c 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -20,10 +20,10 @@ cd bos/Docker docker build . -t boscore/bos -s BOS ``` -The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the v2.0.3 tag, you could do the following: +The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the v3.0.0 tag, you could do the following: ```bash -docker build -t boscore/bos:v2.0.3 --build-arg branch=v2.0.3 . +docker build -t boscore/bos:v3.0.0 --build-arg branch=v3.0.0 . ``` diff --git a/README.md b/README.md index 0b90a01c333..8b4a1662194 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # BOSCore - Born for DApps. Born for Usability. -## BOSCore Version: v2.0.3 +## BOSCore Version: v3.0.0 ### Basic EOSIO Version: v1.6.4 (support REX) # Background @@ -39,16 +39,6 @@ Attention: 3. Treat update of eosio/eos code as new feature. 4. Emergent issues must repaired by adopting hotfixes mode. -## BOSCore Workflow -BOSCore encourage community developer actively participate in contributing the code, members should follow the workflow below. -![BOSCore Workflow](./images/bos-workflow.png) - -Attention: -1. Only allow Feature Branch or bug fix to submit PR to Develop Branch. -2. Rebase is required before submitting PR to Develop Branch. -3. Treat update of eosio/eos code as new feature. -4. Emergent issues must repaired by adopting hotfixes mode. - BOSCore bases on EOSIO, so you can also referer: [Getting Started](https://developers.eos.io/eosio-nodeos/docs/overview-1) diff --git a/README_CN.md b/README_CN.md index 23b9199068f..35861debeca 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,6 +1,6 @@ # BOSCore - 更可用的链,为DApp而生。 -## BOSCore Version: v2.0.3 +## BOSCore Version: v3.0.0 ### Basic EOSIO Version: v1.6.4 (support REX) # 背景 @@ -39,16 +39,6 @@ BOSCore 鼓励社区开发者参与代码贡献,社区成员应当遵循以下 3. EOSIO 主网版本作为一个 Feature Branch 来对待 4. 紧急问题修复采用 hotfixes 模式 -## BOSCore 开发流程 -BOSCore 鼓励社区开发者参与代码贡献,社区成员应当遵循以下工作流: -![BOSCore Workflow](./images/bos-workflow.png) - -注意: -1. 只有待发布的 Feature Branch 或者Bug修复才应该向 Develop Branch 提交 -2. 向 Develop Branch 提交 PR 之前需要现在本地执行 rebase 操作 -3. EOSIO 主网版本作为一个 Feature Branch 来对待 -4. 紧急问题修复采用 hotfixes 模式 - BOSCore是基于EOSIO技术的扩展,所以EOSIO的相关资料也可以参考: [EOSIO 开始](https://developers.eos.io/eosio-nodeos/docs/overview-1) From f6222541151cba44a785dd323899b55c18b9e0da Mon Sep 17 00:00:00 2001 From: oldcold Date: Tue, 30 Apr 2019 21:55:33 +0800 Subject: [PATCH 070/145] bug fix: sort vectors before intersection. --- libraries/chain/pbft_database.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index 6d6f7b4e575..b11afe061bb 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -786,6 +786,9 @@ namespace eosio { } vector intersection; + + std::sort(lib_producers.begin(),lib_producers.end()); + std::sort(view_change_producers.begin(),view_change_producers.end()); std::set_intersection(lib_producers.begin(),lib_producers.end(), view_change_producers.begin(),view_change_producers.end(), back_inserter(intersection)); From d8cd42e7298c39ebec5d591f9bed9860ad1e35b2 Mon Sep 17 00:00:00 2001 From: oldcold Date: Tue, 30 Apr 2019 22:21:01 +0800 Subject: [PATCH 071/145] sort view change and producer vector before intersection. --- libraries/chain/pbft_database.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index 6d6f7b4e575..b11afe061bb 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -786,6 +786,9 @@ namespace eosio { } vector intersection; + + std::sort(lib_producers.begin(),lib_producers.end()); + std::sort(view_change_producers.begin(),view_change_producers.end()); std::set_intersection(lib_producers.begin(),lib_producers.end(), view_change_producers.begin(),view_change_producers.end(), back_inserter(intersection)); From 0d6ffa02078a28df2a0735122441b6ae6942990b Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 29 Apr 2019 11:06:51 -0500 Subject: [PATCH 072/145] Determine flags for pgrep dynamicly instead of by list of hosts --- tests/testUtils.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/testUtils.py b/tests/testUtils.py index a8dbe0fd4d2..ac18fd407ce 100755 --- a/tests/testUtils.py +++ b/tests/testUtils.py @@ -217,12 +217,17 @@ def arePortsAvailable(ports): @staticmethod def pgrepCmd(serverName): - pgrepOpts="-fl" # pylint: disable=deprecated-method - if platform.linux_distribution()[0] in ["Ubuntu", "LinuxMint", "Fedora","CentOS Linux","arch"]: + # pgrep differs on different platform (amazonlinux1 and 2 for example). We need to check if pgrep -h has -a available and add that if so: + try: + pgrepHelp = re.search('-a', subprocess.Popen("pgrep --help 2>/dev/null", shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8')) + pgrepHelp.group(0) # group() errors if -a is not found, so we don't need to do anything else special here. pgrepOpts="-a" + except AttributeError as error: + # If no -a, AttributeError: 'NoneType' object has no attribute 'group' + pgrepOpts="-fl" - return "pgrep %s %s" % (pgrepOpts, serverName) + return "pgrep %s %s" % (pgrepOpts, serverName)\ @staticmethod def getBlockLog(blockLogLocation, silentErrors=False, exitOnError=False): From 665f334c9c74a47980e4d51c0e14997228cc580c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 29 Apr 2019 13:28:05 -0500 Subject: [PATCH 073/145] Add missing import or re --- tests/testUtils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/testUtils.py b/tests/testUtils.py index ac18fd407ce..85a7f89f301 100755 --- a/tests/testUtils.py +++ b/tests/testUtils.py @@ -1,3 +1,4 @@ +import re import errno import subprocess import time From 707e8c91dae88d448730b6aa874b3146ed4aceda Mon Sep 17 00:00:00 2001 From: oldcold Date: Wed, 1 May 2019 11:05:53 +0800 Subject: [PATCH 074/145] force attempt to switch forks upon set_pbft_lib. --- libraries/chain/controller.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 93c5b0c63fb..a1ca3f5bb0a 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1664,10 +1664,10 @@ struct controller_impl { 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(controller::block_status::complete); - } + if (read_mode != db_read_mode::IRREVERSIBLE) { + maybe_switch_forks(controller::block_status::complete); } } From 822201b1200ed2a8265ed1229f6fdd7a14eeffac Mon Sep 17 00:00:00 2001 From: oldcold Date: Wed, 1 May 2019 21:03:08 +0800 Subject: [PATCH 075/145] attempt to switch forks after mark fork db. --- libraries/chain/controller.cpp | 23 +++++++++++++------ .../chain/include/eosio/chain/controller.hpp | 7 +++--- .../include/eosio/chain/pbft_database.hpp | 3 ++- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index a1ca3f5bb0a..91eacfe5390 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1664,10 +1664,10 @@ struct controller_impl { 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(controller::block_status::complete); + if (read_mode != db_read_mode::IRREVERSIBLE) { + maybe_switch_forks(controller::block_status::complete); + } } } @@ -1698,7 +1698,7 @@ struct controller_impl { void maybe_switch_forks( controller::block_status s ) { auto new_head = fork_db.head(); - if( new_head->header.previous == head->id ) { + if( new_head->header.previous == head->id && !pending) { try { apply_block( new_head->block, s ); fork_db.mark_in_current_chain( new_head, true ); @@ -2520,21 +2520,23 @@ chain_id_type controller::get_chain_id()const { return my->chain_id; } -void controller::set_pbft_prepared(const block_id_type& id) const { +void controller::set_pbft_prepared(const block_id_type& id) { my->pbft_prepared.reset(); auto bs = fetch_block_state_by_id(id); if (bs) { my->pbft_prepared = bs; my->fork_db.mark_pbft_prepared_fork(bs); + maybe_switch_forks(); } } -void controller::set_pbft_my_prepare(const block_id_type& id) const { +void controller::set_pbft_my_prepare(const block_id_type& id) { my->my_prepare.reset(); auto bs = fetch_block_state_by_id(id); if (bs) { my->my_prepare = bs; my->fork_db.mark_pbft_my_prepare_fork(bs); + maybe_switch_forks(); } } @@ -2543,8 +2545,9 @@ block_id_type controller::get_pbft_my_prepare() const { return block_id_type{}; } -void controller::reset_pbft_my_prepare() const { +void controller::reset_pbft_my_prepare() { my->fork_db.remove_pbft_my_prepare_fork(); + maybe_switch_forks(); if (my->my_prepare) my->my_prepare.reset(); } @@ -2711,6 +2714,12 @@ 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); + } +} + // this will be used in unit_test only, should not be called anywhere else. void controller::set_upo(uint32_t target_block_num) { try { diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 392a052819d..d2172f683ab 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -289,10 +289,11 @@ namespace eosio { namespace chain { 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; + void set_pbft_prepared(const block_id_type& id); + void set_pbft_my_prepare(const block_id_type& id); block_id_type get_pbft_my_prepare()const; - void reset_pbft_my_prepare()const; + void reset_pbft_my_prepare(); + void maybe_switch_forks(); signal pre_accepted_block; signal accepted_block_header; diff --git a/libraries/chain/include/eosio/chain/pbft_database.hpp b/libraries/chain/include/eosio/chain/pbft_database.hpp index 01c36132a6b..33e0a54f558 100644 --- a/libraries/chain/include/eosio/chain/pbft_database.hpp +++ b/libraries/chain/include/eosio/chain/pbft_database.hpp @@ -597,8 +597,9 @@ namespace eosio { bool should_stop_view_change(const pbft_view_change &vc); block_num_type get_current_pbft_watermark(); - controller &ctrl; + 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; From e08fb6b7ae123d738186fa1a211112b5047a110a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 1 May 2019 11:55:23 -0500 Subject: [PATCH 076/145] Change default log level from debug to info. --- libraries/fc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fc b/libraries/fc index 16aa1495402..0934dd7d3b5 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 16aa1495402083f1f1e923cb8806157784991437 +Subproject commit 0934dd7d3b5aacee1cc7229a3bfaf1a1ac89adf5 From 41c8d6f19609362b3fb880a1dbd60776af0f9a35 Mon Sep 17 00:00:00 2001 From: oldcold Date: Thu, 2 May 2019 23:44:16 +0800 Subject: [PATCH 077/145] add committed cert info view change & new view; --- libraries/chain/controller.cpp | 6 +- libraries/chain/include/eosio/chain/pbft.hpp | 5 + .../include/eosio/chain/pbft_database.hpp | 60 ++++- libraries/chain/pbft.cpp | 22 ++ libraries/chain/pbft_database.cpp | 252 ++++++++++++++++-- plugins/net_plugin/net_plugin.cpp | 4 +- 6 files changed, 316 insertions(+), 33 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 91eacfe5390..a1847a0b12b 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -351,6 +351,9 @@ struct controller_impl { void init(std::function shutdown, const snapshot_reader_ptr& snapshot) { + //do upgrade migration if necessary; + migrate_upgrade(); + bool report_integrity_hash = !!snapshot; if (snapshot) { EOS_ASSERT( !head, fork_database_exception, "" ); @@ -381,9 +384,6 @@ struct controller_impl { } } - //do upgrade migration if necessary; - migrate_upgrade(); - if( shutdown() ) return; const auto& ubi = reversible_blocks.get_index(); diff --git a/libraries/chain/include/eosio/chain/pbft.hpp b/libraries/chain/include/eosio/chain/pbft.hpp index bbca6a494ae..4d62f6c7507 100644 --- a/libraries/chain/include/eosio/chain/pbft.hpp +++ b/libraries/chain/include/eosio/chain/pbft.hpp @@ -14,6 +14,7 @@ namespace eosio { vector commits_cache; vector view_changes_cache; vector prepared_certificate; + vector> committed_certificate; vector view_changed_certificate; }; @@ -73,6 +74,10 @@ namespace eosio { void set_prepared_certificate(const vector &prepared_certificate); + const vector> &get_committed_certificate() const; + + void set_committed_certificate(const vector> &pbft_committed_certificate_vector); + const vector &get_view_changed_certificate() const; void set_view_changed_certificate(const vector &view_changed_certificate); diff --git a/libraries/chain/include/eosio/chain/pbft_database.hpp b/libraries/chain/include/eosio/chain/pbft_database.hpp index 33e0a54f558..4e021700577 100644 --- a/libraries/chain/include/eosio/chain/pbft_database.hpp +++ b/libraries/chain/include/eosio/chain/pbft_database.hpp @@ -243,11 +243,51 @@ namespace eosio { } }; + struct pbft_committed_certificate { + block_id_type block_id; + block_num_type block_num = 0; + vector commits; + + public_key_type public_key; + signature_type producer_signature; + + bool operator==(const pbft_committed_certificate &rhs) const { + return block_num == rhs.block_num + && block_id == rhs.block_id + && commits == rhs.commits + && public_key == rhs.public_key; + } + + bool operator!=(const pbft_committed_certificate &rhs) const { + return !(*this == rhs); + } + + digest_type digest() const { + digest_type::encoder enc; + fc::raw::pack(enc, block_id); + fc::raw::pack(enc, block_num); + fc::raw::pack(enc, commits); + 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; + vector committed; pbft_stable_checkpoint stable_checkpoint; public_key_type public_key; chain_id_type chain_id = chain_id_type(""); @@ -258,6 +298,7 @@ namespace eosio { return current_view == rhs.current_view && target_view == rhs.target_view && prepared == rhs.prepared + && committed == rhs.committed && stable_checkpoint == rhs.stable_checkpoint && public_key == rhs.public_key && chain_id == rhs.chain_id @@ -277,6 +318,7 @@ namespace eosio { fc::raw::pack(enc, current_view); fc::raw::pack(enc, target_view); fc::raw::pack(enc, prepared); + fc::raw::pack(enc, committed); fc::raw::pack(enc, stable_checkpoint); fc::raw::pack(enc, public_key); fc::raw::pack(enc, chain_id); @@ -333,6 +375,7 @@ namespace eosio { string uuid; uint32_t view; pbft_prepared_certificate prepared; + vector committed; pbft_stable_checkpoint stable_checkpoint; pbft_view_changed_certificate view_changed; public_key_type public_key; @@ -343,6 +386,7 @@ namespace eosio { bool operator==(const pbft_new_view &rhs) const { return view == rhs.view && prepared == rhs.prepared + && committed == rhs.committed && stable_checkpoint == rhs.stable_checkpoint && view_changed == rhs.view_changed && public_key == rhs.public_key @@ -362,6 +406,7 @@ namespace eosio { digest_type::encoder enc; fc::raw::pack(enc, view); fc::raw::pack(enc, prepared); + fc::raw::pack(enc, committed); fc::raw::pack(enc, stable_checkpoint); fc::raw::pack(enc, view_changed); fc::raw::pack(enc, public_key); @@ -532,6 +577,7 @@ namespace eosio { vector send_and_add_pbft_view_change( const vector &vcv = vector{}, const vector &ppc = vector{}, + const vector> &pcc = vector>{}, uint32_t current_view = 0, uint32_t new_view = 1); @@ -557,6 +603,8 @@ namespace eosio { vector generate_prepared_certificate(); + vector> generate_committed_certificate(); + vector generate_view_changed_certificate(uint32_t target_view); pbft_stable_checkpoint get_stable_checkpoint_by_id(const block_id_type &block_id); @@ -596,6 +644,8 @@ namespace eosio { bool should_stop_view_change(const pbft_view_change &vc); + pbft_state_ptr get_pbft_state_by_id(const block_id_type& id)const; + block_num_type get_current_pbft_watermark(); private: @@ -610,13 +660,15 @@ namespace eosio { bool is_valid_prepared_certificate(const pbft_prepared_certificate &certificate); + bool is_valid_committed_certificate(const pbft_committed_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; + producer_schedule_type lscb_active_producers() const; template void emit(const Signal &s, Arg &&a); @@ -638,12 +690,12 @@ FC_REFLECT(eosio::chain::pbft_prepare, 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)) + (uuid)(current_view)(target_view)(prepared)(committed)(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)) + (uuid)(view)(prepared)(committed)(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_committed_certificate, (block_id)(block_num)(commits)(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)) diff --git a/libraries/chain/pbft.cpp b/libraries/chain/pbft.cpp index 08c52087fd4..5e013ed1c24 100644 --- a/libraries/chain/pbft.cpp +++ b/libraries/chain/pbft.cpp @@ -509,6 +509,18 @@ namespace eosio { } } + if (!new_view.committed.empty()) { + for (auto cp :new_view.committed) { + for (auto c: cp.commits) { + try { + pbft_db.add_pbft_commit(c); + } catch (...) { + wlog( "commit insertion failure: ${cp}", ("cp", cp)); + } + } + } + } + if (!new_view.prepared.prepares.empty()) { for (auto p: new_view.prepared.prepares) { try { @@ -531,6 +543,7 @@ namespace eosio { if (this->get_target_view_retries() == 0) { this->set_view_changes_cache(vector{}); this->set_prepared_certificate(pbft_db.generate_prepared_certificate()); + this->set_committed_certificate(pbft_db.generate_committed_certificate()); } EOS_ASSERT((this->get_target_view() > this->get_current_view()), pbft_exception, @@ -547,6 +560,7 @@ namespace eosio { auto view_changes = pbft_db.send_and_add_pbft_view_change( this->get_view_changes_cache(), this->get_prepared_certificate(), + this->get_committed_certificate(), this->get_current_view(), this->get_target_view()); @@ -595,6 +609,14 @@ namespace eosio { this->cache.prepared_certificate = prepared_certificate; } + const vector> &psm_machine::get_committed_certificate() const { + return this->cache.committed_certificate; + } + + void psm_machine::set_committed_certificate(const vector> &pbft_committed_certificate_vector) { + this->cache.committed_certificate = pbft_committed_certificate_vector; + } + const vector &psm_machine::get_view_changed_certificate() const { return this->cache.view_changed_certificate; } diff --git a/libraries/chain/pbft_database.cpp b/libraries/chain/pbft_database.cpp index b11afe061bb..a0cf032b012 100644 --- a/libraries/chain/pbft_database.cpp +++ b/libraries/chain/pbft_database.cpp @@ -427,7 +427,7 @@ namespace eosio { 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 active_bps = lscb_active_producers().producers; auto &by_view_index = view_state_index.get(); auto itr = by_view_index.find(vc.target_view); @@ -472,7 +472,7 @@ namespace eosio { if (itr == by_view_index.end()) return nv; while (itr != by_view_index.end()) { - auto active_bps = lib_active_producers().producers; + auto active_bps = lscb_active_producers().producers; auto vc_count = 0; auto pvs = (*itr); @@ -494,6 +494,7 @@ namespace eosio { vector pbft_database::send_and_add_pbft_view_change( const vector &vcv, const vector &ppc, + const vector> &pcc, uint32_t current_view, uint32_t new_view) { if (!vcv.empty()) { @@ -517,9 +518,18 @@ namespace eosio { auto my_ppc = pbft_prepared_certificate{}; if (ppc_ptr != ppc.end()) my_ppc = *ppc_ptr; + + auto my_pcc = vector{}; + for (auto const &c: pcc) { + for (auto const &e: c) { + if (my_pcc.empty() && e.public_key == my_sp.first) { + my_pcc = c; + } + } + } 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()}; + auto vc = pbft_view_change{uuid, current_view, new_view, my_ppc, my_pcc, 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); @@ -553,7 +563,7 @@ namespace eosio { } void pbft_database::prune_pbft_index() { - pbft_state_index.clear(); +// pbft_state_index.clear(); view_state_index.clear(); ctrl.reset_pbft_my_prepare(); } @@ -575,6 +585,7 @@ namespace eosio { if (vcc_ptr == vcc.end()) return pbft_new_view{}; auto highest_ppc = pbft_prepared_certificate{}; + auto highest_pcc = vector{}; auto highest_sc = pbft_stable_checkpoint{}; for (const auto &vc: vcc_ptr->view_changes) { @@ -582,6 +593,14 @@ namespace eosio { highest_ppc = vc.prepared; } + for (auto const &cc: vc.committed) { + if (is_valid_committed_certificate(cc)) { + auto p_itr = find_if(highest_pcc.begin(), highest_pcc.end(), + [&](const pbft_committed_certificate &ext) { return ext.block_id == cc.block_id; }); + if (p_itr == highest_pcc.end()) highest_pcc.emplace_back(cc); + } + } + if (vc.stable_checkpoint.block_num > highest_sc.block_num && is_valid_stable_checkpoint(vc.stable_checkpoint)) { highest_sc = vc.stable_checkpoint; @@ -590,7 +609,7 @@ 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()}; + auto nv = pbft_new_view{uuid, current_view, highest_ppc, highest_pcc, highest_sc, *vcc_ptr, sp_itr->first, chain_id()}; nv.producer_signature = sp_itr->second(nv.digest()); emit(pbft_outgoing_new_view, nv); @@ -643,6 +662,80 @@ namespace eosio { } else return vector{}; } + vector> pbft_database::generate_committed_certificate() { + auto pcc = vector>{}; + pcc.resize(ctrl.my_signature_providers().size()); + auto vc = 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()) return vector>{}; + pbft_state_ptr psp = *itr; + auto committed_block_num = psp->block_num; + + if (prepare_watermarks.empty() || committed_block_num < prepare_watermarks[0]) { + if (psp->should_committed && (committed_block_num > (ctrl.last_stable_checkpoint_block_num()))) { + for (auto const &my_sp : ctrl.my_signature_providers()) { + auto pc = pbft_committed_certificate{psp->block_id, psp->block_num, psp->commits, my_sp.first}; + pc.producer_signature = my_sp.second(pc.digest()); + for (auto &c: pcc) { + c.emplace_back(pc); + } + } + return pcc; + } else return vector>{}; + } + + const auto &by_id_index = pbft_state_index.get(); + + for (auto i = 0; i < prepare_watermarks.size() && prepare_watermarks[i] <= committed_block_num; ++i) { + auto cbs = ctrl.fetch_block_state_by_number(prepare_watermarks[i]); + if (cbs) { + auto it = by_id_index.find(cbs->id); + if (it == by_id_index.end() || !(*it)->should_committed) return vector>{}; + auto as = cbs->active_schedule.producers; + + auto commits = (*it)->commits; + auto valid_commits = vector{}; + + flat_map commit_count; + flat_map> commit_msg; + + for (const auto &com: commits) { + if (commit_count.find(com.view) == commit_count.end()) commit_count[com.view] = 0; + commit_msg[com.view].emplace_back(com); + } + + for (auto const &sp: as) { + for (auto const &cc: commits) { + if (sp.block_signing_key == cc.public_key) commit_count[cc.view] += 1; + } + } + + for (auto const &e: commit_count) { + if (e.second >= as.size() * 2 / 3 + 1) { + valid_commits = commit_msg[e.first]; + } + } + + if (valid_commits.empty()) return vector>{}; + + for (auto &c: valid_commits) { + vc.emplace_back(c); + } + + for (auto const &my_sp : ctrl.my_signature_providers()) { + auto pc = pbft_committed_certificate{cbs->id, cbs->block_num, vc, my_sp.first}; + pc.producer_signature = my_sp.second(pc.digest()); + for (auto &c: pcc) { + c.emplace_back(pc); + } + } + } else return vector>{}; + } + return pcc; + } + vector pbft_database::generate_view_changed_certificate(uint32_t target_view) { auto vcc = vector{}; @@ -662,7 +755,7 @@ namespace eosio { } else return vector{}; } - bool pbft_database::is_valid_prepared_certificate(const eosio::chain::pbft_prepared_certificate &certificate) { + bool pbft_database::is_valid_prepared_certificate(const 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. @@ -677,7 +770,7 @@ namespace eosio { auto cert_id = certificate.block_id; auto cert_bs = ctrl.fetch_block_state_by_id(cert_id); - auto producer_schedule = lib_active_producers(); + auto producer_schedule = lscb_active_producers(); if (certificate.block_num > 0 && cert_bs) { producer_schedule = cert_bs->active_schedule; } @@ -744,6 +837,88 @@ namespace eosio { } } + bool pbft_database::is_valid_committed_certificate(const pbft_committed_certificate &certificate) { + // an empty certificate is valid since it acts as a null digest in pbft. + if (certificate == pbft_committed_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 = valid && certificate.is_signature_valid(); + for (auto const &c : certificate.commits) { + valid = valid && is_valid_commit(c); + if (!valid) return false; + } + + auto cert_id = certificate.block_id; + auto cert_bs = ctrl.fetch_block_state_by_id(cert_id); + auto producer_schedule = lscb_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 commits = certificate.commits; + flat_map commit_count; + + for (const auto &pre: commits) { + if (commit_count.find(pre.view) == commit_count.end()) commit_count[pre.view] = 0; + } + + for (auto const &sp: producer_schedule.producers) { + for (auto const &pp: commits) { + if (sp.block_signing_key == pp.public_key) commit_count[pp.view] += 1; + } + } + + auto should_committed = false; + + for (auto const &e: commit_count) { + if (e.second >= bp_threshold) { + should_committed = true; + } + } + + if (!should_committed) return false; + + { + //validate commit + auto lscb = ctrl.last_stable_checkpoint_block_num(); + auto non_fork_bp_count = 0; + vector commit_infos(certificate.commits.size()); + for (auto const &p : certificate.commits) { + //only search in fork db + if (p.block_num <= lscb) { + ++non_fork_bp_count; + } else { + commit_infos.emplace_back(block_info{p.block_id, p.block_num}); + } + } + + auto commit_forks = fetch_fork_from(commit_infos); + vector longest_fork; + for (auto const &f : commit_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; @@ -771,7 +946,7 @@ namespace eosio { 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) { + for (const auto& pk: lscb_active_producers().producers) { lib_producers.emplace_back(pk.block_signing_key); } auto schedule_threshold = lib_producers.size() * 2 / 3 + 1; @@ -798,6 +973,7 @@ namespace eosio { 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_pcc = vector{}; auto highest_scp = pbft_stable_checkpoint{}; for (const auto &vc: nv.view_changed.view_changes) { @@ -806,6 +982,14 @@ namespace eosio { highest_ppc = vc.prepared; } + for (auto const &cc: vc.committed) { + if (is_valid_committed_certificate(cc)) { + auto p_itr = find_if(highest_pcc.begin(), highest_pcc.end(), + [&](const pbft_committed_certificate &ext) { return ext.block_id == cc.block_id; }); + if (p_itr == highest_pcc.end()) highest_pcc.emplace_back(cc); + } + } + if (vc.stable_checkpoint.block_num > highest_scp.block_num && is_valid_stable_checkpoint(vc.stable_checkpoint)) { highest_scp = vc.stable_checkpoint; @@ -816,6 +1000,8 @@ namespace eosio { "prepared certificate not match, should be ${hpcc} but ${pc} given", ("hpcc",highest_ppc)("pc", nv.prepared)); + EOS_ASSERT(highest_pcc == nv.committed, pbft_exception, "committed certificate not match"); + 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)); @@ -963,11 +1149,16 @@ namespace eosio { if (lscb_num == 0) { const auto& ucb = ctrl.get_upgrade_properties().upgrade_complete_block_num; - 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; +// if (ucb == 0) return lscb_info; + if (ucb == 0) { + current_schedule = ctrl.initial_schedule(); + new_schedule = ctrl.initial_schedule(); + } else { + 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; @@ -1001,7 +1192,7 @@ namespace eosio { auto checkpoint = [&](const block_num_type &in) { const auto& ucb = ctrl.get_upgrade_properties().upgrade_complete_block_num; - if (ucb == 0) return false; + if (!ctrl.is_upgraded()) return false; return in >= ucb && (in % 100 == 1 || std::find(prepare_watermarks.begin(), prepare_watermarks.end(), in) != prepare_watermarks.end()); }; @@ -1203,7 +1394,7 @@ namespace eosio { //use last_stable_checkpoint producer schedule auto lscb_num = ctrl.last_stable_checkpoint_block_num(); - auto as = lib_active_producers(); + auto as = lscb_active_producers(); auto my_sp = ctrl.my_signature_providers(); for (auto i = lscb_num; i <= ctrl.head_block_num(); ++i) { @@ -1221,7 +1412,7 @@ namespace eosio { 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 as = lscb_active_producers(); auto my_sp = ctrl.my_signature_providers(); for (auto i = lscb_num; i <= ctrl.head_block_num(); ++i) { @@ -1236,21 +1427,21 @@ namespace eosio { public_key_type pbft_database::get_new_view_primary_key(const uint32_t target_view) { - auto active_bps = lib_active_producers().producers; + auto active_bps = lscb_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(); + producer_schedule_type pbft_database::lscb_active_producers() const { + auto lscb_num = ctrl.last_stable_checkpoint_block_num(); + if (lscb_num == 0) return ctrl.initial_schedule(); - auto lib_state = ctrl.fetch_block_state_by_number(lib_num); - if (!lib_state) return ctrl.initial_schedule(); + auto lscb_state = ctrl.fetch_block_state_by_number(lscb_num); + if (!lscb_state) return ctrl.initial_schedule(); - if (lib_state->pending_schedule.producers.empty()) return lib_state->active_schedule; - return lib_state->pending_schedule; + if (lscb_state->pending_schedule.producers.empty()) return lscb_state->active_schedule; + return lscb_state->pending_schedule; } chain_id_type pbft_database::chain_id() { @@ -1294,6 +1485,19 @@ namespace eosio { if (cw > lib) return cw; else return 0; } + pbft_state_ptr pbft_database::get_pbft_state_by_id(const block_id_type& id) const { + + auto &by_block_id_index = pbft_state_index.get(); + + auto itr = by_block_id_index.find(id); + + if (itr != by_block_id_index.end()) { + return (*itr); + } + + return pbft_state_ptr{}; + } + void pbft_database::set(pbft_state_ptr s) { auto result = pbft_state_index.insert(s); diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 7321c5de820..a450177c41a 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3098,7 +3098,7 @@ namespace eosio { if (!pcc.pbft_db.is_valid_new_view(msg)) return; forward_pbft_msg(c, msg); - fc_dlog( logger, "received 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); } @@ -3114,7 +3114,7 @@ namespace eosio { 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)); + fc_ilog( logger, "received checkpoint at ${n}, from ${v}", ("n", msg.block_num)("v", msg.public_key)); pbft_incoming_checkpoint_channel.publish(msg); } From 24f5c46235fc1892032ae578a56a466ed8c0ec18 Mon Sep 17 00:00:00 2001 From: deadlock Date: Fri, 3 May 2019 01:24:18 +0800 Subject: [PATCH 078/145] add pbft test case --- unittests/pbft_tests.cpp | 298 ++++++++++++++++++++++++++++++++++----- 1 file changed, 262 insertions(+), 36 deletions(-) diff --git a/unittests/pbft_tests.cpp b/unittests/pbft_tests.cpp index 50d8e573fe4..8f725f27488 100644 --- a/unittests/pbft_tests.cpp +++ b/unittests/pbft_tests.cpp @@ -17,37 +17,71 @@ using namespace eosio::testing; BOOST_AUTO_TEST_SUITE(pbft_tests) +std::map make_signature_provider(){ + std::map msp; - BOOST_AUTO_TEST_CASE(can_init) { - tester tester; - controller &ctrl = *tester.control.get(); - pbft_controller pbft_ctrl{ctrl}; + auto priv_eosio = tester::get_private_key( N(eosio), "active" ); + auto pub_eosio = tester::get_public_key( N(eosio), "active"); + auto sp_eosio = [priv_eosio]( const eosio::chain::digest_type& digest ) { + return priv_eosio.sign(digest); + }; + msp[pub_eosio]=sp_eosio; - tester.produce_block(); - auto p = pbft_ctrl.pbft_db.should_prepared(); - BOOST_CHECK(!p); - } + auto priv_alice = tester::get_private_key( N(alice), "active" ); + auto pub_alice = tester::get_public_key( N(alice), "active"); + auto sp_alice = [priv_alice]( const eosio::chain::digest_type& digest ) { + return priv_alice.sign(digest); + }; + msp[pub_alice]=sp_alice; - BOOST_AUTO_TEST_CASE(can_advance_lib_in_old_version) { - 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); - tester.produce_block(); - BOOST_REQUIRE_EQUAL(ctrl.last_irreversible_block_num(), 2); - BOOST_REQUIRE_EQUAL(ctrl.head_block_num(), 3); + auto priv_bob = tester::get_private_key( N(bob), "active" ); + auto pub_bob = tester::get_public_key( N(bob), "active"); + auto sp_bob = [priv_bob]( const eosio::chain::digest_type& digest ) { + return priv_bob.sign(digest); + }; + msp[pub_bob]=sp_bob; + + auto priv_carol = tester::get_private_key( N(carol), "active" ); + auto pub_carol = tester::get_public_key( N(carol), "active"); + auto sp_carol = [priv_carol]( const eosio::chain::digest_type& digest ) { + return priv_carol.sign(digest); + }; + msp[pub_carol]=sp_carol; + + auto priv_deny = tester::get_private_key( N(deny), "active" ); + auto pub_deny = tester::get_public_key( N(deny), "active"); + auto sp_deny = [priv_deny]( const eosio::chain::digest_type& digest ) { + return priv_deny.sign(digest); + }; + msp[pub_deny]=sp_deny; + + return msp; +} + +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_advance_lib_in_old_version) { + tester tester; + controller &ctrl = *tester.control.get(); + pbft_controller pbft_ctrl{ctrl}; + + auto msp = make_signature_provider(); + 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); + 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) { @@ -60,14 +94,7 @@ BOOST_AUTO_TEST_CASE(can_advance_lib_after_upgrade) { 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; + auto msp = make_signature_provider(); ctrl.set_my_signature_providers(msp); auto is_upgraded = ctrl.is_upgraded(); @@ -100,4 +127,203 @@ BOOST_AUTO_TEST_CASE(can_advance_lib_after_upgrade) { } + +BOOST_AUTO_TEST_CASE(can_advance_lib_after_upgrade_with_four_producers) { + tester tester; + controller &ctrl = *tester.control.get(); + pbft_controller pbft_ctrl{ctrl}; + + ctrl.set_upo(109); + + 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, 109); + + auto msp = make_signature_provider(); + 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 + tester.create_accounts( {N(alice),N(bob),N(carol),N(deny)} ); + tester.set_producers({N(alice),N(bob),N(carol),N(deny)}); + tester.produce_blocks(3);//produce block num 3,4,5 + BOOST_CHECK_EQUAL(ctrl.active_producers().producers.front().producer_name, N(alice)); + BOOST_CHECK_EQUAL(ctrl.head_block_producer(),N(eosio)); + tester.produce_blocks(7);//produce to block 12 + BOOST_CHECK_EQUAL(ctrl.head_block_producer(),N(alice)); + + BOOST_CHECK_EQUAL(ctrl.last_irreversible_block_num(), 4); + BOOST_CHECK_EQUAL(ctrl.head_block_num(), 12); + tester.produce_blocks(156 - 12); + BOOST_CHECK_EQUAL(ctrl.last_irreversible_block_num(), 108); + BOOST_CHECK_EQUAL(ctrl.head_block_num(), 156); + + is_upgraded = ctrl.is_upgraded(); + BOOST_CHECK_EQUAL(is_upgraded, false); + tester.produce_blocks(12); + is_upgraded = ctrl.is_upgraded(); + BOOST_CHECK_EQUAL(is_upgraded, true); + BOOST_CHECK_EQUAL(ctrl.last_irreversible_block_num(), 120); + BOOST_CHECK_EQUAL(ctrl.head_block_num(), 168); + BOOST_CHECK_EQUAL(ctrl.pending_pbft_lib(), false); + + 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(), 168); + BOOST_CHECK_EQUAL(ctrl.head_block_num(), 169); +} + +void push_blocks( tester& from, tester& to ) { + while( to.control->fork_db_head_block_num() < from.control->fork_db_head_block_num() ) { + auto fb = from.control->fetch_block_by_number( to.control->fork_db_head_block_num()+1 ); + to.push_block( fb ); +// if(fb->block_num()>60) { +// BOOST_CHECK_EQUAL(from.control.get()->fetch_block_state_by_id(fb->id())->id, +// to.control.get()->fetch_block_state_by_id(fb->id())->id); +// } + } +} + +BOOST_AUTO_TEST_CASE(switch_fork_when_accept_new_view_with_prepare_certificate_on_short_fork) { + tester base_fork, short_prepared_fork, long_non_prepared_fork, new_view_generator; + controller &ctrl_base_fork = *base_fork.control.get(); + pbft_controller pbft_ctrl{ctrl_base_fork}; + controller &ctrl_short_prepared_fork = *short_prepared_fork.control.get(); + pbft_controller pbft_short_prepared_fork{ctrl_short_prepared_fork}; + controller &ctrl_long_non_prepared_fork = *long_non_prepared_fork.control.get(); + pbft_controller pbft_long_non_prepared_fork{ctrl_long_non_prepared_fork}; + controller &ctrl_new_view_generator = *new_view_generator.control.get(); + pbft_controller pbft_new_view_generator{ctrl_new_view_generator}; + + auto msp = make_signature_provider(); + ctrl_base_fork.set_my_signature_providers(msp); + ctrl_short_prepared_fork.set_my_signature_providers(msp); + ctrl_long_non_prepared_fork.set_my_signature_providers(msp); + ctrl_new_view_generator.set_my_signature_providers(msp); + + ctrl_base_fork.set_upo(48); + ctrl_short_prepared_fork.set_upo(48); + ctrl_long_non_prepared_fork.set_upo(48); + ctrl_new_view_generator.set_upo(48); + + base_fork.create_accounts( {N(alice),N(bob),N(carol),N(deny)} ); + base_fork.set_producers({N(alice),N(bob),N(carol),N(deny)}); + base_fork.produce_blocks(95); + + long_non_prepared_fork.create_accounts( {N(alice),N(bob),N(carol),N(deny)} ); + long_non_prepared_fork.set_producers({N(alice),N(bob),N(carol),N(deny)}); + long_non_prepared_fork.produce_blocks(95); + + short_prepared_fork.create_accounts( {N(alice),N(bob),N(carol),N(deny)} ); + short_prepared_fork.set_producers({N(alice),N(bob),N(carol),N(deny)}); + short_prepared_fork.produce_blocks(95); + + new_view_generator.create_accounts( {N(alice),N(bob),N(carol),N(deny)} ); + new_view_generator.set_producers({N(alice),N(bob),N(carol),N(deny)}); + new_view_generator.produce_blocks(95); + + auto is_upgraded = ctrl_base_fork.is_upgraded(); + BOOST_CHECK_EQUAL(ctrl_base_fork.active_producers().producers.front().producer_name, N(alice)); + BOOST_CHECK_EQUAL(is_upgraded, true); + BOOST_CHECK_EQUAL(ctrl_base_fork.last_irreversible_block_num(), 48); + BOOST_CHECK_EQUAL(ctrl_base_fork.head_block_num(), 96); + + pbft_ctrl.maybe_pbft_prepare(); + pbft_ctrl.maybe_pbft_commit(); + base_fork.produce_blocks(4); + + pbft_long_non_prepared_fork.maybe_pbft_prepare(); + pbft_long_non_prepared_fork.maybe_pbft_commit(); + long_non_prepared_fork.produce_blocks(4); + + pbft_short_prepared_fork.maybe_pbft_prepare(); + pbft_short_prepared_fork.maybe_pbft_commit(); + short_prepared_fork.produce_blocks(4); + + + pbft_new_view_generator.maybe_pbft_prepare(); + pbft_new_view_generator.maybe_pbft_commit(); + new_view_generator.produce_blocks(4); + + BOOST_CHECK_EQUAL(ctrl_base_fork.last_irreversible_block_num(), 96); + BOOST_CHECK_EQUAL(ctrl_base_fork.head_block_num(), 100); + +// push_blocks(base_fork, long_non_prepared_fork); +// push_blocks(base_fork, short_prepared_fork); +// push_blocks(base_fork, new_view_generator); + +// pbft_short_prepared_fork.maybe_pbft_prepare(); +// pbft_short_prepared_fork.maybe_pbft_commit(); +// pbft_long_non_prepared_fork.maybe_pbft_prepare(); +// pbft_long_non_prepared_fork.maybe_pbft_commit(); +// pbft_new_view_generator.maybe_pbft_prepare(); +// pbft_new_view_generator.maybe_pbft_commit(); + + BOOST_CHECK_EQUAL(ctrl_base_fork.is_upgraded(), true); + BOOST_CHECK_EQUAL(ctrl_short_prepared_fork.is_upgraded(), true); + BOOST_CHECK_EQUAL(ctrl_long_non_prepared_fork.is_upgraded(), true); + BOOST_CHECK_EQUAL(ctrl_new_view_generator.is_upgraded(), true); + BOOST_CHECK_EQUAL(ctrl_short_prepared_fork.head_block_num(), 100); + BOOST_CHECK_EQUAL(ctrl_long_non_prepared_fork.head_block_num(), 100); + BOOST_CHECK_EQUAL(ctrl_long_non_prepared_fork.fetch_block_by_number(100)->id(), ctrl_short_prepared_fork.fetch_block_by_number(100)->id()); + + + + short_prepared_fork.create_accounts({N(shortname)}); + long_non_prepared_fork.create_accounts({N(longname)}); + short_prepared_fork.produce_blocks(36); + push_blocks(short_prepared_fork, new_view_generator); + long_non_prepared_fork.produce_blocks(72); + + + + BOOST_CHECK_EQUAL(ctrl_new_view_generator.head_block_num(), 136); + BOOST_CHECK_EQUAL(ctrl_short_prepared_fork.head_block_num(), 136); + BOOST_CHECK_EQUAL(ctrl_long_non_prepared_fork.head_block_num(), 172); + BOOST_CHECK_EQUAL(ctrl_new_view_generator.last_irreversible_block_num(), 96); + BOOST_CHECK_EQUAL(ctrl_short_prepared_fork.last_irreversible_block_num(), 96); + BOOST_CHECK_EQUAL(ctrl_long_non_prepared_fork.last_irreversible_block_num(), 96); + + //generate new view with short fork prepare certificate + pbft_new_view_generator.state_machine.set_current(new psm_committed_state); + pbft_new_view_generator.state_machine.set_prepares_cache({}); + BOOST_CHECK_EQUAL(pbft_new_view_generator.pbft_db.should_send_pbft_msg(), true); + pbft_new_view_generator.maybe_pbft_prepare(); + BOOST_CHECK_EQUAL(pbft_new_view_generator.pbft_db.should_prepared(), true); + BOOST_CHECK_EQUAL(ctrl_new_view_generator.head_block_num(), 136); + for(int i = 0; i Date: Thu, 2 May 2019 22:05:55 -0400 Subject: [PATCH 079/145] Created test metrics Buildkite job --- .buildkite/pipeline.yml | 11 + .../node_modules/node-fetch/CHANGELOG.md | 260 +++ .../node_modules/node-fetch/LICENSE.md | 22 + .../metrics/node_modules/node-fetch/README.md | 538 ++++++ .../node_modules/node-fetch/browser.js | 23 + .../node_modules/node-fetch/lib/index.es.js | 1631 ++++++++++++++++ .../node_modules/node-fetch/lib/index.js | 1640 +++++++++++++++++ .../node_modules/node-fetch/lib/index.mjs | 1629 ++++++++++++++++ .../node_modules/node-fetch/package.json | 94 + scripts/metrics/node_modules/sax/LICENSE | 41 + scripts/metrics/node_modules/sax/README.md | 225 +++ scripts/metrics/node_modules/sax/lib/sax.js | 1565 ++++++++++++++++ scripts/metrics/node_modules/sax/package.json | 61 + scripts/metrics/node_modules/xml2js/LICENSE | 19 + scripts/metrics/node_modules/xml2js/README.md | 406 ++++ .../metrics/node_modules/xml2js/lib/bom.js | 12 + .../node_modules/xml2js/lib/builder.js | 127 ++ .../node_modules/xml2js/lib/defaults.js | 72 + .../metrics/node_modules/xml2js/lib/parser.js | 357 ++++ .../node_modules/xml2js/lib/processors.js | 34 + .../metrics/node_modules/xml2js/lib/xml2js.js | 37 + .../metrics/node_modules/xml2js/package.json | 280 +++ .../node_modules/xmlbuilder/.npmignore | 5 + .../node_modules/xmlbuilder/CHANGELOG.md | 423 +++++ .../metrics/node_modules/xmlbuilder/LICENSE | 21 + .../metrics/node_modules/xmlbuilder/README.md | 85 + .../node_modules/xmlbuilder/lib/Utility.js | 73 + .../xmlbuilder/lib/XMLAttribute.js | 31 + .../node_modules/xmlbuilder/lib/XMLCData.js | 32 + .../node_modules/xmlbuilder/lib/XMLComment.js | 32 + .../xmlbuilder/lib/XMLDTDAttList.js | 50 + .../xmlbuilder/lib/XMLDTDElement.js | 35 + .../xmlbuilder/lib/XMLDTDEntity.js | 56 + .../xmlbuilder/lib/XMLDTDNotation.js | 37 + .../xmlbuilder/lib/XMLDeclaration.js | 40 + .../node_modules/xmlbuilder/lib/XMLDocType.js | 107 ++ .../xmlbuilder/lib/XMLDocument.js | 48 + .../xmlbuilder/lib/XMLDocumentCB.js | 402 ++++ .../node_modules/xmlbuilder/lib/XMLElement.js | 111 ++ .../node_modules/xmlbuilder/lib/XMLNode.js | 432 +++++ .../lib/XMLProcessingInstruction.js | 35 + .../node_modules/xmlbuilder/lib/XMLRaw.js | 32 + .../xmlbuilder/lib/XMLStreamWriter.js | 279 +++ .../xmlbuilder/lib/XMLStringWriter.js | 334 ++++ .../xmlbuilder/lib/XMLStringifier.js | 163 ++ .../node_modules/xmlbuilder/lib/XMLText.js | 32 + .../xmlbuilder/lib/XMLWriterBase.js | 90 + .../node_modules/xmlbuilder/lib/index.js | 53 + .../node_modules/xmlbuilder/package.json | 65 + scripts/metrics/package-lock.json | 30 + scripts/metrics/test-metrics.js | 415 +++++ scripts/metrics/test-metrics.json | 1 + 52 files changed, 12633 insertions(+) create mode 100644 scripts/metrics/node_modules/node-fetch/CHANGELOG.md create mode 100644 scripts/metrics/node_modules/node-fetch/LICENSE.md create mode 100644 scripts/metrics/node_modules/node-fetch/README.md create mode 100644 scripts/metrics/node_modules/node-fetch/browser.js create mode 100644 scripts/metrics/node_modules/node-fetch/lib/index.es.js create mode 100644 scripts/metrics/node_modules/node-fetch/lib/index.js create mode 100644 scripts/metrics/node_modules/node-fetch/lib/index.mjs create mode 100644 scripts/metrics/node_modules/node-fetch/package.json create mode 100644 scripts/metrics/node_modules/sax/LICENSE create mode 100644 scripts/metrics/node_modules/sax/README.md create mode 100644 scripts/metrics/node_modules/sax/lib/sax.js create mode 100644 scripts/metrics/node_modules/sax/package.json create mode 100644 scripts/metrics/node_modules/xml2js/LICENSE create mode 100644 scripts/metrics/node_modules/xml2js/README.md create mode 100644 scripts/metrics/node_modules/xml2js/lib/bom.js create mode 100644 scripts/metrics/node_modules/xml2js/lib/builder.js create mode 100644 scripts/metrics/node_modules/xml2js/lib/defaults.js create mode 100644 scripts/metrics/node_modules/xml2js/lib/parser.js create mode 100644 scripts/metrics/node_modules/xml2js/lib/processors.js create mode 100644 scripts/metrics/node_modules/xml2js/lib/xml2js.js create mode 100644 scripts/metrics/node_modules/xml2js/package.json create mode 100644 scripts/metrics/node_modules/xmlbuilder/.npmignore create mode 100644 scripts/metrics/node_modules/xmlbuilder/CHANGELOG.md create mode 100644 scripts/metrics/node_modules/xmlbuilder/LICENSE create mode 100644 scripts/metrics/node_modules/xmlbuilder/README.md create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/Utility.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLAttribute.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLCData.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLComment.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLDTDAttList.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLDTDElement.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLDTDEntity.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLDTDNotation.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLDeclaration.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLDocType.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLDocument.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLDocumentCB.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLElement.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLNode.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLProcessingInstruction.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLRaw.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLStreamWriter.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLStringWriter.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLStringifier.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLText.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/XMLWriterBase.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/lib/index.js create mode 100644 scripts/metrics/node_modules/xmlbuilder/package.json create mode 100644 scripts/metrics/package-lock.json create mode 100755 scripts/metrics/test-metrics.js create mode 100644 scripts/metrics/test-metrics.json diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 893a19f9ff9..6fed5a0f33a 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -384,6 +384,17 @@ steps: workdir: /data/job timeout: 60 + - wait: + continue_on_failure: true + + - command: | + cd scripts/metrics + node --max-old-space-size=4096 test-metrics.js + label: ":bar_chart: Test Metrics" + agents: + queue: "automation-apps-builder-fleet" + timeout: 10 + - wait - command: | diff --git a/scripts/metrics/node_modules/node-fetch/CHANGELOG.md b/scripts/metrics/node_modules/node-fetch/CHANGELOG.md new file mode 100644 index 00000000000..941b6a8d8b7 --- /dev/null +++ b/scripts/metrics/node_modules/node-fetch/CHANGELOG.md @@ -0,0 +1,260 @@ + +Changelog +========= + + +# 2.x release + +## v2.5.0 + +- Enhance: `Response` object now includes `redirected` property. +- Enhance: `fetch()` now accepts third-party `Blob` implementation as body. +- Other: disable `package-lock.json` generation as we never commit them. +- Other: dev dependency update. +- Other: readme update. + +## v2.4.1 + +- Fix: `Blob` import rule for node < 10, as `Readable` isn't a named export. + +## v2.4.0 + +- Enhance: added `Brotli` compression support (using node's zlib). +- Enhance: updated `Blob` implementation per spec. +- Fix: set content type automatically for `URLSearchParams`. +- Fix: `Headers` now reject empty header names. +- Fix: test cases, as node 12+ no longer accepts invalid header response. + +## v2.3.0 + +- Enhance: added `AbortSignal` support, with README example. +- Enhance: handle invalid `Location` header during redirect by rejecting them explicitly with `FetchError`. +- Fix: update `browser.js` to support react-native environment, where `self` isn't available globally. + +## v2.2.1 + +- Fix: `compress` flag shouldn't overwrite existing `Accept-Encoding` header. +- Fix: multiple `import` rules, where `PassThrough` etc. doesn't have a named export when using node <10 and `--exerimental-modules` flag. +- Other: Better README. + +## v2.2.0 + +- Enhance: Support all `ArrayBuffer` view types +- Enhance: Support Web Workers +- Enhance: Support Node.js' `--experimental-modules` mode; deprecate `.es.js` file +- Fix: Add `__esModule` property to the exports object +- Other: Better example in README for writing response to a file +- Other: More tests for Agent + +## v2.1.2 + +- Fix: allow `Body` methods to work on `ArrayBuffer`-backed `Body` objects +- Fix: reject promise returned by `Body` methods when the accumulated `Buffer` exceeds the maximum size +- Fix: support custom `Host` headers with any casing +- Fix: support importing `fetch()` from TypeScript in `browser.js` +- Fix: handle the redirect response body properly + +## v2.1.1 + +Fix packaging errors in v2.1.0. + +## v2.1.0 + +- Enhance: allow using ArrayBuffer as the `body` of a `fetch()` or `Request` +- Fix: store HTTP headers of a `Headers` object internally with the given case, for compatibility with older servers that incorrectly treated header names in a case-sensitive manner +- Fix: silently ignore invalid HTTP headers +- Fix: handle HTTP redirect responses without a `Location` header just like non-redirect responses +- Fix: include bodies when following a redirection when appropriate + +## v2.0.0 + +This is a major release. Check [our upgrade guide](https://github.com/bitinn/node-fetch/blob/master/UPGRADE-GUIDE.md) for an overview on some key differences between v1 and v2. + +### General changes + +- Major: Node.js 0.10.x and 0.12.x support is dropped +- Major: `require('node-fetch/lib/response')` etc. is now unsupported; use `require('node-fetch').Response` or ES6 module imports +- Enhance: start testing on Node.js v4.x, v6.x, v8.x LTS, as well as v9.x stable +- Enhance: use Rollup to produce a distributed bundle (less memory overhead and faster startup) +- Enhance: make `Object.prototype.toString()` on Headers, Requests, and Responses return correct class strings +- Other: rewrite in ES2015 using Babel +- Other: use Codecov for code coverage tracking +- Other: update package.json script for npm 5 +- Other: `encoding` module is now optional (alpha.7) +- Other: expose browser.js through package.json, avoid bundling mishaps (alpha.9) +- Other: allow TypeScript to `import` node-fetch by exposing default (alpha.9) + +### HTTP requests + +- Major: overwrite user's `Content-Length` if we can be sure our information is correct (per spec) +- Fix: errors in a response are caught before the body is accessed +- Fix: support WHATWG URL objects, created by `whatwg-url` package or `require('url').URL` in Node.js 7+ + +### Response and Request classes + +- Major: `response.text()` no longer attempts to detect encoding, instead always opting for UTF-8 (per spec); use `response.textConverted()` for the v1 behavior +- Major: make `response.json()` throw error instead of returning an empty object on 204 no-content respose (per spec; reverts behavior changed in v1.6.2) +- Major: internal methods are no longer exposed +- Major: throw error when a `GET` or `HEAD` Request is constructed with a non-null body (per spec) +- Enhance: add `response.arrayBuffer()` (also applies to Requests) +- Enhance: add experimental `response.blob()` (also applies to Requests) +- Enhance: `URLSearchParams` is now accepted as a body +- Enhance: wrap `response.json()` json parsing error as `FetchError` +- Fix: fix Request and Response with `null` body + +### Headers class + +- Major: remove `headers.getAll()`; make `get()` return all headers delimited by commas (per spec) +- Enhance: make Headers iterable +- Enhance: make Headers constructor accept an array of tuples +- Enhance: make sure header names and values are valid in HTTP +- Fix: coerce Headers prototype function parameters to strings, where applicable + +### Documentation + +- Enhance: more comprehensive API docs +- Enhance: add a list of default headers in README + + +# 1.x release + +## backport releases (v1.7.0 and beyond) + +See [changelog on 1.x branch](https://github.com/bitinn/node-fetch/blob/1.x/CHANGELOG.md) for details. + +## v1.6.3 + +- Enhance: error handling document to explain `FetchError` design +- Fix: support `form-data` 2.x releases (requires `form-data` >= 2.1.0) + +## v1.6.2 + +- Enhance: minor document update +- Fix: response.json() returns empty object on 204 no-content response instead of throwing a syntax error + +## v1.6.1 + +- Fix: if `res.body` is a non-stream non-formdata object, we will call `body.toString` and send it as a string +- Fix: `counter` value is incorrectly set to `follow` value when wrapping Request instance +- Fix: documentation update + +## v1.6.0 + +- Enhance: added `res.buffer()` api for convenience, it returns body as a Node.js buffer +- Enhance: better old server support by handling raw deflate response +- Enhance: skip encoding detection for non-HTML/XML response +- Enhance: minor document update +- Fix: HEAD request doesn't need decompression, as body is empty +- Fix: `req.body` now accepts a Node.js buffer + +## v1.5.3 + +- Fix: handle 204 and 304 responses when body is empty but content-encoding is gzip/deflate +- Fix: allow resolving response and cloned response in any order +- Fix: avoid setting `content-length` when `form-data` body use streams +- Fix: send DELETE request with content-length when body is present +- Fix: allow any url when calling new Request, but still reject non-http(s) url in fetch + +## v1.5.2 + +- Fix: allow node.js core to handle keep-alive connection pool when passing a custom agent + +## v1.5.1 + +- Fix: redirect mode `manual` should work even when there is no redirection or broken redirection + +## v1.5.0 + +- Enhance: rejected promise now use custom `Error` (thx to @pekeler) +- Enhance: `FetchError` contains `err.type` and `err.code`, allows for better error handling (thx to @pekeler) +- Enhance: basic support for redirect mode `manual` and `error`, allows for location header extraction (thx to @jimmywarting for the initial PR) + +## v1.4.1 + +- Fix: wrapping Request instance with FormData body again should preserve the body as-is + +## v1.4.0 + +- Enhance: Request and Response now have `clone` method (thx to @kirill-konshin for the initial PR) +- Enhance: Request and Response now have proper string and buffer body support (thx to @kirill-konshin) +- Enhance: Body constructor has been refactored out (thx to @kirill-konshin) +- Enhance: Headers now has `forEach` method (thx to @tricoder42) +- Enhance: back to 100% code coverage +- Fix: better form-data support (thx to @item4) +- Fix: better character encoding detection under chunked encoding (thx to @dsuket for the initial PR) + +## v1.3.3 + +- Fix: make sure `Content-Length` header is set when body is string for POST/PUT/PATCH requests +- Fix: handle body stream error, for cases such as incorrect `Content-Encoding` header +- Fix: when following certain redirects, use `GET` on subsequent request per Fetch Spec +- Fix: `Request` and `Response` constructors now parse headers input using `Headers` + +## v1.3.2 + +- Enhance: allow auto detect of form-data input (no `FormData` spec on node.js, this is form-data specific feature) + +## v1.3.1 + +- Enhance: allow custom host header to be set (server-side only feature, as it's a forbidden header on client-side) + +## v1.3.0 + +- Enhance: now `fetch.Request` is exposed as well + +## v1.2.1 + +- Enhance: `Headers` now normalized `Number` value to `String`, prevent common mistakes + +## v1.2.0 + +- Enhance: now fetch.Headers and fetch.Response are exposed, making testing easier + +## v1.1.2 + +- Fix: `Headers` should only support `String` and `Array` properties, and ignore others + +## v1.1.1 + +- Enhance: now req.headers accept both plain object and `Headers` instance + +## v1.1.0 + +- Enhance: timeout now also applies to response body (in case of slow response) +- Fix: timeout is now cleared properly when fetch is done/has failed + +## v1.0.6 + +- Fix: less greedy content-type charset matching + +## v1.0.5 + +- Fix: when `follow = 0`, fetch should not follow redirect +- Enhance: update tests for better coverage +- Enhance: code formatting +- Enhance: clean up doc + +## v1.0.4 + +- Enhance: test iojs support +- Enhance: timeout attached to socket event only fire once per redirect + +## v1.0.3 + +- Fix: response size limit should reject large chunk +- Enhance: added character encoding detection for xml, such as rss/atom feed (encoding in DTD) + +## v1.0.2 + +- Fix: added res.ok per spec change + +## v1.0.0 + +- Enhance: better test coverage and doc + + +# 0.x release + +## v0.1 + +- Major: initial public release diff --git a/scripts/metrics/node_modules/node-fetch/LICENSE.md b/scripts/metrics/node_modules/node-fetch/LICENSE.md new file mode 100644 index 00000000000..660ffecb58b --- /dev/null +++ b/scripts/metrics/node_modules/node-fetch/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 David Frank + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/scripts/metrics/node_modules/node-fetch/README.md b/scripts/metrics/node_modules/node-fetch/README.md new file mode 100644 index 00000000000..48f4215e4e7 --- /dev/null +++ b/scripts/metrics/node_modules/node-fetch/README.md @@ -0,0 +1,538 @@ +node-fetch +========== + +[![npm version][npm-image]][npm-url] +[![build status][travis-image]][travis-url] +[![coverage status][codecov-image]][codecov-url] +[![install size][install-size-image]][install-size-url] + +A light-weight module that brings `window.fetch` to Node.js + +(We are looking for [v2 maintainers and collaborators](https://github.com/bitinn/node-fetch/issues/567)) + + + +- [Motivation](#motivation) +- [Features](#features) +- [Difference from client-side fetch](#difference-from-client-side-fetch) +- [Installation](#installation) +- [Loading and configuring the module](#loading-and-configuring-the-module) +- [Common Usage](#common-usage) + - [Plain text or HTML](#plain-text-or-html) + - [JSON](#json) + - [Simple Post](#simple-post) + - [Post with JSON](#post-with-json) + - [Post with form parameters](#post-with-form-parameters) + - [Handling exceptions](#handling-exceptions) + - [Handling client and server errors](#handling-client-and-server-errors) +- [Advanced Usage](#advanced-usage) + - [Streams](#streams) + - [Buffer](#buffer) + - [Accessing Headers and other Meta data](#accessing-headers-and-other-meta-data) + - [Post data using a file stream](#post-data-using-a-file-stream) + - [Post with form-data (detect multipart)](#post-with-form-data-detect-multipart) + - [Request cancellation with AbortSignal](#request-cancellation-with-abortsignal) +- [API](#api) + - [fetch(url[, options])](#fetchurl-options) + - [Options](#options) + - [Class: Request](#class-request) + - [Class: Response](#class-response) + - [Class: Headers](#class-headers) + - [Interface: Body](#interface-body) + - [Class: FetchError](#class-fetcherror) +- [License](#license) +- [Acknowledgement](#acknowledgement) + + + +## Motivation + +Instead of implementing `XMLHttpRequest` in Node.js to run browser-specific [Fetch polyfill](https://github.com/github/fetch), why not go from native `http` to `fetch` API directly? Hence `node-fetch`, minimal code for a `window.fetch` compatible API on Node.js runtime. + +See Matt Andrews' [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) or Leonardo Quixada's [cross-fetch](https://github.com/lquixada/cross-fetch) for isomorphic usage (exports `node-fetch` for server-side, `whatwg-fetch` for client-side). + +## Features + +- Stay consistent with `window.fetch` API. +- Make conscious trade-off when following [WHATWG fetch spec][whatwg-fetch] and [stream spec](https://streams.spec.whatwg.org/) implementation details, document known differences. +- Use native promise, but allow substituting it with [insert your favorite promise library]. +- Use native Node streams for body, on both request and response. +- Decode content encoding (gzip/deflate) properly, and convert string output (such as `res.text()` and `res.json()`) to UTF-8 automatically. +- Useful extensions such as timeout, redirect limit, response size limit, [explicit errors](ERROR-HANDLING.md) for troubleshooting. + +## Difference from client-side fetch + +- See [Known Differences](LIMITS.md) for details. +- If you happen to use a missing feature that `window.fetch` offers, feel free to open an issue. +- Pull requests are welcomed too! + +## Installation + +Current stable release (`2.x`) + +```sh +$ npm install node-fetch --save +``` + +## Loading and configuring the module +We suggest you load the module via `require`, pending the stabalizing of es modules in node: +```js +const fetch = require('node-fetch'); +``` + +If you are using a Promise library other than native, set it through fetch.Promise: +```js +const Bluebird = require('bluebird'); + +fetch.Promise = Bluebird; +``` + +## Common Usage + +NOTE: The documentation below is up-to-date with `2.x` releases, [see `1.x` readme](https://github.com/bitinn/node-fetch/blob/1.x/README.md), [changelog](https://github.com/bitinn/node-fetch/blob/1.x/CHANGELOG.md) and [2.x upgrade guide](UPGRADE-GUIDE.md) for the differences. + +#### Plain text or HTML +```js +fetch('https://github.com/') + .then(res => res.text()) + .then(body => console.log(body)); +``` + +#### JSON + +```js + +fetch('https://api.github.com/users/github') + .then(res => res.json()) + .then(json => console.log(json)); +``` + +#### Simple Post +```js +fetch('https://httpbin.org/post', { method: 'POST', body: 'a=1' }) + .then(res => res.json()) // expecting a json response + .then(json => console.log(json)); +``` + +#### Post with JSON + +```js +const body = { a: 1 }; + +fetch('https://httpbin.org/post', { + method: 'post', + body: JSON.stringify(body), + headers: { 'Content-Type': 'application/json' }, + }) + .then(res => res.json()) + .then(json => console.log(json)); +``` + +#### Post with form parameters +`URLSearchParams` is available in Node.js as of v7.5.0. See [official documentation](https://nodejs.org/api/url.html#url_class_urlsearchparams) for more usage methods. + +NOTE: The `Content-Type` header is only set automatically to `x-www-form-urlencoded` when an instance of `URLSearchParams` is given as such: + +```js +const { URLSearchParams } = require('url'); + +const params = new URLSearchParams(); +params.append('a', 1); + +fetch('https://httpbin.org/post', { method: 'POST', body: params }) + .then(res => res.json()) + .then(json => console.log(json)); +``` + +#### Handling exceptions +NOTE: 3xx-5xx responses are *NOT* exceptions, and should be handled in `then()`, see the next section. + +Adding a catch to the fetch promise chain will catch *all* exceptions, such as errors originating from node core libraries, like network errors, and operational errors which are instances of FetchError. See the [error handling document](ERROR-HANDLING.md) for more details. + +```js +fetch('https://domain.invalid/') + .catch(err => console.error(err)); +``` + +#### Handling client and server errors +It is common to create a helper function to check that the response contains no client (4xx) or server (5xx) error responses: + +```js +function checkStatus(res) { + if (res.ok) { // res.status >= 200 && res.status < 300 + return res; + } else { + throw MyCustomError(res.statusText); + } +} + +fetch('https://httpbin.org/status/400') + .then(checkStatus) + .then(res => console.log('will not get here...')) +``` + +## Advanced Usage + +#### Streams +The "Node.js way" is to use streams when possible: + +```js +fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png') + .then(res => { + const dest = fs.createWriteStream('./octocat.png'); + res.body.pipe(dest); + }); +``` + +#### Buffer +If you prefer to cache binary data in full, use buffer(). (NOTE: buffer() is a `node-fetch` only API) + +```js +const fileType = require('file-type'); + +fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png') + .then(res => res.buffer()) + .then(buffer => fileType(buffer)) + .then(type => { /* ... */ }); +``` + +#### Accessing Headers and other Meta data +```js +fetch('https://github.com/') + .then(res => { + console.log(res.ok); + console.log(res.status); + console.log(res.statusText); + console.log(res.headers.raw()); + console.log(res.headers.get('content-type')); + }); +``` + +#### Post data using a file stream + +```js +const { createReadStream } = require('fs'); + +const stream = createReadStream('input.txt'); + +fetch('https://httpbin.org/post', { method: 'POST', body: stream }) + .then(res => res.json()) + .then(json => console.log(json)); +``` + +#### Post with form-data (detect multipart) + +```js +const FormData = require('form-data'); + +const form = new FormData(); +form.append('a', 1); + +fetch('https://httpbin.org/post', { method: 'POST', body: form }) + .then(res => res.json()) + .then(json => console.log(json)); + +// OR, using custom headers +// NOTE: getHeaders() is non-standard API + +const form = new FormData(); +form.append('a', 1); + +const options = { + method: 'POST', + body: form, + headers: form.getHeaders() +} + +fetch('https://httpbin.org/post', options) + .then(res => res.json()) + .then(json => console.log(json)); +``` + +#### Request cancellation with AbortSignal + +> NOTE: You may only cancel streamed requests on Node >= v8.0.0 + +You may cancel requests with `AbortController`. A suggested implementation is [`abort-controller`](https://www.npmjs.com/package/abort-controller). + +An example of timing out a request after 150ms could be achieved as follows: + +```js +import AbortController from 'abort-controller'; + +const controller = new AbortController(); +const timeout = setTimeout( + () => { controller.abort(); }, + 150, +); + +fetch(url, { signal: controller.signal }) + .then(res => res.json()) + .then( + data => { + useData(data) + }, + err => { + if (err.name === 'AbortError') { + // request was aborted + } + }, + ) + .finally(() => { + clearTimeout(timeout); + }); +``` + +See [test cases](https://github.com/bitinn/node-fetch/blob/master/test/test.js) for more examples. + + +## API + +### fetch(url[, options]) + +- `url` A string representing the URL for fetching +- `options` [Options](#fetch-options) for the HTTP(S) request +- Returns: Promise<[Response](#class-response)> + +Perform an HTTP(S) fetch. + +`url` should be an absolute url, such as `https://example.com/`. A path-relative URL (`/file/under/root`) or protocol-relative URL (`//can-be-http-or-https.com/`) will result in a rejected promise. + + +### Options + +The default values are shown after each option key. + +```js +{ + // These properties are part of the Fetch Standard + method: 'GET', + headers: {}, // request headers. format is the identical to that accepted by the Headers constructor (see below) + body: null, // request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream + redirect: 'follow', // set to `manual` to extract redirect headers, `error` to reject redirect + signal: null, // pass an instance of AbortSignal to optionally abort requests + + // The following properties are node-fetch extensions + follow: 20, // maximum redirect count. 0 to not follow redirect + timeout: 0, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead. + compress: true, // support gzip/deflate content encoding. false to disable + size: 0, // maximum response body size in bytes. 0 to disable + agent: null // http(s).Agent instance, allows custom proxy, certificate, dns lookup etc. +} +``` + +##### Default Headers + +If no values are set, the following request headers will be sent automatically: + +Header | Value +------------------- | -------------------------------------------------------- +`Accept-Encoding` | `gzip,deflate` _(when `options.compress === true`)_ +`Accept` | `*/*` +`Connection` | `close` _(when no `options.agent` is present)_ +`Content-Length` | _(automatically calculated, if possible)_ +`Transfer-Encoding` | `chunked` _(when `req.body` is a stream)_ +`User-Agent` | `node-fetch/1.0 (+https://github.com/bitinn/node-fetch)` + + +### Class: Request + +An HTTP(S) request containing information about URL, method, headers, and the body. This class implements the [Body](#iface-body) interface. + +Due to the nature of Node.js, the following properties are not implemented at this moment: + +- `type` +- `destination` +- `referrer` +- `referrerPolicy` +- `mode` +- `credentials` +- `cache` +- `integrity` +- `keepalive` + +The following node-fetch extension properties are provided: + +- `follow` +- `compress` +- `counter` +- `agent` + +See [options](#fetch-options) for exact meaning of these extensions. + +#### new Request(input[, options]) + +*(spec-compliant)* + +- `input` A string representing a URL, or another `Request` (which will be cloned) +- `options` [Options][#fetch-options] for the HTTP(S) request + +Constructs a new `Request` object. The constructor is identical to that in the [browser](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request). + +In most cases, directly `fetch(url, options)` is simpler than creating a `Request` object. + + +### Class: Response + +An HTTP(S) response. This class implements the [Body](#iface-body) interface. + +The following properties are not implemented in node-fetch at this moment: + +- `Response.error()` +- `Response.redirect()` +- `type` +- `trailer` + +#### new Response([body[, options]]) + +*(spec-compliant)* + +- `body` A string or [Readable stream][node-readable] +- `options` A [`ResponseInit`][response-init] options dictionary + +Constructs a new `Response` object. The constructor is identical to that in the [browser](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response). + +Because Node.js does not implement service workers (for which this class was designed), one rarely has to construct a `Response` directly. + +#### response.ok + +*(spec-compliant)* + +Convenience property representing if the request ended normally. Will evaluate to true if the response status was greater than or equal to 200 but smaller than 300. + +#### response.redirected + +*(spec-compliant)* + +Convenience property representing if the request has been redirected at least once. Will evaluate to true if the internal redirect counter is greater than 0. + + +### Class: Headers + +This class allows manipulating and iterating over a set of HTTP headers. All methods specified in the [Fetch Standard][whatwg-fetch] are implemented. + +#### new Headers([init]) + +*(spec-compliant)* + +- `init` Optional argument to pre-fill the `Headers` object + +Construct a new `Headers` object. `init` can be either `null`, a `Headers` object, an key-value map object, or any iterable object. + +```js +// Example adapted from https://fetch.spec.whatwg.org/#example-headers-class + +const meta = { + 'Content-Type': 'text/xml', + 'Breaking-Bad': '<3' +}; +const headers = new Headers(meta); + +// The above is equivalent to +const meta = [ + [ 'Content-Type', 'text/xml' ], + [ 'Breaking-Bad', '<3' ] +]; +const headers = new Headers(meta); + +// You can in fact use any iterable objects, like a Map or even another Headers +const meta = new Map(); +meta.set('Content-Type', 'text/xml'); +meta.set('Breaking-Bad', '<3'); +const headers = new Headers(meta); +const copyOfHeaders = new Headers(headers); +``` + + +### Interface: Body + +`Body` is an abstract interface with methods that are applicable to both `Request` and `Response` classes. + +The following methods are not yet implemented in node-fetch at this moment: + +- `formData()` + +#### body.body + +*(deviation from spec)* + +* Node.js [`Readable` stream][node-readable] + +The data encapsulated in the `Body` object. Note that while the [Fetch Standard][whatwg-fetch] requires the property to always be a WHATWG `ReadableStream`, in node-fetch it is a Node.js [`Readable` stream][node-readable]. + +#### body.bodyUsed + +*(spec-compliant)* + +* `Boolean` + +A boolean property for if this body has been consumed. Per spec, a consumed body cannot be used again. + +#### body.arrayBuffer() +#### body.blob() +#### body.json() +#### body.text() + +*(spec-compliant)* + +* Returns: Promise + +Consume the body and return a promise that will resolve to one of these formats. + +#### body.buffer() + +*(node-fetch extension)* + +* Returns: Promise<Buffer> + +Consume the body and return a promise that will resolve to a Buffer. + +#### body.textConverted() + +*(node-fetch extension)* + +* Returns: Promise<String> + +Identical to `body.text()`, except instead of always converting to UTF-8, encoding sniffing will be performed and text converted to UTF-8, if possible. + +(This API requires an optional dependency on npm package [encoding](https://www.npmjs.com/package/encoding), which you need to install manually. `webpack` users may see [a warning message](https://github.com/bitinn/node-fetch/issues/412#issuecomment-379007792) due to this optional dependency.) + + +### Class: FetchError + +*(node-fetch extension)* + +An operational error in the fetching process. See [ERROR-HANDLING.md][] for more info. + + +### Class: AbortError + +*(node-fetch extension)* + +An Error thrown when the request is aborted in response to an `AbortSignal`'s `abort` event. It has a `name` property of `AbortError`. See [ERROR-HANDLING.MD][] for more info. + +## Acknowledgement + +Thanks to [github/fetch](https://github.com/github/fetch) for providing a solid implementation reference. + +`node-fetch` v1 was maintained by [@bitinn](https://github.com/bitinn); v2 was maintained by [@TimothyGu](https://github.com/timothygu), [@bitinn](https://github.com/bitinn) and [@jimmywarting](https://github.com/jimmywarting); v2 readme is written by [@jkantr](https://github.com/jkantr). + +## License + +MIT + +[npm-image]: https://flat.badgen.net/npm/v/node-fetch +[npm-url]: https://www.npmjs.com/package/node-fetch +[travis-image]: https://flat.badgen.net/travis/bitinn/node-fetch +[travis-url]: https://travis-ci.org/bitinn/node-fetch +[codecov-image]: https://flat.badgen.net/codecov/c/github/bitinn/node-fetch/master +[codecov-url]: https://codecov.io/gh/bitinn/node-fetch +[install-size-image]: https://flat.badgen.net/packagephobia/install/node-fetch +[install-size-url]: https://packagephobia.now.sh/result?p=node-fetch +[whatwg-fetch]: https://fetch.spec.whatwg.org/ +[response-init]: https://fetch.spec.whatwg.org/#responseinit +[node-readable]: https://nodejs.org/api/stream.html#stream_readable_streams +[mdn-headers]: https://developer.mozilla.org/en-US/docs/Web/API/Headers +[LIMITS.md]: https://github.com/bitinn/node-fetch/blob/master/LIMITS.md +[ERROR-HANDLING.md]: https://github.com/bitinn/node-fetch/blob/master/ERROR-HANDLING.md +[UPGRADE-GUIDE.md]: https://github.com/bitinn/node-fetch/blob/master/UPGRADE-GUIDE.md diff --git a/scripts/metrics/node_modules/node-fetch/browser.js b/scripts/metrics/node_modules/node-fetch/browser.js new file mode 100644 index 00000000000..0ad5de004c4 --- /dev/null +++ b/scripts/metrics/node_modules/node-fetch/browser.js @@ -0,0 +1,23 @@ +"use strict"; + +// ref: https://github.com/tc39/proposal-global +var getGlobal = function () { + // the only reliable means to get the global object is + // `Function('return this')()` + // However, this causes CSP violations in Chrome apps. + if (typeof self !== 'undefined') { return self; } + if (typeof window !== 'undefined') { return window; } + if (typeof global !== 'undefined') { return global; } + throw new Error('unable to locate global object'); +} + +var global = getGlobal(); + +module.exports = exports = global.fetch; + +// Needed for TypeScript and Webpack. +exports.default = global.fetch.bind(global); + +exports.Headers = global.Headers; +exports.Request = global.Request; +exports.Response = global.Response; \ No newline at end of file diff --git a/scripts/metrics/node_modules/node-fetch/lib/index.es.js b/scripts/metrics/node_modules/node-fetch/lib/index.es.js new file mode 100644 index 00000000000..20ab807872f --- /dev/null +++ b/scripts/metrics/node_modules/node-fetch/lib/index.es.js @@ -0,0 +1,1631 @@ +process.emitWarning("The .es.js file is deprecated. Use .mjs instead."); + +import Stream from 'stream'; +import http from 'http'; +import Url from 'url'; +import https from 'https'; +import zlib from 'zlib'; + +// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js + +// fix for "Readable" isn't a named export issue +const Readable = Stream.Readable; + +const BUFFER = Symbol('buffer'); +const TYPE = Symbol('type'); + +class Blob { + constructor() { + this[TYPE] = ''; + + const blobParts = arguments[0]; + const options = arguments[1]; + + const buffers = []; + let size = 0; + + if (blobParts) { + const a = blobParts; + const length = Number(a.length); + for (let i = 0; i < length; i++) { + const element = a[i]; + let buffer; + if (element instanceof Buffer) { + buffer = element; + } else if (ArrayBuffer.isView(element)) { + buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength); + } else if (element instanceof ArrayBuffer) { + buffer = Buffer.from(element); + } else if (element instanceof Blob) { + buffer = element[BUFFER]; + } else { + buffer = Buffer.from(typeof element === 'string' ? element : String(element)); + } + size += buffer.length; + buffers.push(buffer); + } + } + + this[BUFFER] = Buffer.concat(buffers); + + let type = options && options.type !== undefined && String(options.type).toLowerCase(); + if (type && !/[^\u0020-\u007E]/.test(type)) { + this[TYPE] = type; + } + } + get size() { + return this[BUFFER].length; + } + get type() { + return this[TYPE]; + } + text() { + return Promise.resolve(this[BUFFER].toString()); + } + arrayBuffer() { + const buf = this[BUFFER]; + const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + return Promise.resolve(ab); + } + stream() { + const readable = new Readable(); + readable._read = function () {}; + readable.push(this[BUFFER]); + readable.push(null); + return readable; + } + toString() { + return '[object Blob]'; + } + slice() { + const size = this.size; + + const start = arguments[0]; + const end = arguments[1]; + let relativeStart, relativeEnd; + if (start === undefined) { + relativeStart = 0; + } else if (start < 0) { + relativeStart = Math.max(size + start, 0); + } else { + relativeStart = Math.min(start, size); + } + if (end === undefined) { + relativeEnd = size; + } else if (end < 0) { + relativeEnd = Math.max(size + end, 0); + } else { + relativeEnd = Math.min(end, size); + } + const span = Math.max(relativeEnd - relativeStart, 0); + + const buffer = this[BUFFER]; + const slicedBuffer = buffer.slice(relativeStart, relativeStart + span); + const blob = new Blob([], { type: arguments[2] }); + blob[BUFFER] = slicedBuffer; + return blob; + } +} + +Object.defineProperties(Blob.prototype, { + size: { enumerable: true }, + type: { enumerable: true }, + slice: { enumerable: true } +}); + +Object.defineProperty(Blob.prototype, Symbol.toStringTag, { + value: 'Blob', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * fetch-error.js + * + * FetchError interface for operational errors + */ + +/** + * Create FetchError instance + * + * @param String message Error message for human + * @param String type Error type for machine + * @param String systemError For Node.js system error + * @return FetchError + */ +function FetchError(message, type, systemError) { + Error.call(this, message); + + this.message = message; + this.type = type; + + // when err.type is `system`, err.code contains system error code + if (systemError) { + this.code = this.errno = systemError.code; + } + + // hide custom error implementation details from end-users + Error.captureStackTrace(this, this.constructor); +} + +FetchError.prototype = Object.create(Error.prototype); +FetchError.prototype.constructor = FetchError; +FetchError.prototype.name = 'FetchError'; + +let convert; +try { + convert = require('encoding').convert; +} catch (e) {} + +const INTERNALS = Symbol('Body internals'); + +// fix an issue where "PassThrough" isn't a named export for node <10 +const PassThrough = Stream.PassThrough; + +/** + * Body mixin + * + * Ref: https://fetch.spec.whatwg.org/#body + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +function Body(body) { + var _this = this; + + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref$size = _ref.size; + + let size = _ref$size === undefined ? 0 : _ref$size; + var _ref$timeout = _ref.timeout; + let timeout = _ref$timeout === undefined ? 0 : _ref$timeout; + + if (body == null) { + // body is undefined or null + body = null; + } else if (isURLSearchParams(body)) { + // body is a URLSearchParams + body = Buffer.from(body.toString()); + } else if (isBlob(body)) ; else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { + // body is ArrayBuffer + body = Buffer.from(body); + } else if (ArrayBuffer.isView(body)) { + // body is ArrayBufferView + body = Buffer.from(body.buffer, body.byteOffset, body.byteLength); + } else if (body instanceof Stream) ; else { + // none of the above + // coerce to string then buffer + body = Buffer.from(String(body)); + } + this[INTERNALS] = { + body, + disturbed: false, + error: null + }; + this.size = size; + this.timeout = timeout; + + if (body instanceof Stream) { + body.on('error', function (err) { + const error = err.name === 'AbortError' ? err : new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err); + _this[INTERNALS].error = error; + }); + } +} + +Body.prototype = { + get body() { + return this[INTERNALS].body; + }, + + get bodyUsed() { + return this[INTERNALS].disturbed; + }, + + /** + * Decode response as ArrayBuffer + * + * @return Promise + */ + arrayBuffer() { + return consumeBody.call(this).then(function (buf) { + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + }); + }, + + /** + * Return raw response as Blob + * + * @return Promise + */ + blob() { + let ct = this.headers && this.headers.get('content-type') || ''; + return consumeBody.call(this).then(function (buf) { + return Object.assign( + // Prevent copying + new Blob([], { + type: ct.toLowerCase() + }), { + [BUFFER]: buf + }); + }); + }, + + /** + * Decode response as json + * + * @return Promise + */ + json() { + var _this2 = this; + + return consumeBody.call(this).then(function (buffer) { + try { + return JSON.parse(buffer.toString()); + } catch (err) { + return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json')); + } + }); + }, + + /** + * Decode response as text + * + * @return Promise + */ + text() { + return consumeBody.call(this).then(function (buffer) { + return buffer.toString(); + }); + }, + + /** + * Decode response as buffer (non-spec api) + * + * @return Promise + */ + buffer() { + return consumeBody.call(this); + }, + + /** + * Decode response as text, while automatically detecting the encoding and + * trying to decode to UTF-8 (non-spec api) + * + * @return Promise + */ + textConverted() { + var _this3 = this; + + return consumeBody.call(this).then(function (buffer) { + return convertBody(buffer, _this3.headers); + }); + } +}; + +// In browsers, all properties are enumerable. +Object.defineProperties(Body.prototype, { + body: { enumerable: true }, + bodyUsed: { enumerable: true }, + arrayBuffer: { enumerable: true }, + blob: { enumerable: true }, + json: { enumerable: true }, + text: { enumerable: true } +}); + +Body.mixIn = function (proto) { + for (const name of Object.getOwnPropertyNames(Body.prototype)) { + // istanbul ignore else: future proof + if (!(name in proto)) { + const desc = Object.getOwnPropertyDescriptor(Body.prototype, name); + Object.defineProperty(proto, name, desc); + } + } +}; + +/** + * Consume and convert an entire Body to a Buffer. + * + * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body + * + * @return Promise + */ +function consumeBody() { + var _this4 = this; + + if (this[INTERNALS].disturbed) { + return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`)); + } + + this[INTERNALS].disturbed = true; + + if (this[INTERNALS].error) { + return Body.Promise.reject(this[INTERNALS].error); + } + + let body = this.body; + + // body is null + if (body === null) { + return Body.Promise.resolve(Buffer.alloc(0)); + } + + // body is blob + if (isBlob(body)) { + body = body.stream(); + } + + // body is buffer + if (Buffer.isBuffer(body)) { + return Body.Promise.resolve(body); + } + + // istanbul ignore if: should never happen + if (!(body instanceof Stream)) { + return Body.Promise.resolve(Buffer.alloc(0)); + } + + // body is stream + // get ready to actually consume the body + let accum = []; + let accumBytes = 0; + let abort = false; + + return new Body.Promise(function (resolve, reject) { + let resTimeout; + + // allow timeout on slow response body + if (_this4.timeout) { + resTimeout = setTimeout(function () { + abort = true; + reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, 'body-timeout')); + }, _this4.timeout); + } + + // handle stream errors + body.on('error', function (err) { + if (err.name === 'AbortError') { + // if the request was aborted, reject with this Error + abort = true; + reject(err); + } else { + // other errors, such as incorrect content-encoding + reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, 'system', err)); + } + }); + + body.on('data', function (chunk) { + if (abort || chunk === null) { + return; + } + + if (_this4.size && accumBytes + chunk.length > _this4.size) { + abort = true; + reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, 'max-size')); + return; + } + + accumBytes += chunk.length; + accum.push(chunk); + }); + + body.on('end', function () { + if (abort) { + return; + } + + clearTimeout(resTimeout); + + try { + resolve(Buffer.concat(accum, accumBytes)); + } catch (err) { + // handle streams that have accumulated too much data (issue #414) + reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, 'system', err)); + } + }); + }); +} + +/** + * Detect buffer encoding and convert to target encoding + * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding + * + * @param Buffer buffer Incoming buffer + * @param String encoding Target encoding + * @return String + */ +function convertBody(buffer, headers) { + if (typeof convert !== 'function') { + throw new Error('The package `encoding` must be installed to use the textConverted() function'); + } + + const ct = headers.get('content-type'); + let charset = 'utf-8'; + let res, str; + + // header + if (ct) { + res = /charset=([^;]*)/i.exec(ct); + } + + // no charset in content type, peek at response body for at most 1024 bytes + str = buffer.slice(0, 1024).toString(); + + // html5 + if (!res && str) { + res = / 0 && arguments[0] !== undefined ? arguments[0] : undefined; + + this[MAP] = Object.create(null); + + if (init instanceof Headers) { + const rawHeaders = init.raw(); + const headerNames = Object.keys(rawHeaders); + + for (const headerName of headerNames) { + for (const value of rawHeaders[headerName]) { + this.append(headerName, value); + } + } + + return; + } + + // We don't worry about converting prop to ByteString here as append() + // will handle it. + if (init == null) ; else if (typeof init === 'object') { + const method = init[Symbol.iterator]; + if (method != null) { + if (typeof method !== 'function') { + throw new TypeError('Header pairs must be iterable'); + } + + // sequence> + // Note: per spec we have to first exhaust the lists then process them + const pairs = []; + for (const pair of init) { + if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { + throw new TypeError('Each header pair must be iterable'); + } + pairs.push(Array.from(pair)); + } + + for (const pair of pairs) { + if (pair.length !== 2) { + throw new TypeError('Each header pair must be a name/value tuple'); + } + this.append(pair[0], pair[1]); + } + } else { + // record + for (const key of Object.keys(init)) { + const value = init[key]; + this.append(key, value); + } + } + } else { + throw new TypeError('Provided initializer must be an object'); + } + } + + /** + * Return combined header value given name + * + * @param String name Header name + * @return Mixed + */ + get(name) { + name = `${name}`; + validateName(name); + const key = find(this[MAP], name); + if (key === undefined) { + return null; + } + + return this[MAP][key].join(', '); + } + + /** + * Iterate over all headers + * + * @param Function callback Executed for each item with parameters (value, name, thisArg) + * @param Boolean thisArg `this` context for callback function + * @return Void + */ + forEach(callback) { + let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; + + let pairs = getHeaders(this); + let i = 0; + while (i < pairs.length) { + var _pairs$i = pairs[i]; + const name = _pairs$i[0], + value = _pairs$i[1]; + + callback.call(thisArg, value, name, this); + pairs = getHeaders(this); + i++; + } + } + + /** + * Overwrite header values given name + * + * @param String name Header name + * @param String value Header value + * @return Void + */ + set(name, value) { + name = `${name}`; + value = `${value}`; + validateName(name); + validateValue(value); + const key = find(this[MAP], name); + this[MAP][key !== undefined ? key : name] = [value]; + } + + /** + * Append a value onto existing header + * + * @param String name Header name + * @param String value Header value + * @return Void + */ + append(name, value) { + name = `${name}`; + value = `${value}`; + validateName(name); + validateValue(value); + const key = find(this[MAP], name); + if (key !== undefined) { + this[MAP][key].push(value); + } else { + this[MAP][name] = [value]; + } + } + + /** + * Check for header name existence + * + * @param String name Header name + * @return Boolean + */ + has(name) { + name = `${name}`; + validateName(name); + return find(this[MAP], name) !== undefined; + } + + /** + * Delete all header values given name + * + * @param String name Header name + * @return Void + */ + delete(name) { + name = `${name}`; + validateName(name); + const key = find(this[MAP], name); + if (key !== undefined) { + delete this[MAP][key]; + } + } + + /** + * Return raw headers (non-spec api) + * + * @return Object + */ + raw() { + return this[MAP]; + } + + /** + * Get an iterator on keys. + * + * @return Iterator + */ + keys() { + return createHeadersIterator(this, 'key'); + } + + /** + * Get an iterator on values. + * + * @return Iterator + */ + values() { + return createHeadersIterator(this, 'value'); + } + + /** + * Get an iterator on entries. + * + * This is the default iterator of the Headers object. + * + * @return Iterator + */ + [Symbol.iterator]() { + return createHeadersIterator(this, 'key+value'); + } +} +Headers.prototype.entries = Headers.prototype[Symbol.iterator]; + +Object.defineProperty(Headers.prototype, Symbol.toStringTag, { + value: 'Headers', + writable: false, + enumerable: false, + configurable: true +}); + +Object.defineProperties(Headers.prototype, { + get: { enumerable: true }, + forEach: { enumerable: true }, + set: { enumerable: true }, + append: { enumerable: true }, + has: { enumerable: true }, + delete: { enumerable: true }, + keys: { enumerable: true }, + values: { enumerable: true }, + entries: { enumerable: true } +}); + +function getHeaders(headers) { + let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value'; + + const keys = Object.keys(headers[MAP]).sort(); + return keys.map(kind === 'key' ? function (k) { + return k.toLowerCase(); + } : kind === 'value' ? function (k) { + return headers[MAP][k].join(', '); + } : function (k) { + return [k.toLowerCase(), headers[MAP][k].join(', ')]; + }); +} + +const INTERNAL = Symbol('internal'); + +function createHeadersIterator(target, kind) { + const iterator = Object.create(HeadersIteratorPrototype); + iterator[INTERNAL] = { + target, + kind, + index: 0 + }; + return iterator; +} + +const HeadersIteratorPrototype = Object.setPrototypeOf({ + next() { + // istanbul ignore if + if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) { + throw new TypeError('Value of `this` is not a HeadersIterator'); + } + + var _INTERNAL = this[INTERNAL]; + const target = _INTERNAL.target, + kind = _INTERNAL.kind, + index = _INTERNAL.index; + + const values = getHeaders(target, kind); + const len = values.length; + if (index >= len) { + return { + value: undefined, + done: true + }; + } + + this[INTERNAL].index = index + 1; + + return { + value: values[index], + done: false + }; + } +}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))); + +Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, { + value: 'HeadersIterator', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * Export the Headers object in a form that Node.js can consume. + * + * @param Headers headers + * @return Object + */ +function exportNodeCompatibleHeaders(headers) { + const obj = Object.assign({ __proto__: null }, headers[MAP]); + + // http.request() only supports string as Host header. This hack makes + // specifying custom Host header possible. + const hostHeaderKey = find(headers[MAP], 'Host'); + if (hostHeaderKey !== undefined) { + obj[hostHeaderKey] = obj[hostHeaderKey][0]; + } + + return obj; +} + +/** + * Create a Headers object from an object of headers, ignoring those that do + * not conform to HTTP grammar productions. + * + * @param Object obj Object of headers + * @return Headers + */ +function createHeadersLenient(obj) { + const headers = new Headers(); + for (const name of Object.keys(obj)) { + if (invalidTokenRegex.test(name)) { + continue; + } + if (Array.isArray(obj[name])) { + for (const val of obj[name]) { + if (invalidHeaderCharRegex.test(val)) { + continue; + } + if (headers[MAP][name] === undefined) { + headers[MAP][name] = [val]; + } else { + headers[MAP][name].push(val); + } + } + } else if (!invalidHeaderCharRegex.test(obj[name])) { + headers[MAP][name] = [obj[name]]; + } + } + return headers; +} + +const INTERNALS$1 = Symbol('Response internals'); + +// fix an issue where "STATUS_CODES" aren't a named export for node <10 +const STATUS_CODES = http.STATUS_CODES; + +/** + * Response class + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +class Response { + constructor() { + let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + Body.call(this, body, opts); + + const status = opts.status || 200; + const headers = new Headers(opts.headers); + + if (body != null && !headers.has('Content-Type')) { + const contentType = extractContentType(body); + if (contentType) { + headers.append('Content-Type', contentType); + } + } + + this[INTERNALS$1] = { + url: opts.url, + status, + statusText: opts.statusText || STATUS_CODES[status], + headers, + counter: opts.counter + }; + } + + get url() { + return this[INTERNALS$1].url; + } + + get status() { + return this[INTERNALS$1].status; + } + + /** + * Convenience property representing if the request ended normally + */ + get ok() { + return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300; + } + + get redirected() { + return this[INTERNALS$1].counter > 0; + } + + get statusText() { + return this[INTERNALS$1].statusText; + } + + get headers() { + return this[INTERNALS$1].headers; + } + + /** + * Clone this response + * + * @return Response + */ + clone() { + return new Response(clone(this), { + url: this.url, + status: this.status, + statusText: this.statusText, + headers: this.headers, + ok: this.ok, + redirected: this.redirected + }); + } +} + +Body.mixIn(Response.prototype); + +Object.defineProperties(Response.prototype, { + url: { enumerable: true }, + status: { enumerable: true }, + ok: { enumerable: true }, + redirected: { enumerable: true }, + statusText: { enumerable: true }, + headers: { enumerable: true }, + clone: { enumerable: true } +}); + +Object.defineProperty(Response.prototype, Symbol.toStringTag, { + value: 'Response', + writable: false, + enumerable: false, + configurable: true +}); + +const INTERNALS$2 = Symbol('Request internals'); + +// fix an issue where "format", "parse" aren't a named export for node <10 +const parse_url = Url.parse; +const format_url = Url.format; + +const streamDestructionSupported = 'destroy' in Stream.Readable.prototype; + +/** + * Check if a value is an instance of Request. + * + * @param Mixed input + * @return Boolean + */ +function isRequest(input) { + return typeof input === 'object' && typeof input[INTERNALS$2] === 'object'; +} + +function isAbortSignal(signal) { + const proto = signal && typeof signal === 'object' && Object.getPrototypeOf(signal); + return !!(proto && proto.constructor.name === 'AbortSignal'); +} + +/** + * Request class + * + * @param Mixed input Url or Request instance + * @param Object init Custom options + * @return Void + */ +class Request { + constructor(input) { + let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + let parsedURL; + + // normalize input + if (!isRequest(input)) { + if (input && input.href) { + // in order to support Node.js' Url objects; though WHATWG's URL objects + // will fall into this branch also (since their `toString()` will return + // `href` property anyway) + parsedURL = parse_url(input.href); + } else { + // coerce input to a string before attempting to parse + parsedURL = parse_url(`${input}`); + } + input = {}; + } else { + parsedURL = parse_url(input.url); + } + + let method = init.method || input.method || 'GET'; + method = method.toUpperCase(); + + if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) { + throw new TypeError('Request with GET/HEAD method cannot have body'); + } + + let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null; + + Body.call(this, inputBody, { + timeout: init.timeout || input.timeout || 0, + size: init.size || input.size || 0 + }); + + const headers = new Headers(init.headers || input.headers || {}); + + if (inputBody != null && !headers.has('Content-Type')) { + const contentType = extractContentType(inputBody); + if (contentType) { + headers.append('Content-Type', contentType); + } + } + + let signal = isRequest(input) ? input.signal : null; + if ('signal' in init) signal = init.signal; + + if (signal != null && !isAbortSignal(signal)) { + throw new TypeError('Expected signal to be an instanceof AbortSignal'); + } + + this[INTERNALS$2] = { + method, + redirect: init.redirect || input.redirect || 'follow', + headers, + parsedURL, + signal + }; + + // node-fetch-only options + this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20; + this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true; + this.counter = init.counter || input.counter || 0; + this.agent = init.agent || input.agent; + } + + get method() { + return this[INTERNALS$2].method; + } + + get url() { + return format_url(this[INTERNALS$2].parsedURL); + } + + get headers() { + return this[INTERNALS$2].headers; + } + + get redirect() { + return this[INTERNALS$2].redirect; + } + + get signal() { + return this[INTERNALS$2].signal; + } + + /** + * Clone this request + * + * @return Request + */ + clone() { + return new Request(this); + } +} + +Body.mixIn(Request.prototype); + +Object.defineProperty(Request.prototype, Symbol.toStringTag, { + value: 'Request', + writable: false, + enumerable: false, + configurable: true +}); + +Object.defineProperties(Request.prototype, { + method: { enumerable: true }, + url: { enumerable: true }, + headers: { enumerable: true }, + redirect: { enumerable: true }, + clone: { enumerable: true }, + signal: { enumerable: true } +}); + +/** + * Convert a Request to Node.js http request options. + * + * @param Request A Request instance + * @return Object The options object to be passed to http.request + */ +function getNodeRequestOptions(request) { + const parsedURL = request[INTERNALS$2].parsedURL; + const headers = new Headers(request[INTERNALS$2].headers); + + // fetch step 1.3 + if (!headers.has('Accept')) { + headers.set('Accept', '*/*'); + } + + // Basic fetch + if (!parsedURL.protocol || !parsedURL.hostname) { + throw new TypeError('Only absolute URLs are supported'); + } + + if (!/^https?:$/.test(parsedURL.protocol)) { + throw new TypeError('Only HTTP(S) protocols are supported'); + } + + if (request.signal && request.body instanceof Stream.Readable && !streamDestructionSupported) { + throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8'); + } + + // HTTP-network-or-cache fetch steps 2.4-2.7 + let contentLengthValue = null; + if (request.body == null && /^(POST|PUT)$/i.test(request.method)) { + contentLengthValue = '0'; + } + if (request.body != null) { + const totalBytes = getTotalBytes(request); + if (typeof totalBytes === 'number') { + contentLengthValue = String(totalBytes); + } + } + if (contentLengthValue) { + headers.set('Content-Length', contentLengthValue); + } + + // HTTP-network-or-cache fetch step 2.11 + if (!headers.has('User-Agent')) { + headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)'); + } + + // HTTP-network-or-cache fetch step 2.15 + if (request.compress && !headers.has('Accept-Encoding')) { + headers.set('Accept-Encoding', 'gzip,deflate'); + } + + if (!headers.has('Connection') && !request.agent) { + headers.set('Connection', 'close'); + } + + // HTTP-network fetch step 4.2 + // chunked encoding is handled by Node.js + + return Object.assign({}, parsedURL, { + method: request.method, + headers: exportNodeCompatibleHeaders(headers), + agent: request.agent + }); +} + +/** + * abort-error.js + * + * AbortError interface for cancelled requests + */ + +/** + * Create AbortError instance + * + * @param String message Error message for human + * @return AbortError + */ +function AbortError(message) { + Error.call(this, message); + + this.type = 'aborted'; + this.message = message; + + // hide custom error implementation details from end-users + Error.captureStackTrace(this, this.constructor); +} + +AbortError.prototype = Object.create(Error.prototype); +AbortError.prototype.constructor = AbortError; +AbortError.prototype.name = 'AbortError'; + +// fix an issue where "PassThrough", "resolve" aren't a named export for node <10 +const PassThrough$1 = Stream.PassThrough; +const resolve_url = Url.resolve; + +/** + * Fetch function + * + * @param Mixed url Absolute url or Request instance + * @param Object opts Fetch options + * @return Promise + */ +function fetch(url, opts) { + + // allow custom promise + if (!fetch.Promise) { + throw new Error('native promise missing, set fetch.Promise to your favorite alternative'); + } + + Body.Promise = fetch.Promise; + + // wrap http.request into fetch + return new fetch.Promise(function (resolve, reject) { + // build request object + const request = new Request(url, opts); + const options = getNodeRequestOptions(request); + + const send = (options.protocol === 'https:' ? https : http).request; + const signal = request.signal; + + let response = null; + + const abort = function abort() { + let error = new AbortError('The user aborted a request.'); + reject(error); + if (request.body && request.body instanceof Stream.Readable) { + request.body.destroy(error); + } + if (!response || !response.body) return; + response.body.emit('error', error); + }; + + if (signal && signal.aborted) { + abort(); + return; + } + + const abortAndFinalize = function abortAndFinalize() { + abort(); + finalize(); + }; + + // send request + const req = send(options); + let reqTimeout; + + if (signal) { + signal.addEventListener('abort', abortAndFinalize); + } + + function finalize() { + req.abort(); + if (signal) signal.removeEventListener('abort', abortAndFinalize); + clearTimeout(reqTimeout); + } + + if (request.timeout) { + req.once('socket', function (socket) { + reqTimeout = setTimeout(function () { + reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); + finalize(); + }, request.timeout); + }); + } + + req.on('error', function (err) { + reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); + finalize(); + }); + + req.on('response', function (res) { + clearTimeout(reqTimeout); + + const headers = createHeadersLenient(res.headers); + + // HTTP fetch step 5 + if (fetch.isRedirect(res.statusCode)) { + // HTTP fetch step 5.2 + const location = headers.get('Location'); + + // HTTP fetch step 5.3 + const locationURL = location === null ? null : resolve_url(request.url, location); + + // HTTP fetch step 5.5 + switch (request.redirect) { + case 'error': + reject(new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect')); + finalize(); + return; + case 'manual': + // node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL. + if (locationURL !== null) { + // handle corrupted header + try { + headers.set('Location', locationURL); + } catch (err) { + // istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request + reject(err); + } + } + break; + case 'follow': + // HTTP-redirect fetch step 2 + if (locationURL === null) { + break; + } + + // HTTP-redirect fetch step 5 + if (request.counter >= request.follow) { + reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); + finalize(); + return; + } + + // HTTP-redirect fetch step 6 (counter increment) + // Create a new Request object. + const requestOpts = { + headers: new Headers(request.headers), + follow: request.follow, + counter: request.counter + 1, + agent: request.agent, + compress: request.compress, + method: request.method, + body: request.body, + signal: request.signal, + timeout: request.timeout + }; + + // HTTP-redirect fetch step 9 + if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) { + reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect')); + finalize(); + return; + } + + // HTTP-redirect fetch step 11 + if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') { + requestOpts.method = 'GET'; + requestOpts.body = undefined; + requestOpts.headers.delete('content-length'); + } + + // HTTP-redirect fetch step 15 + resolve(fetch(new Request(locationURL, requestOpts))); + finalize(); + return; + } + } + + // prepare response + res.once('end', function () { + if (signal) signal.removeEventListener('abort', abortAndFinalize); + }); + let body = res.pipe(new PassThrough$1()); + + const response_options = { + url: request.url, + status: res.statusCode, + statusText: res.statusMessage, + headers: headers, + size: request.size, + timeout: request.timeout, + counter: request.counter + }; + + // HTTP-network fetch step 12.1.1.3 + const codings = headers.get('Content-Encoding'); + + // HTTP-network fetch step 12.1.1.4: handle content codings + + // in following scenarios we ignore compression support + // 1. compression support is disabled + // 2. HEAD request + // 3. no Content-Encoding header + // 4. no content response (204) + // 5. content not modified response (304) + if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) { + response = new Response(body, response_options); + resolve(response); + return; + } + + // For Node v6+ + // Be less strict when decoding compressed responses, since sometimes + // servers send slightly invalid responses that are still accepted + // by common browsers. + // Always using Z_SYNC_FLUSH is what cURL does. + const zlibOptions = { + flush: zlib.Z_SYNC_FLUSH, + finishFlush: zlib.Z_SYNC_FLUSH + }; + + // for gzip + if (codings == 'gzip' || codings == 'x-gzip') { + body = body.pipe(zlib.createGunzip(zlibOptions)); + response = new Response(body, response_options); + resolve(response); + return; + } + + // for deflate + if (codings == 'deflate' || codings == 'x-deflate') { + // handle the infamous raw deflate response from old servers + // a hack for old IIS and Apache servers + const raw = res.pipe(new PassThrough$1()); + raw.once('data', function (chunk) { + // see http://stackoverflow.com/questions/37519828 + if ((chunk[0] & 0x0F) === 0x08) { + body = body.pipe(zlib.createInflate()); + } else { + body = body.pipe(zlib.createInflateRaw()); + } + response = new Response(body, response_options); + resolve(response); + }); + return; + } + + // for br + if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') { + body = body.pipe(zlib.createBrotliDecompress()); + response = new Response(body, response_options); + resolve(response); + return; + } + + // otherwise, use response as-is + response = new Response(body, response_options); + resolve(response); + }); + + writeToStream(req, request); + }); +} +/** + * Redirect code matching + * + * @param Number code Status code + * @return Boolean + */ +fetch.isRedirect = function (code) { + return code === 301 || code === 302 || code === 303 || code === 307 || code === 308; +}; + +// expose Promise +fetch.Promise = global.Promise; + +export default fetch; +export { Headers, Request, Response, FetchError }; diff --git a/scripts/metrics/node_modules/node-fetch/lib/index.js b/scripts/metrics/node_modules/node-fetch/lib/index.js new file mode 100644 index 00000000000..86c7c031229 --- /dev/null +++ b/scripts/metrics/node_modules/node-fetch/lib/index.js @@ -0,0 +1,1640 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } + +var Stream = _interopDefault(require('stream')); +var http = _interopDefault(require('http')); +var Url = _interopDefault(require('url')); +var https = _interopDefault(require('https')); +var zlib = _interopDefault(require('zlib')); + +// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js + +// fix for "Readable" isn't a named export issue +const Readable = Stream.Readable; + +const BUFFER = Symbol('buffer'); +const TYPE = Symbol('type'); + +class Blob { + constructor() { + this[TYPE] = ''; + + const blobParts = arguments[0]; + const options = arguments[1]; + + const buffers = []; + let size = 0; + + if (blobParts) { + const a = blobParts; + const length = Number(a.length); + for (let i = 0; i < length; i++) { + const element = a[i]; + let buffer; + if (element instanceof Buffer) { + buffer = element; + } else if (ArrayBuffer.isView(element)) { + buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength); + } else if (element instanceof ArrayBuffer) { + buffer = Buffer.from(element); + } else if (element instanceof Blob) { + buffer = element[BUFFER]; + } else { + buffer = Buffer.from(typeof element === 'string' ? element : String(element)); + } + size += buffer.length; + buffers.push(buffer); + } + } + + this[BUFFER] = Buffer.concat(buffers); + + let type = options && options.type !== undefined && String(options.type).toLowerCase(); + if (type && !/[^\u0020-\u007E]/.test(type)) { + this[TYPE] = type; + } + } + get size() { + return this[BUFFER].length; + } + get type() { + return this[TYPE]; + } + text() { + return Promise.resolve(this[BUFFER].toString()); + } + arrayBuffer() { + const buf = this[BUFFER]; + const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + return Promise.resolve(ab); + } + stream() { + const readable = new Readable(); + readable._read = function () {}; + readable.push(this[BUFFER]); + readable.push(null); + return readable; + } + toString() { + return '[object Blob]'; + } + slice() { + const size = this.size; + + const start = arguments[0]; + const end = arguments[1]; + let relativeStart, relativeEnd; + if (start === undefined) { + relativeStart = 0; + } else if (start < 0) { + relativeStart = Math.max(size + start, 0); + } else { + relativeStart = Math.min(start, size); + } + if (end === undefined) { + relativeEnd = size; + } else if (end < 0) { + relativeEnd = Math.max(size + end, 0); + } else { + relativeEnd = Math.min(end, size); + } + const span = Math.max(relativeEnd - relativeStart, 0); + + const buffer = this[BUFFER]; + const slicedBuffer = buffer.slice(relativeStart, relativeStart + span); + const blob = new Blob([], { type: arguments[2] }); + blob[BUFFER] = slicedBuffer; + return blob; + } +} + +Object.defineProperties(Blob.prototype, { + size: { enumerable: true }, + type: { enumerable: true }, + slice: { enumerable: true } +}); + +Object.defineProperty(Blob.prototype, Symbol.toStringTag, { + value: 'Blob', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * fetch-error.js + * + * FetchError interface for operational errors + */ + +/** + * Create FetchError instance + * + * @param String message Error message for human + * @param String type Error type for machine + * @param String systemError For Node.js system error + * @return FetchError + */ +function FetchError(message, type, systemError) { + Error.call(this, message); + + this.message = message; + this.type = type; + + // when err.type is `system`, err.code contains system error code + if (systemError) { + this.code = this.errno = systemError.code; + } + + // hide custom error implementation details from end-users + Error.captureStackTrace(this, this.constructor); +} + +FetchError.prototype = Object.create(Error.prototype); +FetchError.prototype.constructor = FetchError; +FetchError.prototype.name = 'FetchError'; + +let convert; +try { + convert = require('encoding').convert; +} catch (e) {} + +const INTERNALS = Symbol('Body internals'); + +// fix an issue where "PassThrough" isn't a named export for node <10 +const PassThrough = Stream.PassThrough; + +/** + * Body mixin + * + * Ref: https://fetch.spec.whatwg.org/#body + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +function Body(body) { + var _this = this; + + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref$size = _ref.size; + + let size = _ref$size === undefined ? 0 : _ref$size; + var _ref$timeout = _ref.timeout; + let timeout = _ref$timeout === undefined ? 0 : _ref$timeout; + + if (body == null) { + // body is undefined or null + body = null; + } else if (isURLSearchParams(body)) { + // body is a URLSearchParams + body = Buffer.from(body.toString()); + } else if (isBlob(body)) ; else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { + // body is ArrayBuffer + body = Buffer.from(body); + } else if (ArrayBuffer.isView(body)) { + // body is ArrayBufferView + body = Buffer.from(body.buffer, body.byteOffset, body.byteLength); + } else if (body instanceof Stream) ; else { + // none of the above + // coerce to string then buffer + body = Buffer.from(String(body)); + } + this[INTERNALS] = { + body, + disturbed: false, + error: null + }; + this.size = size; + this.timeout = timeout; + + if (body instanceof Stream) { + body.on('error', function (err) { + const error = err.name === 'AbortError' ? err : new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err); + _this[INTERNALS].error = error; + }); + } +} + +Body.prototype = { + get body() { + return this[INTERNALS].body; + }, + + get bodyUsed() { + return this[INTERNALS].disturbed; + }, + + /** + * Decode response as ArrayBuffer + * + * @return Promise + */ + arrayBuffer() { + return consumeBody.call(this).then(function (buf) { + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + }); + }, + + /** + * Return raw response as Blob + * + * @return Promise + */ + blob() { + let ct = this.headers && this.headers.get('content-type') || ''; + return consumeBody.call(this).then(function (buf) { + return Object.assign( + // Prevent copying + new Blob([], { + type: ct.toLowerCase() + }), { + [BUFFER]: buf + }); + }); + }, + + /** + * Decode response as json + * + * @return Promise + */ + json() { + var _this2 = this; + + return consumeBody.call(this).then(function (buffer) { + try { + return JSON.parse(buffer.toString()); + } catch (err) { + return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json')); + } + }); + }, + + /** + * Decode response as text + * + * @return Promise + */ + text() { + return consumeBody.call(this).then(function (buffer) { + return buffer.toString(); + }); + }, + + /** + * Decode response as buffer (non-spec api) + * + * @return Promise + */ + buffer() { + return consumeBody.call(this); + }, + + /** + * Decode response as text, while automatically detecting the encoding and + * trying to decode to UTF-8 (non-spec api) + * + * @return Promise + */ + textConverted() { + var _this3 = this; + + return consumeBody.call(this).then(function (buffer) { + return convertBody(buffer, _this3.headers); + }); + } +}; + +// In browsers, all properties are enumerable. +Object.defineProperties(Body.prototype, { + body: { enumerable: true }, + bodyUsed: { enumerable: true }, + arrayBuffer: { enumerable: true }, + blob: { enumerable: true }, + json: { enumerable: true }, + text: { enumerable: true } +}); + +Body.mixIn = function (proto) { + for (const name of Object.getOwnPropertyNames(Body.prototype)) { + // istanbul ignore else: future proof + if (!(name in proto)) { + const desc = Object.getOwnPropertyDescriptor(Body.prototype, name); + Object.defineProperty(proto, name, desc); + } + } +}; + +/** + * Consume and convert an entire Body to a Buffer. + * + * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body + * + * @return Promise + */ +function consumeBody() { + var _this4 = this; + + if (this[INTERNALS].disturbed) { + return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`)); + } + + this[INTERNALS].disturbed = true; + + if (this[INTERNALS].error) { + return Body.Promise.reject(this[INTERNALS].error); + } + + let body = this.body; + + // body is null + if (body === null) { + return Body.Promise.resolve(Buffer.alloc(0)); + } + + // body is blob + if (isBlob(body)) { + body = body.stream(); + } + + // body is buffer + if (Buffer.isBuffer(body)) { + return Body.Promise.resolve(body); + } + + // istanbul ignore if: should never happen + if (!(body instanceof Stream)) { + return Body.Promise.resolve(Buffer.alloc(0)); + } + + // body is stream + // get ready to actually consume the body + let accum = []; + let accumBytes = 0; + let abort = false; + + return new Body.Promise(function (resolve, reject) { + let resTimeout; + + // allow timeout on slow response body + if (_this4.timeout) { + resTimeout = setTimeout(function () { + abort = true; + reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, 'body-timeout')); + }, _this4.timeout); + } + + // handle stream errors + body.on('error', function (err) { + if (err.name === 'AbortError') { + // if the request was aborted, reject with this Error + abort = true; + reject(err); + } else { + // other errors, such as incorrect content-encoding + reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, 'system', err)); + } + }); + + body.on('data', function (chunk) { + if (abort || chunk === null) { + return; + } + + if (_this4.size && accumBytes + chunk.length > _this4.size) { + abort = true; + reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, 'max-size')); + return; + } + + accumBytes += chunk.length; + accum.push(chunk); + }); + + body.on('end', function () { + if (abort) { + return; + } + + clearTimeout(resTimeout); + + try { + resolve(Buffer.concat(accum, accumBytes)); + } catch (err) { + // handle streams that have accumulated too much data (issue #414) + reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, 'system', err)); + } + }); + }); +} + +/** + * Detect buffer encoding and convert to target encoding + * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding + * + * @param Buffer buffer Incoming buffer + * @param String encoding Target encoding + * @return String + */ +function convertBody(buffer, headers) { + if (typeof convert !== 'function') { + throw new Error('The package `encoding` must be installed to use the textConverted() function'); + } + + const ct = headers.get('content-type'); + let charset = 'utf-8'; + let res, str; + + // header + if (ct) { + res = /charset=([^;]*)/i.exec(ct); + } + + // no charset in content type, peek at response body for at most 1024 bytes + str = buffer.slice(0, 1024).toString(); + + // html5 + if (!res && str) { + res = / 0 && arguments[0] !== undefined ? arguments[0] : undefined; + + this[MAP] = Object.create(null); + + if (init instanceof Headers) { + const rawHeaders = init.raw(); + const headerNames = Object.keys(rawHeaders); + + for (const headerName of headerNames) { + for (const value of rawHeaders[headerName]) { + this.append(headerName, value); + } + } + + return; + } + + // We don't worry about converting prop to ByteString here as append() + // will handle it. + if (init == null) ; else if (typeof init === 'object') { + const method = init[Symbol.iterator]; + if (method != null) { + if (typeof method !== 'function') { + throw new TypeError('Header pairs must be iterable'); + } + + // sequence> + // Note: per spec we have to first exhaust the lists then process them + const pairs = []; + for (const pair of init) { + if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { + throw new TypeError('Each header pair must be iterable'); + } + pairs.push(Array.from(pair)); + } + + for (const pair of pairs) { + if (pair.length !== 2) { + throw new TypeError('Each header pair must be a name/value tuple'); + } + this.append(pair[0], pair[1]); + } + } else { + // record + for (const key of Object.keys(init)) { + const value = init[key]; + this.append(key, value); + } + } + } else { + throw new TypeError('Provided initializer must be an object'); + } + } + + /** + * Return combined header value given name + * + * @param String name Header name + * @return Mixed + */ + get(name) { + name = `${name}`; + validateName(name); + const key = find(this[MAP], name); + if (key === undefined) { + return null; + } + + return this[MAP][key].join(', '); + } + + /** + * Iterate over all headers + * + * @param Function callback Executed for each item with parameters (value, name, thisArg) + * @param Boolean thisArg `this` context for callback function + * @return Void + */ + forEach(callback) { + let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; + + let pairs = getHeaders(this); + let i = 0; + while (i < pairs.length) { + var _pairs$i = pairs[i]; + const name = _pairs$i[0], + value = _pairs$i[1]; + + callback.call(thisArg, value, name, this); + pairs = getHeaders(this); + i++; + } + } + + /** + * Overwrite header values given name + * + * @param String name Header name + * @param String value Header value + * @return Void + */ + set(name, value) { + name = `${name}`; + value = `${value}`; + validateName(name); + validateValue(value); + const key = find(this[MAP], name); + this[MAP][key !== undefined ? key : name] = [value]; + } + + /** + * Append a value onto existing header + * + * @param String name Header name + * @param String value Header value + * @return Void + */ + append(name, value) { + name = `${name}`; + value = `${value}`; + validateName(name); + validateValue(value); + const key = find(this[MAP], name); + if (key !== undefined) { + this[MAP][key].push(value); + } else { + this[MAP][name] = [value]; + } + } + + /** + * Check for header name existence + * + * @param String name Header name + * @return Boolean + */ + has(name) { + name = `${name}`; + validateName(name); + return find(this[MAP], name) !== undefined; + } + + /** + * Delete all header values given name + * + * @param String name Header name + * @return Void + */ + delete(name) { + name = `${name}`; + validateName(name); + const key = find(this[MAP], name); + if (key !== undefined) { + delete this[MAP][key]; + } + } + + /** + * Return raw headers (non-spec api) + * + * @return Object + */ + raw() { + return this[MAP]; + } + + /** + * Get an iterator on keys. + * + * @return Iterator + */ + keys() { + return createHeadersIterator(this, 'key'); + } + + /** + * Get an iterator on values. + * + * @return Iterator + */ + values() { + return createHeadersIterator(this, 'value'); + } + + /** + * Get an iterator on entries. + * + * This is the default iterator of the Headers object. + * + * @return Iterator + */ + [Symbol.iterator]() { + return createHeadersIterator(this, 'key+value'); + } +} +Headers.prototype.entries = Headers.prototype[Symbol.iterator]; + +Object.defineProperty(Headers.prototype, Symbol.toStringTag, { + value: 'Headers', + writable: false, + enumerable: false, + configurable: true +}); + +Object.defineProperties(Headers.prototype, { + get: { enumerable: true }, + forEach: { enumerable: true }, + set: { enumerable: true }, + append: { enumerable: true }, + has: { enumerable: true }, + delete: { enumerable: true }, + keys: { enumerable: true }, + values: { enumerable: true }, + entries: { enumerable: true } +}); + +function getHeaders(headers) { + let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value'; + + const keys = Object.keys(headers[MAP]).sort(); + return keys.map(kind === 'key' ? function (k) { + return k.toLowerCase(); + } : kind === 'value' ? function (k) { + return headers[MAP][k].join(', '); + } : function (k) { + return [k.toLowerCase(), headers[MAP][k].join(', ')]; + }); +} + +const INTERNAL = Symbol('internal'); + +function createHeadersIterator(target, kind) { + const iterator = Object.create(HeadersIteratorPrototype); + iterator[INTERNAL] = { + target, + kind, + index: 0 + }; + return iterator; +} + +const HeadersIteratorPrototype = Object.setPrototypeOf({ + next() { + // istanbul ignore if + if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) { + throw new TypeError('Value of `this` is not a HeadersIterator'); + } + + var _INTERNAL = this[INTERNAL]; + const target = _INTERNAL.target, + kind = _INTERNAL.kind, + index = _INTERNAL.index; + + const values = getHeaders(target, kind); + const len = values.length; + if (index >= len) { + return { + value: undefined, + done: true + }; + } + + this[INTERNAL].index = index + 1; + + return { + value: values[index], + done: false + }; + } +}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))); + +Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, { + value: 'HeadersIterator', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * Export the Headers object in a form that Node.js can consume. + * + * @param Headers headers + * @return Object + */ +function exportNodeCompatibleHeaders(headers) { + const obj = Object.assign({ __proto__: null }, headers[MAP]); + + // http.request() only supports string as Host header. This hack makes + // specifying custom Host header possible. + const hostHeaderKey = find(headers[MAP], 'Host'); + if (hostHeaderKey !== undefined) { + obj[hostHeaderKey] = obj[hostHeaderKey][0]; + } + + return obj; +} + +/** + * Create a Headers object from an object of headers, ignoring those that do + * not conform to HTTP grammar productions. + * + * @param Object obj Object of headers + * @return Headers + */ +function createHeadersLenient(obj) { + const headers = new Headers(); + for (const name of Object.keys(obj)) { + if (invalidTokenRegex.test(name)) { + continue; + } + if (Array.isArray(obj[name])) { + for (const val of obj[name]) { + if (invalidHeaderCharRegex.test(val)) { + continue; + } + if (headers[MAP][name] === undefined) { + headers[MAP][name] = [val]; + } else { + headers[MAP][name].push(val); + } + } + } else if (!invalidHeaderCharRegex.test(obj[name])) { + headers[MAP][name] = [obj[name]]; + } + } + return headers; +} + +const INTERNALS$1 = Symbol('Response internals'); + +// fix an issue where "STATUS_CODES" aren't a named export for node <10 +const STATUS_CODES = http.STATUS_CODES; + +/** + * Response class + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +class Response { + constructor() { + let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + Body.call(this, body, opts); + + const status = opts.status || 200; + const headers = new Headers(opts.headers); + + if (body != null && !headers.has('Content-Type')) { + const contentType = extractContentType(body); + if (contentType) { + headers.append('Content-Type', contentType); + } + } + + this[INTERNALS$1] = { + url: opts.url, + status, + statusText: opts.statusText || STATUS_CODES[status], + headers, + counter: opts.counter + }; + } + + get url() { + return this[INTERNALS$1].url; + } + + get status() { + return this[INTERNALS$1].status; + } + + /** + * Convenience property representing if the request ended normally + */ + get ok() { + return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300; + } + + get redirected() { + return this[INTERNALS$1].counter > 0; + } + + get statusText() { + return this[INTERNALS$1].statusText; + } + + get headers() { + return this[INTERNALS$1].headers; + } + + /** + * Clone this response + * + * @return Response + */ + clone() { + return new Response(clone(this), { + url: this.url, + status: this.status, + statusText: this.statusText, + headers: this.headers, + ok: this.ok, + redirected: this.redirected + }); + } +} + +Body.mixIn(Response.prototype); + +Object.defineProperties(Response.prototype, { + url: { enumerable: true }, + status: { enumerable: true }, + ok: { enumerable: true }, + redirected: { enumerable: true }, + statusText: { enumerable: true }, + headers: { enumerable: true }, + clone: { enumerable: true } +}); + +Object.defineProperty(Response.prototype, Symbol.toStringTag, { + value: 'Response', + writable: false, + enumerable: false, + configurable: true +}); + +const INTERNALS$2 = Symbol('Request internals'); + +// fix an issue where "format", "parse" aren't a named export for node <10 +const parse_url = Url.parse; +const format_url = Url.format; + +const streamDestructionSupported = 'destroy' in Stream.Readable.prototype; + +/** + * Check if a value is an instance of Request. + * + * @param Mixed input + * @return Boolean + */ +function isRequest(input) { + return typeof input === 'object' && typeof input[INTERNALS$2] === 'object'; +} + +function isAbortSignal(signal) { + const proto = signal && typeof signal === 'object' && Object.getPrototypeOf(signal); + return !!(proto && proto.constructor.name === 'AbortSignal'); +} + +/** + * Request class + * + * @param Mixed input Url or Request instance + * @param Object init Custom options + * @return Void + */ +class Request { + constructor(input) { + let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + let parsedURL; + + // normalize input + if (!isRequest(input)) { + if (input && input.href) { + // in order to support Node.js' Url objects; though WHATWG's URL objects + // will fall into this branch also (since their `toString()` will return + // `href` property anyway) + parsedURL = parse_url(input.href); + } else { + // coerce input to a string before attempting to parse + parsedURL = parse_url(`${input}`); + } + input = {}; + } else { + parsedURL = parse_url(input.url); + } + + let method = init.method || input.method || 'GET'; + method = method.toUpperCase(); + + if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) { + throw new TypeError('Request with GET/HEAD method cannot have body'); + } + + let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null; + + Body.call(this, inputBody, { + timeout: init.timeout || input.timeout || 0, + size: init.size || input.size || 0 + }); + + const headers = new Headers(init.headers || input.headers || {}); + + if (inputBody != null && !headers.has('Content-Type')) { + const contentType = extractContentType(inputBody); + if (contentType) { + headers.append('Content-Type', contentType); + } + } + + let signal = isRequest(input) ? input.signal : null; + if ('signal' in init) signal = init.signal; + + if (signal != null && !isAbortSignal(signal)) { + throw new TypeError('Expected signal to be an instanceof AbortSignal'); + } + + this[INTERNALS$2] = { + method, + redirect: init.redirect || input.redirect || 'follow', + headers, + parsedURL, + signal + }; + + // node-fetch-only options + this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20; + this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true; + this.counter = init.counter || input.counter || 0; + this.agent = init.agent || input.agent; + } + + get method() { + return this[INTERNALS$2].method; + } + + get url() { + return format_url(this[INTERNALS$2].parsedURL); + } + + get headers() { + return this[INTERNALS$2].headers; + } + + get redirect() { + return this[INTERNALS$2].redirect; + } + + get signal() { + return this[INTERNALS$2].signal; + } + + /** + * Clone this request + * + * @return Request + */ + clone() { + return new Request(this); + } +} + +Body.mixIn(Request.prototype); + +Object.defineProperty(Request.prototype, Symbol.toStringTag, { + value: 'Request', + writable: false, + enumerable: false, + configurable: true +}); + +Object.defineProperties(Request.prototype, { + method: { enumerable: true }, + url: { enumerable: true }, + headers: { enumerable: true }, + redirect: { enumerable: true }, + clone: { enumerable: true }, + signal: { enumerable: true } +}); + +/** + * Convert a Request to Node.js http request options. + * + * @param Request A Request instance + * @return Object The options object to be passed to http.request + */ +function getNodeRequestOptions(request) { + const parsedURL = request[INTERNALS$2].parsedURL; + const headers = new Headers(request[INTERNALS$2].headers); + + // fetch step 1.3 + if (!headers.has('Accept')) { + headers.set('Accept', '*/*'); + } + + // Basic fetch + if (!parsedURL.protocol || !parsedURL.hostname) { + throw new TypeError('Only absolute URLs are supported'); + } + + if (!/^https?:$/.test(parsedURL.protocol)) { + throw new TypeError('Only HTTP(S) protocols are supported'); + } + + if (request.signal && request.body instanceof Stream.Readable && !streamDestructionSupported) { + throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8'); + } + + // HTTP-network-or-cache fetch steps 2.4-2.7 + let contentLengthValue = null; + if (request.body == null && /^(POST|PUT)$/i.test(request.method)) { + contentLengthValue = '0'; + } + if (request.body != null) { + const totalBytes = getTotalBytes(request); + if (typeof totalBytes === 'number') { + contentLengthValue = String(totalBytes); + } + } + if (contentLengthValue) { + headers.set('Content-Length', contentLengthValue); + } + + // HTTP-network-or-cache fetch step 2.11 + if (!headers.has('User-Agent')) { + headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)'); + } + + // HTTP-network-or-cache fetch step 2.15 + if (request.compress && !headers.has('Accept-Encoding')) { + headers.set('Accept-Encoding', 'gzip,deflate'); + } + + if (!headers.has('Connection') && !request.agent) { + headers.set('Connection', 'close'); + } + + // HTTP-network fetch step 4.2 + // chunked encoding is handled by Node.js + + return Object.assign({}, parsedURL, { + method: request.method, + headers: exportNodeCompatibleHeaders(headers), + agent: request.agent + }); +} + +/** + * abort-error.js + * + * AbortError interface for cancelled requests + */ + +/** + * Create AbortError instance + * + * @param String message Error message for human + * @return AbortError + */ +function AbortError(message) { + Error.call(this, message); + + this.type = 'aborted'; + this.message = message; + + // hide custom error implementation details from end-users + Error.captureStackTrace(this, this.constructor); +} + +AbortError.prototype = Object.create(Error.prototype); +AbortError.prototype.constructor = AbortError; +AbortError.prototype.name = 'AbortError'; + +// fix an issue where "PassThrough", "resolve" aren't a named export for node <10 +const PassThrough$1 = Stream.PassThrough; +const resolve_url = Url.resolve; + +/** + * Fetch function + * + * @param Mixed url Absolute url or Request instance + * @param Object opts Fetch options + * @return Promise + */ +function fetch(url, opts) { + + // allow custom promise + if (!fetch.Promise) { + throw new Error('native promise missing, set fetch.Promise to your favorite alternative'); + } + + Body.Promise = fetch.Promise; + + // wrap http.request into fetch + return new fetch.Promise(function (resolve, reject) { + // build request object + const request = new Request(url, opts); + const options = getNodeRequestOptions(request); + + const send = (options.protocol === 'https:' ? https : http).request; + const signal = request.signal; + + let response = null; + + const abort = function abort() { + let error = new AbortError('The user aborted a request.'); + reject(error); + if (request.body && request.body instanceof Stream.Readable) { + request.body.destroy(error); + } + if (!response || !response.body) return; + response.body.emit('error', error); + }; + + if (signal && signal.aborted) { + abort(); + return; + } + + const abortAndFinalize = function abortAndFinalize() { + abort(); + finalize(); + }; + + // send request + const req = send(options); + let reqTimeout; + + if (signal) { + signal.addEventListener('abort', abortAndFinalize); + } + + function finalize() { + req.abort(); + if (signal) signal.removeEventListener('abort', abortAndFinalize); + clearTimeout(reqTimeout); + } + + if (request.timeout) { + req.once('socket', function (socket) { + reqTimeout = setTimeout(function () { + reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); + finalize(); + }, request.timeout); + }); + } + + req.on('error', function (err) { + reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); + finalize(); + }); + + req.on('response', function (res) { + clearTimeout(reqTimeout); + + const headers = createHeadersLenient(res.headers); + + // HTTP fetch step 5 + if (fetch.isRedirect(res.statusCode)) { + // HTTP fetch step 5.2 + const location = headers.get('Location'); + + // HTTP fetch step 5.3 + const locationURL = location === null ? null : resolve_url(request.url, location); + + // HTTP fetch step 5.5 + switch (request.redirect) { + case 'error': + reject(new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect')); + finalize(); + return; + case 'manual': + // node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL. + if (locationURL !== null) { + // handle corrupted header + try { + headers.set('Location', locationURL); + } catch (err) { + // istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request + reject(err); + } + } + break; + case 'follow': + // HTTP-redirect fetch step 2 + if (locationURL === null) { + break; + } + + // HTTP-redirect fetch step 5 + if (request.counter >= request.follow) { + reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); + finalize(); + return; + } + + // HTTP-redirect fetch step 6 (counter increment) + // Create a new Request object. + const requestOpts = { + headers: new Headers(request.headers), + follow: request.follow, + counter: request.counter + 1, + agent: request.agent, + compress: request.compress, + method: request.method, + body: request.body, + signal: request.signal, + timeout: request.timeout + }; + + // HTTP-redirect fetch step 9 + if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) { + reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect')); + finalize(); + return; + } + + // HTTP-redirect fetch step 11 + if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') { + requestOpts.method = 'GET'; + requestOpts.body = undefined; + requestOpts.headers.delete('content-length'); + } + + // HTTP-redirect fetch step 15 + resolve(fetch(new Request(locationURL, requestOpts))); + finalize(); + return; + } + } + + // prepare response + res.once('end', function () { + if (signal) signal.removeEventListener('abort', abortAndFinalize); + }); + let body = res.pipe(new PassThrough$1()); + + const response_options = { + url: request.url, + status: res.statusCode, + statusText: res.statusMessage, + headers: headers, + size: request.size, + timeout: request.timeout, + counter: request.counter + }; + + // HTTP-network fetch step 12.1.1.3 + const codings = headers.get('Content-Encoding'); + + // HTTP-network fetch step 12.1.1.4: handle content codings + + // in following scenarios we ignore compression support + // 1. compression support is disabled + // 2. HEAD request + // 3. no Content-Encoding header + // 4. no content response (204) + // 5. content not modified response (304) + if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) { + response = new Response(body, response_options); + resolve(response); + return; + } + + // For Node v6+ + // Be less strict when decoding compressed responses, since sometimes + // servers send slightly invalid responses that are still accepted + // by common browsers. + // Always using Z_SYNC_FLUSH is what cURL does. + const zlibOptions = { + flush: zlib.Z_SYNC_FLUSH, + finishFlush: zlib.Z_SYNC_FLUSH + }; + + // for gzip + if (codings == 'gzip' || codings == 'x-gzip') { + body = body.pipe(zlib.createGunzip(zlibOptions)); + response = new Response(body, response_options); + resolve(response); + return; + } + + // for deflate + if (codings == 'deflate' || codings == 'x-deflate') { + // handle the infamous raw deflate response from old servers + // a hack for old IIS and Apache servers + const raw = res.pipe(new PassThrough$1()); + raw.once('data', function (chunk) { + // see http://stackoverflow.com/questions/37519828 + if ((chunk[0] & 0x0F) === 0x08) { + body = body.pipe(zlib.createInflate()); + } else { + body = body.pipe(zlib.createInflateRaw()); + } + response = new Response(body, response_options); + resolve(response); + }); + return; + } + + // for br + if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') { + body = body.pipe(zlib.createBrotliDecompress()); + response = new Response(body, response_options); + resolve(response); + return; + } + + // otherwise, use response as-is + response = new Response(body, response_options); + resolve(response); + }); + + writeToStream(req, request); + }); +} +/** + * Redirect code matching + * + * @param Number code Status code + * @return Boolean + */ +fetch.isRedirect = function (code) { + return code === 301 || code === 302 || code === 303 || code === 307 || code === 308; +}; + +// expose Promise +fetch.Promise = global.Promise; + +module.exports = exports = fetch; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = exports; +exports.Headers = Headers; +exports.Request = Request; +exports.Response = Response; +exports.FetchError = FetchError; diff --git a/scripts/metrics/node_modules/node-fetch/lib/index.mjs b/scripts/metrics/node_modules/node-fetch/lib/index.mjs new file mode 100644 index 00000000000..dca525658b4 --- /dev/null +++ b/scripts/metrics/node_modules/node-fetch/lib/index.mjs @@ -0,0 +1,1629 @@ +import Stream from 'stream'; +import http from 'http'; +import Url from 'url'; +import https from 'https'; +import zlib from 'zlib'; + +// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js + +// fix for "Readable" isn't a named export issue +const Readable = Stream.Readable; + +const BUFFER = Symbol('buffer'); +const TYPE = Symbol('type'); + +class Blob { + constructor() { + this[TYPE] = ''; + + const blobParts = arguments[0]; + const options = arguments[1]; + + const buffers = []; + let size = 0; + + if (blobParts) { + const a = blobParts; + const length = Number(a.length); + for (let i = 0; i < length; i++) { + const element = a[i]; + let buffer; + if (element instanceof Buffer) { + buffer = element; + } else if (ArrayBuffer.isView(element)) { + buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength); + } else if (element instanceof ArrayBuffer) { + buffer = Buffer.from(element); + } else if (element instanceof Blob) { + buffer = element[BUFFER]; + } else { + buffer = Buffer.from(typeof element === 'string' ? element : String(element)); + } + size += buffer.length; + buffers.push(buffer); + } + } + + this[BUFFER] = Buffer.concat(buffers); + + let type = options && options.type !== undefined && String(options.type).toLowerCase(); + if (type && !/[^\u0020-\u007E]/.test(type)) { + this[TYPE] = type; + } + } + get size() { + return this[BUFFER].length; + } + get type() { + return this[TYPE]; + } + text() { + return Promise.resolve(this[BUFFER].toString()); + } + arrayBuffer() { + const buf = this[BUFFER]; + const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + return Promise.resolve(ab); + } + stream() { + const readable = new Readable(); + readable._read = function () {}; + readable.push(this[BUFFER]); + readable.push(null); + return readable; + } + toString() { + return '[object Blob]'; + } + slice() { + const size = this.size; + + const start = arguments[0]; + const end = arguments[1]; + let relativeStart, relativeEnd; + if (start === undefined) { + relativeStart = 0; + } else if (start < 0) { + relativeStart = Math.max(size + start, 0); + } else { + relativeStart = Math.min(start, size); + } + if (end === undefined) { + relativeEnd = size; + } else if (end < 0) { + relativeEnd = Math.max(size + end, 0); + } else { + relativeEnd = Math.min(end, size); + } + const span = Math.max(relativeEnd - relativeStart, 0); + + const buffer = this[BUFFER]; + const slicedBuffer = buffer.slice(relativeStart, relativeStart + span); + const blob = new Blob([], { type: arguments[2] }); + blob[BUFFER] = slicedBuffer; + return blob; + } +} + +Object.defineProperties(Blob.prototype, { + size: { enumerable: true }, + type: { enumerable: true }, + slice: { enumerable: true } +}); + +Object.defineProperty(Blob.prototype, Symbol.toStringTag, { + value: 'Blob', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * fetch-error.js + * + * FetchError interface for operational errors + */ + +/** + * Create FetchError instance + * + * @param String message Error message for human + * @param String type Error type for machine + * @param String systemError For Node.js system error + * @return FetchError + */ +function FetchError(message, type, systemError) { + Error.call(this, message); + + this.message = message; + this.type = type; + + // when err.type is `system`, err.code contains system error code + if (systemError) { + this.code = this.errno = systemError.code; + } + + // hide custom error implementation details from end-users + Error.captureStackTrace(this, this.constructor); +} + +FetchError.prototype = Object.create(Error.prototype); +FetchError.prototype.constructor = FetchError; +FetchError.prototype.name = 'FetchError'; + +let convert; +try { + convert = require('encoding').convert; +} catch (e) {} + +const INTERNALS = Symbol('Body internals'); + +// fix an issue where "PassThrough" isn't a named export for node <10 +const PassThrough = Stream.PassThrough; + +/** + * Body mixin + * + * Ref: https://fetch.spec.whatwg.org/#body + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +function Body(body) { + var _this = this; + + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref$size = _ref.size; + + let size = _ref$size === undefined ? 0 : _ref$size; + var _ref$timeout = _ref.timeout; + let timeout = _ref$timeout === undefined ? 0 : _ref$timeout; + + if (body == null) { + // body is undefined or null + body = null; + } else if (isURLSearchParams(body)) { + // body is a URLSearchParams + body = Buffer.from(body.toString()); + } else if (isBlob(body)) ; else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { + // body is ArrayBuffer + body = Buffer.from(body); + } else if (ArrayBuffer.isView(body)) { + // body is ArrayBufferView + body = Buffer.from(body.buffer, body.byteOffset, body.byteLength); + } else if (body instanceof Stream) ; else { + // none of the above + // coerce to string then buffer + body = Buffer.from(String(body)); + } + this[INTERNALS] = { + body, + disturbed: false, + error: null + }; + this.size = size; + this.timeout = timeout; + + if (body instanceof Stream) { + body.on('error', function (err) { + const error = err.name === 'AbortError' ? err : new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err); + _this[INTERNALS].error = error; + }); + } +} + +Body.prototype = { + get body() { + return this[INTERNALS].body; + }, + + get bodyUsed() { + return this[INTERNALS].disturbed; + }, + + /** + * Decode response as ArrayBuffer + * + * @return Promise + */ + arrayBuffer() { + return consumeBody.call(this).then(function (buf) { + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + }); + }, + + /** + * Return raw response as Blob + * + * @return Promise + */ + blob() { + let ct = this.headers && this.headers.get('content-type') || ''; + return consumeBody.call(this).then(function (buf) { + return Object.assign( + // Prevent copying + new Blob([], { + type: ct.toLowerCase() + }), { + [BUFFER]: buf + }); + }); + }, + + /** + * Decode response as json + * + * @return Promise + */ + json() { + var _this2 = this; + + return consumeBody.call(this).then(function (buffer) { + try { + return JSON.parse(buffer.toString()); + } catch (err) { + return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json')); + } + }); + }, + + /** + * Decode response as text + * + * @return Promise + */ + text() { + return consumeBody.call(this).then(function (buffer) { + return buffer.toString(); + }); + }, + + /** + * Decode response as buffer (non-spec api) + * + * @return Promise + */ + buffer() { + return consumeBody.call(this); + }, + + /** + * Decode response as text, while automatically detecting the encoding and + * trying to decode to UTF-8 (non-spec api) + * + * @return Promise + */ + textConverted() { + var _this3 = this; + + return consumeBody.call(this).then(function (buffer) { + return convertBody(buffer, _this3.headers); + }); + } +}; + +// In browsers, all properties are enumerable. +Object.defineProperties(Body.prototype, { + body: { enumerable: true }, + bodyUsed: { enumerable: true }, + arrayBuffer: { enumerable: true }, + blob: { enumerable: true }, + json: { enumerable: true }, + text: { enumerable: true } +}); + +Body.mixIn = function (proto) { + for (const name of Object.getOwnPropertyNames(Body.prototype)) { + // istanbul ignore else: future proof + if (!(name in proto)) { + const desc = Object.getOwnPropertyDescriptor(Body.prototype, name); + Object.defineProperty(proto, name, desc); + } + } +}; + +/** + * Consume and convert an entire Body to a Buffer. + * + * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body + * + * @return Promise + */ +function consumeBody() { + var _this4 = this; + + if (this[INTERNALS].disturbed) { + return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`)); + } + + this[INTERNALS].disturbed = true; + + if (this[INTERNALS].error) { + return Body.Promise.reject(this[INTERNALS].error); + } + + let body = this.body; + + // body is null + if (body === null) { + return Body.Promise.resolve(Buffer.alloc(0)); + } + + // body is blob + if (isBlob(body)) { + body = body.stream(); + } + + // body is buffer + if (Buffer.isBuffer(body)) { + return Body.Promise.resolve(body); + } + + // istanbul ignore if: should never happen + if (!(body instanceof Stream)) { + return Body.Promise.resolve(Buffer.alloc(0)); + } + + // body is stream + // get ready to actually consume the body + let accum = []; + let accumBytes = 0; + let abort = false; + + return new Body.Promise(function (resolve, reject) { + let resTimeout; + + // allow timeout on slow response body + if (_this4.timeout) { + resTimeout = setTimeout(function () { + abort = true; + reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, 'body-timeout')); + }, _this4.timeout); + } + + // handle stream errors + body.on('error', function (err) { + if (err.name === 'AbortError') { + // if the request was aborted, reject with this Error + abort = true; + reject(err); + } else { + // other errors, such as incorrect content-encoding + reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, 'system', err)); + } + }); + + body.on('data', function (chunk) { + if (abort || chunk === null) { + return; + } + + if (_this4.size && accumBytes + chunk.length > _this4.size) { + abort = true; + reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, 'max-size')); + return; + } + + accumBytes += chunk.length; + accum.push(chunk); + }); + + body.on('end', function () { + if (abort) { + return; + } + + clearTimeout(resTimeout); + + try { + resolve(Buffer.concat(accum, accumBytes)); + } catch (err) { + // handle streams that have accumulated too much data (issue #414) + reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, 'system', err)); + } + }); + }); +} + +/** + * Detect buffer encoding and convert to target encoding + * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding + * + * @param Buffer buffer Incoming buffer + * @param String encoding Target encoding + * @return String + */ +function convertBody(buffer, headers) { + if (typeof convert !== 'function') { + throw new Error('The package `encoding` must be installed to use the textConverted() function'); + } + + const ct = headers.get('content-type'); + let charset = 'utf-8'; + let res, str; + + // header + if (ct) { + res = /charset=([^;]*)/i.exec(ct); + } + + // no charset in content type, peek at response body for at most 1024 bytes + str = buffer.slice(0, 1024).toString(); + + // html5 + if (!res && str) { + res = / 0 && arguments[0] !== undefined ? arguments[0] : undefined; + + this[MAP] = Object.create(null); + + if (init instanceof Headers) { + const rawHeaders = init.raw(); + const headerNames = Object.keys(rawHeaders); + + for (const headerName of headerNames) { + for (const value of rawHeaders[headerName]) { + this.append(headerName, value); + } + } + + return; + } + + // We don't worry about converting prop to ByteString here as append() + // will handle it. + if (init == null) ; else if (typeof init === 'object') { + const method = init[Symbol.iterator]; + if (method != null) { + if (typeof method !== 'function') { + throw new TypeError('Header pairs must be iterable'); + } + + // sequence> + // Note: per spec we have to first exhaust the lists then process them + const pairs = []; + for (const pair of init) { + if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { + throw new TypeError('Each header pair must be iterable'); + } + pairs.push(Array.from(pair)); + } + + for (const pair of pairs) { + if (pair.length !== 2) { + throw new TypeError('Each header pair must be a name/value tuple'); + } + this.append(pair[0], pair[1]); + } + } else { + // record + for (const key of Object.keys(init)) { + const value = init[key]; + this.append(key, value); + } + } + } else { + throw new TypeError('Provided initializer must be an object'); + } + } + + /** + * Return combined header value given name + * + * @param String name Header name + * @return Mixed + */ + get(name) { + name = `${name}`; + validateName(name); + const key = find(this[MAP], name); + if (key === undefined) { + return null; + } + + return this[MAP][key].join(', '); + } + + /** + * Iterate over all headers + * + * @param Function callback Executed for each item with parameters (value, name, thisArg) + * @param Boolean thisArg `this` context for callback function + * @return Void + */ + forEach(callback) { + let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; + + let pairs = getHeaders(this); + let i = 0; + while (i < pairs.length) { + var _pairs$i = pairs[i]; + const name = _pairs$i[0], + value = _pairs$i[1]; + + callback.call(thisArg, value, name, this); + pairs = getHeaders(this); + i++; + } + } + + /** + * Overwrite header values given name + * + * @param String name Header name + * @param String value Header value + * @return Void + */ + set(name, value) { + name = `${name}`; + value = `${value}`; + validateName(name); + validateValue(value); + const key = find(this[MAP], name); + this[MAP][key !== undefined ? key : name] = [value]; + } + + /** + * Append a value onto existing header + * + * @param String name Header name + * @param String value Header value + * @return Void + */ + append(name, value) { + name = `${name}`; + value = `${value}`; + validateName(name); + validateValue(value); + const key = find(this[MAP], name); + if (key !== undefined) { + this[MAP][key].push(value); + } else { + this[MAP][name] = [value]; + } + } + + /** + * Check for header name existence + * + * @param String name Header name + * @return Boolean + */ + has(name) { + name = `${name}`; + validateName(name); + return find(this[MAP], name) !== undefined; + } + + /** + * Delete all header values given name + * + * @param String name Header name + * @return Void + */ + delete(name) { + name = `${name}`; + validateName(name); + const key = find(this[MAP], name); + if (key !== undefined) { + delete this[MAP][key]; + } + } + + /** + * Return raw headers (non-spec api) + * + * @return Object + */ + raw() { + return this[MAP]; + } + + /** + * Get an iterator on keys. + * + * @return Iterator + */ + keys() { + return createHeadersIterator(this, 'key'); + } + + /** + * Get an iterator on values. + * + * @return Iterator + */ + values() { + return createHeadersIterator(this, 'value'); + } + + /** + * Get an iterator on entries. + * + * This is the default iterator of the Headers object. + * + * @return Iterator + */ + [Symbol.iterator]() { + return createHeadersIterator(this, 'key+value'); + } +} +Headers.prototype.entries = Headers.prototype[Symbol.iterator]; + +Object.defineProperty(Headers.prototype, Symbol.toStringTag, { + value: 'Headers', + writable: false, + enumerable: false, + configurable: true +}); + +Object.defineProperties(Headers.prototype, { + get: { enumerable: true }, + forEach: { enumerable: true }, + set: { enumerable: true }, + append: { enumerable: true }, + has: { enumerable: true }, + delete: { enumerable: true }, + keys: { enumerable: true }, + values: { enumerable: true }, + entries: { enumerable: true } +}); + +function getHeaders(headers) { + let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value'; + + const keys = Object.keys(headers[MAP]).sort(); + return keys.map(kind === 'key' ? function (k) { + return k.toLowerCase(); + } : kind === 'value' ? function (k) { + return headers[MAP][k].join(', '); + } : function (k) { + return [k.toLowerCase(), headers[MAP][k].join(', ')]; + }); +} + +const INTERNAL = Symbol('internal'); + +function createHeadersIterator(target, kind) { + const iterator = Object.create(HeadersIteratorPrototype); + iterator[INTERNAL] = { + target, + kind, + index: 0 + }; + return iterator; +} + +const HeadersIteratorPrototype = Object.setPrototypeOf({ + next() { + // istanbul ignore if + if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) { + throw new TypeError('Value of `this` is not a HeadersIterator'); + } + + var _INTERNAL = this[INTERNAL]; + const target = _INTERNAL.target, + kind = _INTERNAL.kind, + index = _INTERNAL.index; + + const values = getHeaders(target, kind); + const len = values.length; + if (index >= len) { + return { + value: undefined, + done: true + }; + } + + this[INTERNAL].index = index + 1; + + return { + value: values[index], + done: false + }; + } +}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))); + +Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, { + value: 'HeadersIterator', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * Export the Headers object in a form that Node.js can consume. + * + * @param Headers headers + * @return Object + */ +function exportNodeCompatibleHeaders(headers) { + const obj = Object.assign({ __proto__: null }, headers[MAP]); + + // http.request() only supports string as Host header. This hack makes + // specifying custom Host header possible. + const hostHeaderKey = find(headers[MAP], 'Host'); + if (hostHeaderKey !== undefined) { + obj[hostHeaderKey] = obj[hostHeaderKey][0]; + } + + return obj; +} + +/** + * Create a Headers object from an object of headers, ignoring those that do + * not conform to HTTP grammar productions. + * + * @param Object obj Object of headers + * @return Headers + */ +function createHeadersLenient(obj) { + const headers = new Headers(); + for (const name of Object.keys(obj)) { + if (invalidTokenRegex.test(name)) { + continue; + } + if (Array.isArray(obj[name])) { + for (const val of obj[name]) { + if (invalidHeaderCharRegex.test(val)) { + continue; + } + if (headers[MAP][name] === undefined) { + headers[MAP][name] = [val]; + } else { + headers[MAP][name].push(val); + } + } + } else if (!invalidHeaderCharRegex.test(obj[name])) { + headers[MAP][name] = [obj[name]]; + } + } + return headers; +} + +const INTERNALS$1 = Symbol('Response internals'); + +// fix an issue where "STATUS_CODES" aren't a named export for node <10 +const STATUS_CODES = http.STATUS_CODES; + +/** + * Response class + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +class Response { + constructor() { + let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + Body.call(this, body, opts); + + const status = opts.status || 200; + const headers = new Headers(opts.headers); + + if (body != null && !headers.has('Content-Type')) { + const contentType = extractContentType(body); + if (contentType) { + headers.append('Content-Type', contentType); + } + } + + this[INTERNALS$1] = { + url: opts.url, + status, + statusText: opts.statusText || STATUS_CODES[status], + headers, + counter: opts.counter + }; + } + + get url() { + return this[INTERNALS$1].url; + } + + get status() { + return this[INTERNALS$1].status; + } + + /** + * Convenience property representing if the request ended normally + */ + get ok() { + return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300; + } + + get redirected() { + return this[INTERNALS$1].counter > 0; + } + + get statusText() { + return this[INTERNALS$1].statusText; + } + + get headers() { + return this[INTERNALS$1].headers; + } + + /** + * Clone this response + * + * @return Response + */ + clone() { + return new Response(clone(this), { + url: this.url, + status: this.status, + statusText: this.statusText, + headers: this.headers, + ok: this.ok, + redirected: this.redirected + }); + } +} + +Body.mixIn(Response.prototype); + +Object.defineProperties(Response.prototype, { + url: { enumerable: true }, + status: { enumerable: true }, + ok: { enumerable: true }, + redirected: { enumerable: true }, + statusText: { enumerable: true }, + headers: { enumerable: true }, + clone: { enumerable: true } +}); + +Object.defineProperty(Response.prototype, Symbol.toStringTag, { + value: 'Response', + writable: false, + enumerable: false, + configurable: true +}); + +const INTERNALS$2 = Symbol('Request internals'); + +// fix an issue where "format", "parse" aren't a named export for node <10 +const parse_url = Url.parse; +const format_url = Url.format; + +const streamDestructionSupported = 'destroy' in Stream.Readable.prototype; + +/** + * Check if a value is an instance of Request. + * + * @param Mixed input + * @return Boolean + */ +function isRequest(input) { + return typeof input === 'object' && typeof input[INTERNALS$2] === 'object'; +} + +function isAbortSignal(signal) { + const proto = signal && typeof signal === 'object' && Object.getPrototypeOf(signal); + return !!(proto && proto.constructor.name === 'AbortSignal'); +} + +/** + * Request class + * + * @param Mixed input Url or Request instance + * @param Object init Custom options + * @return Void + */ +class Request { + constructor(input) { + let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + let parsedURL; + + // normalize input + if (!isRequest(input)) { + if (input && input.href) { + // in order to support Node.js' Url objects; though WHATWG's URL objects + // will fall into this branch also (since their `toString()` will return + // `href` property anyway) + parsedURL = parse_url(input.href); + } else { + // coerce input to a string before attempting to parse + parsedURL = parse_url(`${input}`); + } + input = {}; + } else { + parsedURL = parse_url(input.url); + } + + let method = init.method || input.method || 'GET'; + method = method.toUpperCase(); + + if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) { + throw new TypeError('Request with GET/HEAD method cannot have body'); + } + + let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null; + + Body.call(this, inputBody, { + timeout: init.timeout || input.timeout || 0, + size: init.size || input.size || 0 + }); + + const headers = new Headers(init.headers || input.headers || {}); + + if (inputBody != null && !headers.has('Content-Type')) { + const contentType = extractContentType(inputBody); + if (contentType) { + headers.append('Content-Type', contentType); + } + } + + let signal = isRequest(input) ? input.signal : null; + if ('signal' in init) signal = init.signal; + + if (signal != null && !isAbortSignal(signal)) { + throw new TypeError('Expected signal to be an instanceof AbortSignal'); + } + + this[INTERNALS$2] = { + method, + redirect: init.redirect || input.redirect || 'follow', + headers, + parsedURL, + signal + }; + + // node-fetch-only options + this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20; + this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true; + this.counter = init.counter || input.counter || 0; + this.agent = init.agent || input.agent; + } + + get method() { + return this[INTERNALS$2].method; + } + + get url() { + return format_url(this[INTERNALS$2].parsedURL); + } + + get headers() { + return this[INTERNALS$2].headers; + } + + get redirect() { + return this[INTERNALS$2].redirect; + } + + get signal() { + return this[INTERNALS$2].signal; + } + + /** + * Clone this request + * + * @return Request + */ + clone() { + return new Request(this); + } +} + +Body.mixIn(Request.prototype); + +Object.defineProperty(Request.prototype, Symbol.toStringTag, { + value: 'Request', + writable: false, + enumerable: false, + configurable: true +}); + +Object.defineProperties(Request.prototype, { + method: { enumerable: true }, + url: { enumerable: true }, + headers: { enumerable: true }, + redirect: { enumerable: true }, + clone: { enumerable: true }, + signal: { enumerable: true } +}); + +/** + * Convert a Request to Node.js http request options. + * + * @param Request A Request instance + * @return Object The options object to be passed to http.request + */ +function getNodeRequestOptions(request) { + const parsedURL = request[INTERNALS$2].parsedURL; + const headers = new Headers(request[INTERNALS$2].headers); + + // fetch step 1.3 + if (!headers.has('Accept')) { + headers.set('Accept', '*/*'); + } + + // Basic fetch + if (!parsedURL.protocol || !parsedURL.hostname) { + throw new TypeError('Only absolute URLs are supported'); + } + + if (!/^https?:$/.test(parsedURL.protocol)) { + throw new TypeError('Only HTTP(S) protocols are supported'); + } + + if (request.signal && request.body instanceof Stream.Readable && !streamDestructionSupported) { + throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8'); + } + + // HTTP-network-or-cache fetch steps 2.4-2.7 + let contentLengthValue = null; + if (request.body == null && /^(POST|PUT)$/i.test(request.method)) { + contentLengthValue = '0'; + } + if (request.body != null) { + const totalBytes = getTotalBytes(request); + if (typeof totalBytes === 'number') { + contentLengthValue = String(totalBytes); + } + } + if (contentLengthValue) { + headers.set('Content-Length', contentLengthValue); + } + + // HTTP-network-or-cache fetch step 2.11 + if (!headers.has('User-Agent')) { + headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)'); + } + + // HTTP-network-or-cache fetch step 2.15 + if (request.compress && !headers.has('Accept-Encoding')) { + headers.set('Accept-Encoding', 'gzip,deflate'); + } + + if (!headers.has('Connection') && !request.agent) { + headers.set('Connection', 'close'); + } + + // HTTP-network fetch step 4.2 + // chunked encoding is handled by Node.js + + return Object.assign({}, parsedURL, { + method: request.method, + headers: exportNodeCompatibleHeaders(headers), + agent: request.agent + }); +} + +/** + * abort-error.js + * + * AbortError interface for cancelled requests + */ + +/** + * Create AbortError instance + * + * @param String message Error message for human + * @return AbortError + */ +function AbortError(message) { + Error.call(this, message); + + this.type = 'aborted'; + this.message = message; + + // hide custom error implementation details from end-users + Error.captureStackTrace(this, this.constructor); +} + +AbortError.prototype = Object.create(Error.prototype); +AbortError.prototype.constructor = AbortError; +AbortError.prototype.name = 'AbortError'; + +// fix an issue where "PassThrough", "resolve" aren't a named export for node <10 +const PassThrough$1 = Stream.PassThrough; +const resolve_url = Url.resolve; + +/** + * Fetch function + * + * @param Mixed url Absolute url or Request instance + * @param Object opts Fetch options + * @return Promise + */ +function fetch(url, opts) { + + // allow custom promise + if (!fetch.Promise) { + throw new Error('native promise missing, set fetch.Promise to your favorite alternative'); + } + + Body.Promise = fetch.Promise; + + // wrap http.request into fetch + return new fetch.Promise(function (resolve, reject) { + // build request object + const request = new Request(url, opts); + const options = getNodeRequestOptions(request); + + const send = (options.protocol === 'https:' ? https : http).request; + const signal = request.signal; + + let response = null; + + const abort = function abort() { + let error = new AbortError('The user aborted a request.'); + reject(error); + if (request.body && request.body instanceof Stream.Readable) { + request.body.destroy(error); + } + if (!response || !response.body) return; + response.body.emit('error', error); + }; + + if (signal && signal.aborted) { + abort(); + return; + } + + const abortAndFinalize = function abortAndFinalize() { + abort(); + finalize(); + }; + + // send request + const req = send(options); + let reqTimeout; + + if (signal) { + signal.addEventListener('abort', abortAndFinalize); + } + + function finalize() { + req.abort(); + if (signal) signal.removeEventListener('abort', abortAndFinalize); + clearTimeout(reqTimeout); + } + + if (request.timeout) { + req.once('socket', function (socket) { + reqTimeout = setTimeout(function () { + reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); + finalize(); + }, request.timeout); + }); + } + + req.on('error', function (err) { + reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); + finalize(); + }); + + req.on('response', function (res) { + clearTimeout(reqTimeout); + + const headers = createHeadersLenient(res.headers); + + // HTTP fetch step 5 + if (fetch.isRedirect(res.statusCode)) { + // HTTP fetch step 5.2 + const location = headers.get('Location'); + + // HTTP fetch step 5.3 + const locationURL = location === null ? null : resolve_url(request.url, location); + + // HTTP fetch step 5.5 + switch (request.redirect) { + case 'error': + reject(new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect')); + finalize(); + return; + case 'manual': + // node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL. + if (locationURL !== null) { + // handle corrupted header + try { + headers.set('Location', locationURL); + } catch (err) { + // istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request + reject(err); + } + } + break; + case 'follow': + // HTTP-redirect fetch step 2 + if (locationURL === null) { + break; + } + + // HTTP-redirect fetch step 5 + if (request.counter >= request.follow) { + reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); + finalize(); + return; + } + + // HTTP-redirect fetch step 6 (counter increment) + // Create a new Request object. + const requestOpts = { + headers: new Headers(request.headers), + follow: request.follow, + counter: request.counter + 1, + agent: request.agent, + compress: request.compress, + method: request.method, + body: request.body, + signal: request.signal, + timeout: request.timeout + }; + + // HTTP-redirect fetch step 9 + if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) { + reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect')); + finalize(); + return; + } + + // HTTP-redirect fetch step 11 + if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') { + requestOpts.method = 'GET'; + requestOpts.body = undefined; + requestOpts.headers.delete('content-length'); + } + + // HTTP-redirect fetch step 15 + resolve(fetch(new Request(locationURL, requestOpts))); + finalize(); + return; + } + } + + // prepare response + res.once('end', function () { + if (signal) signal.removeEventListener('abort', abortAndFinalize); + }); + let body = res.pipe(new PassThrough$1()); + + const response_options = { + url: request.url, + status: res.statusCode, + statusText: res.statusMessage, + headers: headers, + size: request.size, + timeout: request.timeout, + counter: request.counter + }; + + // HTTP-network fetch step 12.1.1.3 + const codings = headers.get('Content-Encoding'); + + // HTTP-network fetch step 12.1.1.4: handle content codings + + // in following scenarios we ignore compression support + // 1. compression support is disabled + // 2. HEAD request + // 3. no Content-Encoding header + // 4. no content response (204) + // 5. content not modified response (304) + if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) { + response = new Response(body, response_options); + resolve(response); + return; + } + + // For Node v6+ + // Be less strict when decoding compressed responses, since sometimes + // servers send slightly invalid responses that are still accepted + // by common browsers. + // Always using Z_SYNC_FLUSH is what cURL does. + const zlibOptions = { + flush: zlib.Z_SYNC_FLUSH, + finishFlush: zlib.Z_SYNC_FLUSH + }; + + // for gzip + if (codings == 'gzip' || codings == 'x-gzip') { + body = body.pipe(zlib.createGunzip(zlibOptions)); + response = new Response(body, response_options); + resolve(response); + return; + } + + // for deflate + if (codings == 'deflate' || codings == 'x-deflate') { + // handle the infamous raw deflate response from old servers + // a hack for old IIS and Apache servers + const raw = res.pipe(new PassThrough$1()); + raw.once('data', function (chunk) { + // see http://stackoverflow.com/questions/37519828 + if ((chunk[0] & 0x0F) === 0x08) { + body = body.pipe(zlib.createInflate()); + } else { + body = body.pipe(zlib.createInflateRaw()); + } + response = new Response(body, response_options); + resolve(response); + }); + return; + } + + // for br + if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') { + body = body.pipe(zlib.createBrotliDecompress()); + response = new Response(body, response_options); + resolve(response); + return; + } + + // otherwise, use response as-is + response = new Response(body, response_options); + resolve(response); + }); + + writeToStream(req, request); + }); +} +/** + * Redirect code matching + * + * @param Number code Status code + * @return Boolean + */ +fetch.isRedirect = function (code) { + return code === 301 || code === 302 || code === 303 || code === 307 || code === 308; +}; + +// expose Promise +fetch.Promise = global.Promise; + +export default fetch; +export { Headers, Request, Response, FetchError }; diff --git a/scripts/metrics/node_modules/node-fetch/package.json b/scripts/metrics/node_modules/node-fetch/package.json new file mode 100644 index 00000000000..e93129c801f --- /dev/null +++ b/scripts/metrics/node_modules/node-fetch/package.json @@ -0,0 +1,94 @@ +{ + "_from": "node-fetch", + "_id": "node-fetch@2.5.0", + "_inBundle": false, + "_integrity": "sha512-YuZKluhWGJwCcUu4RlZstdAxr8bFfOVHakc1mplwHkk8J+tqM1Y5yraYvIUpeX8aY7+crCwiELJq7Vl0o0LWXw==", + "_location": "/node-fetch", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "node-fetch", + "name": "node-fetch", + "escapedName": "node-fetch", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.5.0.tgz", + "_shasum": "8028c49fc1191bba56a07adc6e2a954644a48501", + "_spec": "node-fetch", + "_where": "/Users/zachary.butler/Work/auto-buildkite-pipelines/scripts/metrics", + "author": { + "name": "David Frank" + }, + "browser": "./browser.js", + "bugs": { + "url": "https://github.com/bitinn/node-fetch/issues" + }, + "bundleDependencies": false, + "dependencies": {}, + "deprecated": false, + "description": "A light-weight module that brings window.fetch to node.js", + "devDependencies": { + "@ungap/url-search-params": "^0.1.2", + "abort-controller": "^1.1.0", + "abortcontroller-polyfill": "^1.3.0", + "babel-core": "^6.26.3", + "babel-plugin-istanbul": "^4.1.6", + "babel-preset-env": "^1.6.1", + "babel-register": "^6.16.3", + "chai": "^3.5.0", + "chai-as-promised": "^7.1.1", + "chai-iterator": "^1.1.1", + "chai-string": "~1.3.0", + "codecov": "^3.3.0", + "cross-env": "^5.2.0", + "form-data": "^2.3.3", + "is-builtin-module": "^1.0.0", + "mocha": "^5.0.0", + "nyc": "11.9.0", + "parted": "^0.1.1", + "promise": "^8.0.3", + "resumer": "0.0.0", + "rollup": "^0.63.4", + "rollup-plugin-babel": "^3.0.7", + "string-to-arraybuffer": "^1.0.2", + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "files": [ + "lib/index.js", + "lib/index.mjs", + "lib/index.es.js", + "browser.js" + ], + "homepage": "https://github.com/bitinn/node-fetch", + "keywords": [ + "fetch", + "http", + "promise" + ], + "license": "MIT", + "main": "lib/index", + "module": "lib/index.mjs", + "name": "node-fetch", + "repository": { + "type": "git", + "url": "git+https://github.com/bitinn/node-fetch.git" + }, + "scripts": { + "build": "cross-env BABEL_ENV=rollup rollup -c", + "coverage": "cross-env BABEL_ENV=coverage nyc --reporter json --reporter text mocha -R spec test/test.js && codecov -f coverage/coverage-final.json", + "prepare": "npm run build", + "report": "cross-env BABEL_ENV=coverage nyc --reporter lcov --reporter text mocha -R spec test/test.js", + "test": "cross-env BABEL_ENV=test mocha --require babel-register --throw-deprecation test/test.js" + }, + "version": "2.5.0" +} diff --git a/scripts/metrics/node_modules/sax/LICENSE b/scripts/metrics/node_modules/sax/LICENSE new file mode 100644 index 00000000000..ccffa082c99 --- /dev/null +++ b/scripts/metrics/node_modules/sax/LICENSE @@ -0,0 +1,41 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +==== + +`String.fromCodePoint` by Mathias Bynens used according to terms of MIT +License, as follows: + + Copyright Mathias Bynens + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/scripts/metrics/node_modules/sax/README.md b/scripts/metrics/node_modules/sax/README.md new file mode 100644 index 00000000000..afcd3f3dd65 --- /dev/null +++ b/scripts/metrics/node_modules/sax/README.md @@ -0,0 +1,225 @@ +# sax js + +A sax-style parser for XML and HTML. + +Designed with [node](http://nodejs.org/) in mind, but should work fine in +the browser or other CommonJS implementations. + +## What This Is + +* A very simple tool to parse through an XML string. +* A stepping stone to a streaming HTML parser. +* A handy way to deal with RSS and other mostly-ok-but-kinda-broken XML + docs. + +## What This Is (probably) Not + +* An HTML Parser - That's a fine goal, but this isn't it. It's just + XML. +* A DOM Builder - You can use it to build an object model out of XML, + but it doesn't do that out of the box. +* XSLT - No DOM = no querying. +* 100% Compliant with (some other SAX implementation) - Most SAX + implementations are in Java and do a lot more than this does. +* An XML Validator - It does a little validation when in strict mode, but + not much. +* A Schema-Aware XSD Thing - Schemas are an exercise in fetishistic + masochism. +* A DTD-aware Thing - Fetching DTDs is a much bigger job. + +## Regarding `Hello, world!').close(); + +// stream usage +// takes the same options as the parser +var saxStream = require("sax").createStream(strict, options) +saxStream.on("error", function (e) { + // unhandled errors will throw, since this is a proper node + // event emitter. + console.error("error!", e) + // clear the error + this._parser.error = null + this._parser.resume() +}) +saxStream.on("opentag", function (node) { + // same object as above +}) +// pipe is supported, and it's readable/writable +// same chunks coming in also go out. +fs.createReadStream("file.xml") + .pipe(saxStream) + .pipe(fs.createWriteStream("file-copy.xml")) +``` + + +## Arguments + +Pass the following arguments to the parser function. All are optional. + +`strict` - Boolean. Whether or not to be a jerk. Default: `false`. + +`opt` - Object bag of settings regarding string formatting. All default to `false`. + +Settings supported: + +* `trim` - Boolean. Whether or not to trim text and comment nodes. +* `normalize` - Boolean. If true, then turn any whitespace into a single + space. +* `lowercase` - Boolean. If true, then lowercase tag names and attribute names + in loose mode, rather than uppercasing them. +* `xmlns` - Boolean. If true, then namespaces are supported. +* `position` - Boolean. If false, then don't track line/col/position. +* `strictEntities` - Boolean. If true, only parse [predefined XML + entities](http://www.w3.org/TR/REC-xml/#sec-predefined-ent) + (`&`, `'`, `>`, `<`, and `"`) + +## Methods + +`write` - Write bytes onto the stream. You don't have to do this all at +once. You can keep writing as much as you want. + +`close` - Close the stream. Once closed, no more data may be written until +it is done processing the buffer, which is signaled by the `end` event. + +`resume` - To gracefully handle errors, assign a listener to the `error` +event. Then, when the error is taken care of, you can call `resume` to +continue parsing. Otherwise, the parser will not continue while in an error +state. + +## Members + +At all times, the parser object will have the following members: + +`line`, `column`, `position` - Indications of the position in the XML +document where the parser currently is looking. + +`startTagPosition` - Indicates the position where the current tag starts. + +`closed` - Boolean indicating whether or not the parser can be written to. +If it's `true`, then wait for the `ready` event to write again. + +`strict` - Boolean indicating whether or not the parser is a jerk. + +`opt` - Any options passed into the constructor. + +`tag` - The current tag being dealt with. + +And a bunch of other stuff that you probably shouldn't touch. + +## Events + +All events emit with a single argument. To listen to an event, assign a +function to `on`. Functions get executed in the this-context of +the parser object. The list of supported events are also in the exported +`EVENTS` array. + +When using the stream interface, assign handlers using the EventEmitter +`on` function in the normal fashion. + +`error` - Indication that something bad happened. The error will be hanging +out on `parser.error`, and must be deleted before parsing can continue. By +listening to this event, you can keep an eye on that kind of stuff. Note: +this happens *much* more in strict mode. Argument: instance of `Error`. + +`text` - Text node. Argument: string of text. + +`doctype` - The ``. Argument: +object with `name` and `body` members. Attributes are not parsed, as +processing instructions have implementation dependent semantics. + +`sgmldeclaration` - Random SGML declarations. Stuff like `` +would trigger this kind of event. This is a weird thing to support, so it +might go away at some point. SAX isn't intended to be used to parse SGML, +after all. + +`opentagstart` - Emitted immediately when the tag name is available, +but before any attributes are encountered. Argument: object with a +`name` field and an empty `attributes` set. Note that this is the +same object that will later be emitted in the `opentag` event. + +`opentag` - An opening tag. Argument: object with `name` and `attributes`. +In non-strict mode, tag names are uppercased, unless the `lowercase` +option is set. If the `xmlns` option is set, then it will contain +namespace binding information on the `ns` member, and will have a +`local`, `prefix`, and `uri` member. + +`closetag` - A closing tag. In loose mode, tags are auto-closed if their +parent closes. In strict mode, well-formedness is enforced. Note that +self-closing tags will have `closeTag` emitted immediately after `openTag`. +Argument: tag name. + +`attribute` - An attribute node. Argument: object with `name` and `value`. +In non-strict mode, attribute names are uppercased, unless the `lowercase` +option is set. If the `xmlns` option is set, it will also contains namespace +information. + +`comment` - A comment node. Argument: the string of the comment. + +`opencdata` - The opening tag of a ``) of a `` tags trigger a `"script"` +event, and their contents are not checked for special xml characters. +If you pass `noscript: true`, then this behavior is suppressed. + +## Reporting Problems + +It's best to write a failing test if you find an issue. I will always +accept pull requests with failing tests if they demonstrate intended +behavior, but it is very hard to figure out what issue you're describing +without a test. Writing a test is also the best way for you yourself +to figure out if you really understand the issue you think you have with +sax-js. diff --git a/scripts/metrics/node_modules/sax/lib/sax.js b/scripts/metrics/node_modules/sax/lib/sax.js new file mode 100644 index 00000000000..795d607ef63 --- /dev/null +++ b/scripts/metrics/node_modules/sax/lib/sax.js @@ -0,0 +1,1565 @@ +;(function (sax) { // wrapper for non-node envs + sax.parser = function (strict, opt) { return new SAXParser(strict, opt) } + sax.SAXParser = SAXParser + sax.SAXStream = SAXStream + sax.createStream = createStream + + // When we pass the MAX_BUFFER_LENGTH position, start checking for buffer overruns. + // When we check, schedule the next check for MAX_BUFFER_LENGTH - (max(buffer lengths)), + // since that's the earliest that a buffer overrun could occur. This way, checks are + // as rare as required, but as often as necessary to ensure never crossing this bound. + // Furthermore, buffers are only tested at most once per write(), so passing a very + // large string into write() might have undesirable effects, but this is manageable by + // the caller, so it is assumed to be safe. Thus, a call to write() may, in the extreme + // edge case, result in creating at most one complete copy of the string passed in. + // Set to Infinity to have unlimited buffers. + sax.MAX_BUFFER_LENGTH = 64 * 1024 + + var buffers = [ + 'comment', 'sgmlDecl', 'textNode', 'tagName', 'doctype', + 'procInstName', 'procInstBody', 'entity', 'attribName', + 'attribValue', 'cdata', 'script' + ] + + sax.EVENTS = [ + 'text', + 'processinginstruction', + 'sgmldeclaration', + 'doctype', + 'comment', + 'opentagstart', + 'attribute', + 'opentag', + 'closetag', + 'opencdata', + 'cdata', + 'closecdata', + 'error', + 'end', + 'ready', + 'script', + 'opennamespace', + 'closenamespace' + ] + + function SAXParser (strict, opt) { + if (!(this instanceof SAXParser)) { + return new SAXParser(strict, opt) + } + + var parser = this + clearBuffers(parser) + parser.q = parser.c = '' + parser.bufferCheckPosition = sax.MAX_BUFFER_LENGTH + parser.opt = opt || {} + parser.opt.lowercase = parser.opt.lowercase || parser.opt.lowercasetags + parser.looseCase = parser.opt.lowercase ? 'toLowerCase' : 'toUpperCase' + parser.tags = [] + parser.closed = parser.closedRoot = parser.sawRoot = false + parser.tag = parser.error = null + parser.strict = !!strict + parser.noscript = !!(strict || parser.opt.noscript) + parser.state = S.BEGIN + parser.strictEntities = parser.opt.strictEntities + parser.ENTITIES = parser.strictEntities ? Object.create(sax.XML_ENTITIES) : Object.create(sax.ENTITIES) + parser.attribList = [] + + // namespaces form a prototype chain. + // it always points at the current tag, + // which protos to its parent tag. + if (parser.opt.xmlns) { + parser.ns = Object.create(rootNS) + } + + // mostly just for error reporting + parser.trackPosition = parser.opt.position !== false + if (parser.trackPosition) { + parser.position = parser.line = parser.column = 0 + } + emit(parser, 'onready') + } + + if (!Object.create) { + Object.create = function (o) { + function F () {} + F.prototype = o + var newf = new F() + return newf + } + } + + if (!Object.keys) { + Object.keys = function (o) { + var a = [] + for (var i in o) if (o.hasOwnProperty(i)) a.push(i) + return a + } + } + + function checkBufferLength (parser) { + var maxAllowed = Math.max(sax.MAX_BUFFER_LENGTH, 10) + var maxActual = 0 + for (var i = 0, l = buffers.length; i < l; i++) { + var len = parser[buffers[i]].length + if (len > maxAllowed) { + // Text/cdata nodes can get big, and since they're buffered, + // we can get here under normal conditions. + // Avoid issues by emitting the text node now, + // so at least it won't get any bigger. + switch (buffers[i]) { + case 'textNode': + closeText(parser) + break + + case 'cdata': + emitNode(parser, 'oncdata', parser.cdata) + parser.cdata = '' + break + + case 'script': + emitNode(parser, 'onscript', parser.script) + parser.script = '' + break + + default: + error(parser, 'Max buffer length exceeded: ' + buffers[i]) + } + } + maxActual = Math.max(maxActual, len) + } + // schedule the next check for the earliest possible buffer overrun. + var m = sax.MAX_BUFFER_LENGTH - maxActual + parser.bufferCheckPosition = m + parser.position + } + + function clearBuffers (parser) { + for (var i = 0, l = buffers.length; i < l; i++) { + parser[buffers[i]] = '' + } + } + + function flushBuffers (parser) { + closeText(parser) + if (parser.cdata !== '') { + emitNode(parser, 'oncdata', parser.cdata) + parser.cdata = '' + } + if (parser.script !== '') { + emitNode(parser, 'onscript', parser.script) + parser.script = '' + } + } + + SAXParser.prototype = { + end: function () { end(this) }, + write: write, + resume: function () { this.error = null; return this }, + close: function () { return this.write(null) }, + flush: function () { flushBuffers(this) } + } + + var Stream + try { + Stream = require('stream').Stream + } catch (ex) { + Stream = function () {} + } + + var streamWraps = sax.EVENTS.filter(function (ev) { + return ev !== 'error' && ev !== 'end' + }) + + function createStream (strict, opt) { + return new SAXStream(strict, opt) + } + + function SAXStream (strict, opt) { + if (!(this instanceof SAXStream)) { + return new SAXStream(strict, opt) + } + + Stream.apply(this) + + this._parser = new SAXParser(strict, opt) + this.writable = true + this.readable = true + + var me = this + + this._parser.onend = function () { + me.emit('end') + } + + this._parser.onerror = function (er) { + me.emit('error', er) + + // if didn't throw, then means error was handled. + // go ahead and clear error, so we can write again. + me._parser.error = null + } + + this._decoder = null + + streamWraps.forEach(function (ev) { + Object.defineProperty(me, 'on' + ev, { + get: function () { + return me._parser['on' + ev] + }, + set: function (h) { + if (!h) { + me.removeAllListeners(ev) + me._parser['on' + ev] = h + return h + } + me.on(ev, h) + }, + enumerable: true, + configurable: false + }) + }) + } + + SAXStream.prototype = Object.create(Stream.prototype, { + constructor: { + value: SAXStream + } + }) + + SAXStream.prototype.write = function (data) { + if (typeof Buffer === 'function' && + typeof Buffer.isBuffer === 'function' && + Buffer.isBuffer(data)) { + if (!this._decoder) { + var SD = require('string_decoder').StringDecoder + this._decoder = new SD('utf8') + } + data = this._decoder.write(data) + } + + this._parser.write(data.toString()) + this.emit('data', data) + return true + } + + SAXStream.prototype.end = function (chunk) { + if (chunk && chunk.length) { + this.write(chunk) + } + this._parser.end() + return true + } + + SAXStream.prototype.on = function (ev, handler) { + var me = this + if (!me._parser['on' + ev] && streamWraps.indexOf(ev) !== -1) { + me._parser['on' + ev] = function () { + var args = arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments) + args.splice(0, 0, ev) + me.emit.apply(me, args) + } + } + + return Stream.prototype.on.call(me, ev, handler) + } + + // this really needs to be replaced with character classes. + // XML allows all manner of ridiculous numbers and digits. + var CDATA = '[CDATA[' + var DOCTYPE = 'DOCTYPE' + var XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace' + var XMLNS_NAMESPACE = 'http://www.w3.org/2000/xmlns/' + var rootNS = { xml: XML_NAMESPACE, xmlns: XMLNS_NAMESPACE } + + // http://www.w3.org/TR/REC-xml/#NT-NameStartChar + // This implementation works on strings, a single character at a time + // as such, it cannot ever support astral-plane characters (10000-EFFFF) + // without a significant breaking change to either this parser, or the + // JavaScript language. Implementation of an emoji-capable xml parser + // is left as an exercise for the reader. + var nameStart = /[:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/ + + var nameBody = /[:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u00B7\u0300-\u036F\u203F-\u2040.\d-]/ + + var entityStart = /[#:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/ + var entityBody = /[#:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u00B7\u0300-\u036F\u203F-\u2040.\d-]/ + + function isWhitespace (c) { + return c === ' ' || c === '\n' || c === '\r' || c === '\t' + } + + function isQuote (c) { + return c === '"' || c === '\'' + } + + function isAttribEnd (c) { + return c === '>' || isWhitespace(c) + } + + function isMatch (regex, c) { + return regex.test(c) + } + + function notMatch (regex, c) { + return !isMatch(regex, c) + } + + var S = 0 + sax.STATE = { + BEGIN: S++, // leading byte order mark or whitespace + BEGIN_WHITESPACE: S++, // leading whitespace + TEXT: S++, // general stuff + TEXT_ENTITY: S++, // & and such. + OPEN_WAKA: S++, // < + SGML_DECL: S++, // + SCRIPT: S++, //