|
| 1 | + |
| 2 | +#include <hive/chain/hive_fwd.hpp> |
| 3 | + |
| 4 | +#include <hive/plugins/queen/queen_plugin.hpp> |
| 5 | +#include <hive/plugins/p2p/p2p_plugin.hpp> |
| 6 | + |
| 7 | +#include <hive/chain/database_exceptions.hpp> |
| 8 | +#include <hive/chain/witness_objects.hpp> |
| 9 | + |
| 10 | +#define DEFAULT_QUEEN_TARGET_BLOCK_SIZE 0 // max blocks allowed by witnesses |
| 11 | +#define DEFAULT_QUEEN_TARGET_TX_COUNT 0 // unlimited transactions |
| 12 | + |
| 13 | +namespace hive { namespace plugins { namespace queen { |
| 14 | + |
| 15 | +using namespace hive::protocol; |
| 16 | +using namespace hive::chain; |
| 17 | + |
| 18 | +namespace detail { |
| 19 | + |
| 20 | +class queen_plugin_impl |
| 21 | +{ |
| 22 | +public: |
| 23 | + queen_plugin_impl( queen_plugin& _plugin, appbase::application& app ) |
| 24 | + : _chain( app.get_plugin< hive::plugins::chain::chain_plugin >() ), |
| 25 | + _witness( app.get_plugin< hive::plugins::witness::witness_plugin >() ), |
| 26 | + _db( _chain.db() ), |
| 27 | + _self( _plugin ), theApp( app ) {} |
| 28 | + ~queen_plugin_impl() {} |
| 29 | + |
| 30 | + void on_post_apply_transaction( const transaction_notification& note ); |
| 31 | + void on_finish_push_block( const block_notification& note ); |
| 32 | + void on_fail_apply_block( const block_notification& note ); |
| 33 | + |
| 34 | + void prepare_for_new_block(); |
| 35 | + |
| 36 | + void print_stats(); |
| 37 | + |
| 38 | + chain::chain_plugin& _chain; |
| 39 | + witness::witness_plugin& _witness; |
| 40 | + chain::database& _db; |
| 41 | + queen_plugin& _self; |
| 42 | + appbase::application& theApp; |
| 43 | + |
| 44 | + uint32_t _target_block_size = DEFAULT_QUEEN_TARGET_BLOCK_SIZE; |
| 45 | + uint32_t _target_tx_count = DEFAULT_QUEEN_TARGET_TX_COUNT; |
| 46 | + |
| 47 | + uint32_t remaining_block_size = 0; |
| 48 | + uint32_t remaining_tx_count = 0; |
| 49 | + |
| 50 | + uint32_t blocks_observed = 0; |
| 51 | + uint32_t blocks_produced = 0; |
| 52 | + uint32_t transactions_observed = 0; |
| 53 | + uint32_t min_full_block_cycle_num = 0; |
| 54 | + uint32_t max_full_block_cycle_num = 0; |
| 55 | + fc::time_point end_of_last_block; |
| 56 | + |
| 57 | + fc::microseconds total_production_time; // sum of .exec.work from block stats of produced blocks |
| 58 | + fc::microseconds total_application_time; // sum of .exec.post from block stats of produced blocks |
| 59 | + fc::microseconds total_time; // sum of .exec.all from block stats of produced blocks |
| 60 | + fc::microseconds min_full_block_cycle_time = fc::microseconds::maximum(); |
| 61 | + fc::microseconds max_full_block_cycle_time; |
| 62 | + fc::microseconds total_full_block_cycle_time; |
| 63 | + |
| 64 | + boost::signals2::connection _post_apply_transaction_conn; |
| 65 | + boost::signals2::connection _finish_push_block_conn; |
| 66 | + boost::signals2::connection _fail_apply_block_conn; |
| 67 | +}; |
| 68 | + |
| 69 | +class queen_generate_block_flow_control final : public generate_block_flow_control |
| 70 | +{ |
| 71 | +public: |
| 72 | + queen_generate_block_flow_control( queen_plugin_impl& _this, const fc::time_point_sec _block_ts, |
| 73 | + const protocol::account_name_type& _wo, const fc::ecc::private_key& _key, uint32_t _skip ) |
| 74 | + : generate_block_flow_control( _block_ts, _wo, _key, _skip ), plugin( _this ) |
| 75 | + {} |
| 76 | + virtual ~queen_generate_block_flow_control() = default; |
| 77 | + |
| 78 | + virtual bool skip_transaction_reapplication() const { return true; } |
| 79 | + |
| 80 | + virtual void on_worker_done( appbase::application& app ) const override |
| 81 | + { |
| 82 | + stats.recalculate_times( get_block_timestamp() ); |
| 83 | + generate_block_flow_control::on_worker_done( app ); |
| 84 | + plugin.total_production_time += stats.get_work_time(); |
| 85 | + plugin.total_application_time += stats.get_cleanup_time(); |
| 86 | + plugin.total_time += stats.get_total_time(); |
| 87 | + } |
| 88 | + |
| 89 | +private: |
| 90 | + virtual const char* buffer_type() const override { return "queen"; } |
| 91 | + |
| 92 | + queen_plugin_impl& plugin; |
| 93 | +}; |
| 94 | + |
| 95 | +void queen_plugin_impl::on_post_apply_transaction( const chain::transaction_notification& note ) |
| 96 | +{ |
| 97 | + bool new_tx = _db.is_validating_one_tx(); |
| 98 | + if( new_tx || _db.is_reapplying_one_tx() ) |
| 99 | + { |
| 100 | + auto size = note.full_transaction->get_transaction_size(); |
| 101 | + if( new_tx ) |
| 102 | + ++transactions_observed; |
| 103 | + |
| 104 | + // we can't rely on change in _db._pending_tx_size because it will be updated after this call |
| 105 | + if( remaining_block_size > size && remaining_tx_count > 1 ) |
| 106 | + { |
| 107 | + remaining_block_size -= size; |
| 108 | + --remaining_tx_count; |
| 109 | + } |
| 110 | + else |
| 111 | + { |
| 112 | + // produce block and put it in write queue (it will be placed in front of potential |
| 113 | + // transactions and there should be no other blocks to process) |
| 114 | + const auto& data = _witness.get_production_data(); |
| 115 | + const auto generate_block_ctrl = std::make_shared< queen_generate_block_flow_control >( *this, |
| 116 | + data.next_slot_time, data.scheduled_witness, data.scheduled_private_key, chain::database::skip_nothing ); |
| 117 | + _chain.queue_generate_block_request( generate_block_ctrl ); //note - this call won't wait for block to be produced |
| 118 | + ++blocks_produced; |
| 119 | + } |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +void queen_plugin_impl::on_finish_push_block( const block_notification& note ) |
| 124 | +{ |
| 125 | + ++blocks_observed; |
| 126 | + prepare_for_new_block(); |
| 127 | + auto now = fc::time_point::now(); |
| 128 | + if( end_of_last_block != fc::time_point() ) |
| 129 | + { |
| 130 | + auto block_cycle_time = now - end_of_last_block; |
| 131 | + if( min_full_block_cycle_time > block_cycle_time ) |
| 132 | + { |
| 133 | + min_full_block_cycle_time = block_cycle_time; |
| 134 | + min_full_block_cycle_num = note.block_num; |
| 135 | + } |
| 136 | + if( max_full_block_cycle_time < block_cycle_time ) |
| 137 | + { |
| 138 | + max_full_block_cycle_time = block_cycle_time; |
| 139 | + max_full_block_cycle_num = note.block_num; |
| 140 | + } |
| 141 | + total_full_block_cycle_time += block_cycle_time; |
| 142 | + } |
| 143 | + end_of_last_block = now; |
| 144 | +} |
| 145 | + |
| 146 | +void queen_plugin_impl::on_fail_apply_block( const chain::block_notification& note ) |
| 147 | +{ |
| 148 | + // we could mark if the block is our own and only kill in such situation, however for the time |
| 149 | + // being the only sources of blocks are QUEEN itself (those blocks must not fail) and debug |
| 150 | + // plugin (those blocks also should not fail since they are produced from the same transactions |
| 151 | + // as QUEEN would, only earlier) |
| 152 | + elog( "Failed to apply block with QUEEN active. Closing." ); |
| 153 | + theApp.kill(); |
| 154 | +} |
| 155 | + |
| 156 | +void queen_plugin_impl::prepare_for_new_block() |
| 157 | +{ |
| 158 | + // note: it is important that this is called after witness plugin updated production data |
| 159 | + if( _db.head_block_time() >= _witness.get_production_data().next_slot_time ) |
| 160 | + { |
| 161 | + elog( "QUEEN: internal error - wrong order of calls. Closing." ); |
| 162 | + theApp.kill(); |
| 163 | + return; |
| 164 | + } |
| 165 | + |
| 166 | + // reset counters |
| 167 | + const auto& dgpo = _db.get_dynamic_global_properties(); |
| 168 | + uint32_t max_block_size = dgpo.maximum_block_size - 256; // 256 taken from trx_size_limit check in database.cpp |
| 169 | + if( _target_block_size ) |
| 170 | + remaining_block_size = std::min( _target_block_size, max_block_size ); |
| 171 | + else |
| 172 | + remaining_block_size = max_block_size; |
| 173 | + if( _target_tx_count ) |
| 174 | + remaining_tx_count = _target_tx_count; |
| 175 | + else |
| 176 | + remaining_tx_count = -1; |
| 177 | + |
| 178 | + // check for nearest block that we can produce |
| 179 | + int i = 0; |
| 180 | + for( ; i < HIVE_MAX_WITNESSES; ++i ) |
| 181 | + { |
| 182 | + const auto& production_data = _witness.get_production_data(); |
| 183 | + if( production_data.produce_in_next_slot ) |
| 184 | + break; |
| 185 | + // can't produce in that slot, check why: |
| 186 | + switch( production_data.condition ) |
| 187 | + { |
| 188 | + case witness::block_production_condition::no_private_key: |
| 189 | + dlog( "QUEEN: can't sign for ${w} that is next in schedule, skipping to next slot.", |
| 190 | + ( "w", production_data.scheduled_witness ) ); |
| 191 | + _witness.update_production_data( production_data.next_slot_time + HIVE_BLOCK_INTERVAL ); |
| 192 | + break; |
| 193 | + case witness::block_production_condition::low_participation: |
| 194 | + elog( "QUEEN: participation rate dropped below required level. Closing." ); |
| 195 | + theApp.kill(); |
| 196 | + break; |
| 197 | + default: |
| 198 | + // produced - how?! produce_in_next_slot is ( condition == produced ) |
| 199 | + // not_synced - queen should enable production in witness plugin |
| 200 | + // not_my_turn - queen should enable queen_mode in witness plugin |
| 201 | + // not_time_yet - only used by disabled witness plugin production loop |
| 202 | + // lag - only used by disabled witness plugin production loop |
| 203 | + // wait_for_genesis - only used by disabled witness plugin production loop |
| 204 | + // exception_producing_block - only used by disabled witness plugin production loop |
| 205 | + elog( "QUEEN: internal error - impossible block condition detected (${c}). Closing.", |
| 206 | + ( "c", (int)production_data.condition ) ); |
| 207 | + theApp.kill(); |
| 208 | + break; |
| 209 | + } |
| 210 | + } |
| 211 | + |
| 212 | + if( i == HIVE_MAX_WITNESSES ) |
| 213 | + { |
| 214 | + elog( "QUEEN: active schedule contains no witness that we can sign block for. Closing." ); |
| 215 | + theApp.kill(); |
| 216 | + } |
| 217 | +} |
| 218 | + |
| 219 | +void queen_plugin_impl::print_stats() |
| 220 | +{ |
| 221 | + if( _db._pending_tx_size > 0 ) |
| 222 | + ilog( "QUEEN ended with ${s} bytes of unused pending transactions.", ( "s", _db._pending_tx_size ) ); |
| 223 | + else |
| 224 | + ilog( "QUEEN ended cleanly." ); |
| 225 | + ilog( "Production stats for QUEEN:" ); |
| 226 | + ilog( "${t} new transactions encountered during work", |
| 227 | + ( "t", transactions_observed ) ); |
| 228 | + ilog( "${p} blocks out of ${a} were produced by QUEEN. Stopped at block #${b}", |
| 229 | + ( "p", blocks_produced )( "a", blocks_observed )( "b", _db.head_block_num() ) ); |
| 230 | + auto x = blocks_produced == 0 ? 1u : blocks_produced; |
| 231 | + ilog( "Blocks produced in average time of ${p}μs, applied in ${a}μs, average full processing time ${f}μs", |
| 232 | + ( "p", total_production_time.count() / x ) |
| 233 | + ( "a", total_application_time.count() / x ) |
| 234 | + ( "f", total_time.count() / x ) |
| 235 | + ); |
| 236 | + x = blocks_observed <= 1 ? 1u : blocks_observed - 1; // first block is not measured |
| 237 | + ilog( "Full block cycle: min = ${m}μs at #${mb}, avg = ${a}μs, max = ${x}μs at #${xb}", |
| 238 | + ( "m", min_full_block_cycle_time )( "mb", min_full_block_cycle_num ) |
| 239 | + ( "a", total_full_block_cycle_time.count() / x ) |
| 240 | + ( "x", max_full_block_cycle_time )( "xb", max_full_block_cycle_num ) |
| 241 | + ); |
| 242 | +} |
| 243 | + |
| 244 | +} // detail |
| 245 | + |
| 246 | +queen_plugin::queen_plugin() {} |
| 247 | + |
| 248 | +queen_plugin::~queen_plugin() {} |
| 249 | + |
| 250 | +void queen_plugin::set_program_options( |
| 251 | + boost::program_options::options_description& cli, |
| 252 | + boost::program_options::options_description& cfg |
| 253 | + ) |
| 254 | +{ |
| 255 | + // queen plugin uses configuration of witness plugin (witnesses, keys) |
| 256 | + cfg.add_options() |
| 257 | + ( "queen-block-size", bpo::value<uint32_t>()->default_value( DEFAULT_QUEEN_TARGET_BLOCK_SIZE ), "Size of blocks expected to be filled (or max allowed by witnesses). Default value 0 means max blocks." ) |
| 258 | + ( "queen-tx-count", bpo::value<uint32_t>()->default_value( DEFAULT_QUEEN_TARGET_TX_COUNT ), "Number of transactions in block. Default value 0 means no limit." ) |
| 259 | + ; |
| 260 | +} |
| 261 | + |
| 262 | +void queen_plugin::plugin_initialize( const boost::program_options::variables_map& options ) |
| 263 | +{ |
| 264 | + try |
| 265 | + { |
| 266 | + ilog( "Initializing queen plugin" ); |
| 267 | +#ifndef USE_ALTERNATE_CHAIN_ID |
| 268 | + FC_ASSERT( false, "Queen plugin cannot be used with mainnet, since it has to turn off p2p" ); |
| 269 | +#endif |
| 270 | + |
| 271 | + my = std::make_unique< detail::queen_plugin_impl >( *this, get_app() ); |
| 272 | + |
| 273 | + my->_chain.disable_p2p( false ); // also disables witness_plugin |
| 274 | + my->_witness.enable_queen_mode(); |
| 275 | + |
| 276 | + uint32_t max_size = options.at( "queen-block-size" ).as<uint32_t>(); |
| 277 | + uint32_t max_tx = options.at( "queen-tx-count" ).as<uint32_t>(); |
| 278 | + FC_ASSERT( max_size <= HIVE_MAX_BLOCK_SIZE - 256, "Queen mode block size cannot exceed ${s}", |
| 279 | + ( "s", HIVE_MAX_BLOCK_SIZE - 256 ) ); // 256 taken from trx_size_limit check in database.cpp |
| 280 | + my->_target_block_size = max_size; |
| 281 | + my->_target_tx_count = max_tx; |
| 282 | + |
| 283 | + my->_post_apply_transaction_conn = my->_db.add_post_apply_transaction_handler( |
| 284 | + [&]( const chain::transaction_notification& note ) { my->on_post_apply_transaction( note ); }, *this, 0 ); |
| 285 | + my->_finish_push_block_conn = my->_db.add_finish_push_block_handler( |
| 286 | + [&]( const chain::block_notification& note ) { my->on_finish_push_block( note ); }, *this, 0 ); |
| 287 | + my->_fail_apply_block_conn = my->_db.add_fail_apply_block_handler( |
| 288 | + [&]( const chain::block_notification& note ) { my->on_fail_apply_block( note ); }, *this, 0 ); |
| 289 | + } |
| 290 | + FC_CAPTURE_AND_RETHROW() |
| 291 | +} |
| 292 | + |
| 293 | +void queen_plugin::plugin_startup() |
| 294 | +{ |
| 295 | + std::string txs; |
| 296 | + if( my->_target_tx_count == 0 ) |
| 297 | + txs = "unlimited transactions"; |
| 298 | + else if( my->_target_tx_count == 1 ) |
| 299 | + txs = "single transaction"; |
| 300 | + else |
| 301 | + txs = std::to_string( my->_target_tx_count ) + " transactions"; |
| 302 | + if( my->_target_block_size == 0 ) |
| 303 | + ilog( "QUEEN enabled targeting full blocks (max allowed by witnesses) and ${txs}", ( txs ) ); |
| 304 | + else |
| 305 | + ilog( "QUEEN enabled targeting blocks of size ${s} and ${txs}", ( "s", my->_target_block_size )( txs ) ); |
| 306 | + |
| 307 | + my->prepare_for_new_block(); |
| 308 | +} |
| 309 | + |
| 310 | +void queen_plugin::plugin_shutdown() |
| 311 | +{ |
| 312 | + my->print_stats(); |
| 313 | + |
| 314 | + chain::util::disconnect_signal( my->_post_apply_transaction_conn ); |
| 315 | + chain::util::disconnect_signal( my->_finish_push_block_conn ); |
| 316 | + chain::util::disconnect_signal( my->_fail_apply_block_conn ); |
| 317 | +} |
| 318 | + |
| 319 | +} } } // hive::plugins::queen |
0 commit comments