2015-06-08 15:50:35 +00:00
/*
* Copyright ( c ) 2015 , Cryptonomex , Inc .
* All rights reserved .
*
* This source code is provided for evaluation in private test networks only , until September 8 , 2015. After this date , this license expires and
* the code may not be used , modified or distributed for any purpose . Redistribution and use in source and binary forms , with or without modification ,
* are permitted until September 8 , 2015 , provided that the following conditions are met :
*
* 1. The code and / or derivative works are used only for private test networks consisting of no more than 10 P2P nodes .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS " AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO ,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED TO ,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY ,
* WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
# include <graphene/witness/witness.hpp>
2015-06-18 16:44:26 +00:00
# include <graphene/chain/database.hpp>
2015-06-08 15:50:35 +00:00
# include <graphene/chain/witness_object.hpp>
# include <graphene/time/time.hpp>
2015-06-30 21:28:07 +00:00
# include <graphene/utilities/key_conversion.hpp>
2015-07-20 18:57:08 +00:00
# include <fc/smart_ref_impl.hpp>
2015-06-08 15:50:35 +00:00
# include <fc/thread/thread.hpp>
2015-08-26 22:59:19 +00:00
# include <iostream>
2015-06-08 15:50:35 +00:00
using namespace graphene : : witness_plugin ;
using std : : string ;
using std : : vector ;
2015-06-24 02:12:07 +00:00
namespace bpo = boost : : program_options ;
2015-08-26 22:59:19 +00:00
void new_chain_banner ( const graphene : : chain : : database & db )
{
std : : cerr < < " \n "
" ******************************** \n "
" * * \n "
" * ------- NEW CHAIN ------ * \n "
" * - Welcome to Graphene! - * \n "
" * ------------------------ * \n "
" * * \n "
" ******************************** \n "
" \n " ;
if ( db . get_slot_at_time ( graphene : : time : : now ( ) ) > 200 )
{
std : : cerr < < " Your genesis seems to have an old timestamp \n "
2015-08-28 18:12:54 +00:00
" Please consider using the --genesis-timestamp option to give your genesis a recent timestamp \n "
2015-08-26 22:59:19 +00:00
" \n "
;
}
return ;
}
2015-06-08 15:50:35 +00:00
void witness_plugin : : plugin_set_program_options (
boost : : program_options : : options_description & command_line_options ,
boost : : program_options : : options_description & config_file_options )
{
2015-07-02 05:52:45 +00:00
auto default_priv_key = fc : : ecc : : private_key : : regenerate ( fc : : sha256 : : hash ( std : : string ( " nathan " ) ) ) ;
2015-08-29 02:45:44 +00:00
string witness_id_example = fc : : json : : to_string ( chain : : witness_id_type ( 5 ) ) ;
2015-06-08 15:50:35 +00:00
command_line_options . add_options ( )
2015-07-13 13:47:03 +00:00
( " enable-stale-production " , bpo : : bool_switch ( ) - > notifier ( [ this ] ( bool e ) { _production_enabled = e ; } ) , " Enable block production, even if the chain is stale. " )
2015-07-13 19:07:36 +00:00
( " required-participation " , bpo : : bool_switch ( ) - > notifier ( [ this ] ( int e ) { _required_witness_participation = uint32_t ( e * GRAPHENE_1_PERCENT ) ; } ) , " Percent of witnesses (0-99) that must be participating in order to produce blocks " )
2015-06-08 15:50:35 +00:00
( " witness-id,w " , bpo : : value < vector < string > > ( ) - > composing ( ) - > multitoken ( ) ,
2015-07-06 17:35:11 +00:00
( " ID of witness controlled by this node (e.g. " + witness_id_example + " , quotes are required, may specify multiple times) " ) . c_str ( ) )
2015-06-08 15:50:35 +00:00
( " private-key " , bpo : : value < vector < string > > ( ) - > composing ( ) - > multitoken ( ) - >
2015-07-02 05:52:45 +00:00
DEFAULT_VALUE_VECTOR ( std : : make_pair ( chain : : public_key_type ( default_priv_key . get_public_key ( ) ) , graphene : : utilities : : key_to_wif ( default_priv_key ) ) ) ,
" Tuple of [PublicKey, WIF private key] (may specify multiple times) " )
2015-06-08 15:50:35 +00:00
;
config_file_options . add ( command_line_options ) ;
}
std : : string witness_plugin : : plugin_name ( ) const
{
return " witness " ;
}
void witness_plugin : : plugin_initialize ( const boost : : program_options : : variables_map & options )
2015-06-30 21:28:07 +00:00
{ try {
2015-08-31 18:23:14 +00:00
ilog ( " witness plugin: plugin_initialize() begin " ) ;
2015-06-08 15:50:35 +00:00
_options = & options ;
LOAD_VALUE_SET ( options , " witness-id " , _witnesses , chain : : witness_id_type )
2015-07-06 17:35:11 +00:00
if ( options . count ( " private-key " ) )
{
2015-06-30 21:28:07 +00:00
const std : : vector < std : : string > key_id_to_wif_pair_strings = options [ " private-key " ] . as < std : : vector < std : : string > > ( ) ;
for ( const std : : string & key_id_to_wif_pair_string : key_id_to_wif_pair_strings )
{
2015-07-02 05:52:45 +00:00
auto key_id_to_wif_pair = graphene : : app : : dejsonify < std : : pair < chain : : public_key_type , std : : string > > ( key_id_to_wif_pair_string ) ;
2015-07-02 16:11:43 +00:00
idump ( ( key_id_to_wif_pair ) ) ;
2015-06-30 21:28:07 +00:00
fc : : optional < fc : : ecc : : private_key > private_key = graphene : : utilities : : wif_to_key ( key_id_to_wif_pair . second ) ;
if ( ! private_key )
{
// the key isn't in WIF format; see if they are still passing the old native private key format. This is
// just here to ease the transition, can be removed soon
try
{
private_key = fc : : variant ( key_id_to_wif_pair . second ) . as < fc : : ecc : : private_key > ( ) ;
}
catch ( const fc : : exception & )
{
FC_THROW ( " Invalid WIF-format private key ${key_string} " , ( " key_string " , key_id_to_wif_pair . second ) ) ;
}
}
_private_keys [ key_id_to_wif_pair . first ] = * private_key ;
}
}
2015-08-31 18:23:14 +00:00
ilog ( " witness plugin: plugin_initialize() end " ) ;
2015-07-02 16:11:43 +00:00
} FC_LOG_AND_RETHROW ( ) }
2015-06-08 15:50:35 +00:00
void witness_plugin : : plugin_startup ( )
{ try {
2015-08-31 18:23:14 +00:00
ilog ( " witness plugin: plugin_startup() begin " ) ;
2015-07-10 18:17:13 +00:00
chain : : database & d = database ( ) ;
//Start NTP time client
graphene : : time : : now ( ) ;
2015-06-08 15:50:35 +00:00
if ( ! _witnesses . empty ( ) )
{
ilog ( " Launching block production for ${n} witnesses. " , ( " n " , _witnesses . size ( ) ) ) ;
app ( ) . set_block_production ( true ) ;
2015-09-10 16:08:01 +00:00
if ( _production_enabled )
{
if ( d . head_block_num ( ) = = 0 )
new_chain_banner ( d ) ;
_production_skip_flags | = graphene : : chain : : database : : skip_undo_history_check ;
}
2015-08-29 01:57:10 +00:00
schedule_production_loop ( ) ;
2015-06-08 15:50:35 +00:00
} else
elog ( " No witnesses configured! Please add witness IDs and private keys to configuration. " ) ;
2015-08-31 18:23:14 +00:00
ilog ( " witness plugin: plugin_startup() end " ) ;
2015-06-08 15:50:35 +00:00
} FC_CAPTURE_AND_RETHROW ( ) }
void witness_plugin : : plugin_shutdown ( )
{
graphene : : time : : shutdown_ntp_time ( ) ;
return ;
}
2015-08-29 01:57:10 +00:00
void witness_plugin : : schedule_production_loop ( )
2015-06-08 15:50:35 +00:00
{
2015-08-29 01:57:10 +00:00
//Schedule for the next second's tick regardless of chain state
2015-08-30 00:18:17 +00:00
// If we would wait less than 200ms, wait for the whole second.
fc : : time_point now = graphene : : time : : now ( ) ;
fc : : time_point_sec next_second ( now + fc : : microseconds ( 1200000 ) ) ;
2015-09-18 13:13:17 +00:00
//wdump( (now.time_since_epoch().count())(next_second) );
2015-08-29 01:57:10 +00:00
_block_production_task = fc : : schedule ( [ this ] { block_production_loop ( ) ; } ,
next_second , " Witness Block Production " ) ;
}
2015-06-08 15:50:35 +00:00
2015-08-29 01:57:10 +00:00
block_production_condition : : block_production_condition_enum witness_plugin : : block_production_loop ( )
{
block_production_condition : : block_production_condition_enum result ;
fc : : mutable_variant_object capture ;
try
{
result = maybe_produce_block ( capture ) ;
}
catch ( const fc : : canceled_exception & )
{
//We're trying to exit. Go ahead and let this one out.
throw ;
}
catch ( const fc : : exception & e )
{
elog ( " Got exception while generating block: \n ${e} " , ( " e " , e . to_detail_string ( ) ) ) ;
result = block_production_condition : : exception_producing_block ;
}
2015-06-08 15:50:35 +00:00
2015-08-29 01:57:10 +00:00
switch ( result )
{
case block_production_condition : : produced :
2015-09-01 20:31:08 +00:00
ilog ( " Generated block #${n} with timestamp ${t} at time ${c} " , ( capture ) ) ;
2015-08-29 01:57:10 +00:00
break ;
case block_production_condition : : not_synced :
ilog ( " Not producing block because production is disabled until we receive a recent block (see: --enable-stale-production) " ) ;
break ;
case block_production_condition : : not_my_turn :
ilog ( " Not producing block because it isn't my turn " ) ;
break ;
case block_production_condition : : not_time_yet :
2015-09-18 13:13:17 +00:00
// ilog("Not producing block because slot has not yet arrived");
2015-08-29 01:57:10 +00:00
break ;
case block_production_condition : : no_private_key :
2015-09-01 20:31:08 +00:00
ilog ( " Not producing block because I don't have the private key for ${scheduled_key} " , ( capture ) ) ;
2015-08-29 01:57:10 +00:00
break ;
case block_production_condition : : low_participation :
2015-09-01 20:31:08 +00:00
elog ( " Not producing block because node appears to be on a minority fork with only ${pct}% witness participation " , ( capture ) ) ;
2015-08-29 01:57:10 +00:00
break ;
case block_production_condition : : lag :
elog ( " Not producing block because node didn't wake up within 500ms of the slot time. " ) ;
break ;
case block_production_condition : : consecutive :
2015-08-29 02:45:44 +00:00
elog ( " Not producing block because the last block was generated by the same witness. \n This node is probably disconnected from the network so block production has been disabled. \n Disable this check with --allow-consecutive option. " ) ;
2015-08-29 01:57:10 +00:00
break ;
case block_production_condition : : exception_producing_block :
break ;
}
schedule_production_loop ( ) ;
return result ;
2015-06-08 15:50:35 +00:00
}
2015-08-29 01:57:10 +00:00
block_production_condition : : block_production_condition_enum witness_plugin : : maybe_produce_block ( fc : : mutable_variant_object & capture )
2015-06-08 15:50:35 +00:00
{
chain : : database & db = database ( ) ;
2015-08-30 00:18:17 +00:00
fc : : time_point now_fine = graphene : : time : : now ( ) ;
fc : : time_point_sec now = now_fine + fc : : microseconds ( 500000 ) ;
2015-06-08 15:50:35 +00:00
2015-08-29 01:57:10 +00:00
// If the next block production opportunity is in the present or future, we're synced.
if ( ! _production_enabled )
{
if ( db . get_slot_time ( 1 ) > = now )
_production_enabled = true ;
else
return block_production_condition : : not_synced ;
}
2015-06-08 15:50:35 +00:00
// is anyone scheduled to produce now or one second in the future?
2015-07-14 19:21:04 +00:00
uint32_t slot = db . get_slot_at_time ( now ) ;
2015-08-29 01:57:10 +00:00
if ( slot = = 0 )
2015-06-08 15:50:35 +00:00
{
2015-08-29 01:57:10 +00:00
capture ( " next_time " , db . get_slot_time ( 1 ) ) ;
return block_production_condition : : not_time_yet ;
}
2015-06-08 15:50:35 +00:00
2015-08-29 01:57:10 +00:00
//
// this assert should not fail, because now <= db.head_block_time()
// should have resulted in slot == 0.
//
// if this assert triggers, there is a serious bug in get_slot_at_time()
// which would result in allowing a later block to have a timestamp
// less than or equal to the previous block
//
assert ( now > db . head_block_time ( ) ) ;
2015-08-18 15:03:44 +00:00
2015-08-29 01:57:10 +00:00
graphene : : chain : : witness_id_type scheduled_witness = db . get_scheduled_witness ( slot ) ;
// we must control the witness scheduled to produce the next block.
if ( _witnesses . find ( scheduled_witness ) = = _witnesses . end ( ) )
{
capture ( " scheduled_witness " , scheduled_witness ) ;
return block_production_condition : : not_my_turn ;
}
2015-06-08 15:50:35 +00:00
2015-08-29 01:57:10 +00:00
fc : : time_point_sec scheduled_time = db . get_slot_time ( slot ) ;
graphene : : chain : : public_key_type scheduled_key = scheduled_witness ( db ) . signing_key ;
auto private_key_itr = _private_keys . find ( scheduled_key ) ;
2015-06-08 15:50:35 +00:00
2015-08-29 01:57:10 +00:00
if ( private_key_itr = = _private_keys . end ( ) )
{
capture ( " scheduled_key " , scheduled_key ) ;
return block_production_condition : : no_private_key ;
}
2015-06-08 15:50:35 +00:00
2015-08-29 01:57:10 +00:00
uint32_t prate = db . witness_participation_rate ( ) ;
if ( prate < _required_witness_participation )
{
capture ( " pct " , uint32_t ( 100 * uint64_t ( prate ) / GRAPHENE_1_PERCENT ) ) ;
return block_production_condition : : low_participation ;
}
2015-06-08 15:50:35 +00:00
2015-08-29 01:57:10 +00:00
if ( llabs ( ( scheduled_time - now ) . count ( ) ) > fc : : milliseconds ( 500 ) . count ( ) )
{
capture ( " scheduled_time " , scheduled_time ) ( " now " , now ) ;
return block_production_condition : : lag ;
}
2015-06-08 15:50:35 +00:00
2015-08-29 01:57:10 +00:00
auto block = db . generate_block (
scheduled_time ,
scheduled_witness ,
private_key_itr - > second ,
2015-09-10 16:08:01 +00:00
_production_skip_flags
2015-08-29 01:57:10 +00:00
) ;
capture ( " n " , block . block_num ( ) ) ( " t " , block . timestamp ) ( " c " , now ) ;
2015-09-15 12:38:27 +00:00
fc : : async ( [ this , block ] ( ) { p2p_node ( ) . broadcast ( net : : block_message ( block ) ) ; } ) ;
2015-08-29 01:57:10 +00:00
return block_production_condition : : produced ;
2015-06-08 15:50:35 +00:00
}