Skip to content

Commit d7f5831

Browse files
ABWvogel76
ABW
authored andcommitted
add second version of implementation of queen
This time it is a separate plugin, but still controls witness a bit and reuses its code and settings.
1 parent d241546 commit d7f5831

File tree

5 files changed

+665
-0
lines changed

5 files changed

+665
-0
lines changed
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
file(GLOB HEADERS "include/hive/plugins/queen/*.hpp")
2+
3+
add_library( queen_plugin
4+
queen_plugin.cpp
5+
${HEADERS}
6+
)
7+
8+
target_link_libraries( queen_plugin witness_plugin chain_plugin )
9+
target_include_directories( queen_plugin
10+
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )
11+
12+
if( CLANG_TIDY_EXE )
13+
set_target_properties(
14+
queen_plugin PROPERTIES
15+
CXX_CLANG_TIDY "${DO_CLANG_TIDY}"
16+
)
17+
endif( CLANG_TIDY_EXE )
18+
19+
install( TARGETS
20+
queen_plugin
21+
22+
RUNTIME DESTINATION bin
23+
LIBRARY DESTINATION lib
24+
ARCHIVE DESTINATION lib
25+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#pragma once
2+
#include <hive/chain/hive_fwd.hpp>
3+
4+
#include <hive/plugins/chain/chain_plugin.hpp>
5+
#include <hive/plugins/witness/witness_plugin.hpp>
6+
7+
#define HIVE_QUEEN_PLUGIN_NAME "queen"
8+
9+
10+
namespace hive { namespace plugins { namespace queen {
11+
12+
namespace detail { class queen_plugin_impl; }
13+
14+
using namespace appbase;
15+
16+
class queen_plugin : public appbase::plugin< queen_plugin >
17+
{
18+
public:
19+
queen_plugin();
20+
virtual ~queen_plugin();
21+
22+
APPBASE_PLUGIN_REQUIRES( (hive::plugins::chain::chain_plugin)(hive::plugins::witness::witness_plugin) )
23+
24+
static const std::string& name() { static std::string name = HIVE_QUEEN_PLUGIN_NAME; return name; }
25+
26+
virtual void set_program_options(
27+
options_description& cli,
28+
options_description& cfg ) override;
29+
virtual void plugin_initialize( const variables_map& options ) override;
30+
virtual void plugin_startup() override;
31+
virtual void plugin_shutdown() override;
32+
33+
private:
34+
std::unique_ptr< detail::queen_plugin_impl > my;
35+
};
36+
37+
} } } //hive::plugins::queen

libraries/plugins/queen/plugin.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"plugin_name": "queen",
3+
"plugin_namespace": "queen",
4+
"plugin_project": "queen_plugin"
5+
}
+319
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
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

Comments
 (0)