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>
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-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-07-06 17:35:11 +00:00
string witness_id_example = fc : : json : : to_string ( chain : : witness_id_type ( ) ) ;
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-07-13 13:47:03 +00:00
( " allow-consecutive " , bpo : : bool_switch ( ) - > notifier ( [ this ] ( bool e ) { _consecutive_production_enabled = e ; } ) , " Allow block production, even if the last block was produced by the same witness. " )
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-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-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-07-10 18:17:13 +00:00
chain : : database & d = database ( ) ;
std : : set < chain : : witness_id_type > bad_wits ;
//Start NTP time client
graphene : : time : : now ( ) ;
for ( auto wit : _witnesses )
2015-06-08 15:50:35 +00:00
{
2015-07-10 18:17:13 +00:00
if ( d . find ( wit ) = = nullptr )
{
if ( app ( ) . is_finished_syncing ( ) )
{
elog ( " ERROR: Unable to find witness ${w}, even though syncing has finished. This witness will be ignored. " ,
( " w " , wit ) ) ;
continue ;
} else {
wlog ( " WARNING: Unable to find witness ${w}. Postponing initialization until syncing finishes. " ,
( " w " , wit ) ) ;
app ( ) . syncing_finished . connect ( [ this ] { plugin_startup ( ) ; } ) ;
return ;
}
}
auto signing_key = wit ( d ) . signing_key ;
2015-06-19 14:23:32 +00:00
if ( ! _private_keys . count ( signing_key ) )
2015-06-08 15:50:35 +00:00
{
2015-06-19 14:23:32 +00:00
// Check if it's a duplicate key of one I do have
bool found_duplicate = false ;
for ( const auto & private_key : _private_keys )
2015-07-02 05:52:45 +00:00
if ( chain : : public_key_type ( private_key . second . get_public_key ( ) ) = = signing_key )
2015-06-19 14:23:32 +00:00
{
ilog ( " Found duplicate key: ${k1} matches ${k2}; using this key to sign for ${w} " ,
( " k1 " , private_key . first ) ( " k2 " , signing_key ) ( " w " , wit ) ) ;
_private_keys [ signing_key ] = private_key . second ;
found_duplicate = true ;
break ;
}
if ( found_duplicate )
continue ;
2015-06-08 15:50:35 +00:00
elog ( " Unable to find key for witness ${w}. Removing it from my witnesses. " , ( " w " , wit ) ) ;
bad_wits . insert ( wit ) ;
}
}
for ( auto wit : bad_wits )
_witnesses . erase ( wit ) ;
if ( ! _witnesses . empty ( ) )
{
ilog ( " Launching block production for ${n} witnesses. " , ( " n " , _witnesses . size ( ) ) ) ;
app ( ) . set_block_production ( true ) ;
2015-07-10 18:17:13 +00:00
schedule_next_production ( d . get_global_properties ( ) . parameters ) ;
2015-06-08 15:50:35 +00:00
} else
elog ( " No witnesses configured! Please add witness IDs and private keys to configuration. " ) ;
} FC_CAPTURE_AND_RETHROW ( ) }
void witness_plugin : : plugin_shutdown ( )
{
graphene : : time : : shutdown_ntp_time ( ) ;
return ;
}
void witness_plugin : : schedule_next_production ( const graphene : : chain : : chain_parameters & global_parameters )
{
2015-07-13 19:41:50 +00:00
//Get next production time for *any* witness
2015-06-08 15:50:35 +00:00
auto block_interval = global_parameters . block_interval ;
fc : : time_point next_block_time = fc : : time_point_sec ( ) +
( graphene : : time : : now ( ) . sec_since_epoch ( ) / block_interval + 1 ) * block_interval ;
if ( graphene : : time : : ntp_time ( ) . valid ( ) )
next_block_time - = graphene : : time : : ntp_error ( ) ;
2015-07-13 19:41:50 +00:00
//Sleep until the next production time for *any* witness
2015-06-08 15:50:35 +00:00
_block_production_task = fc : : schedule ( [ this ] { block_production_loop ( ) ; } ,
next_block_time , " Witness Block Production " ) ;
}
void witness_plugin : : block_production_loop ( )
{
chain : : database & db = database ( ) ;
const auto & global_parameters = db . get_global_properties ( ) . parameters ;
// Is there a head block within a block interval of now? If so, we're synced and can begin production.
if ( ! _production_enabled & &
llabs ( ( db . head_block_time ( ) - graphene : : time : : now ( ) ) . to_seconds ( ) ) < = global_parameters . block_interval )
_production_enabled = true ;
// is anyone scheduled to produce now or one second in the future?
2015-07-14 19:21:04 +00:00
const fc : : time_point_sec now = graphene : : time : : now ( ) ;
uint32_t slot = db . get_slot_at_time ( now ) ;
2015-06-08 15:50:35 +00:00
graphene : : chain : : witness_id_type scheduled_witness = db . get_scheduled_witness ( slot ) . first ;
fc : : time_point_sec scheduled_time = db . get_slot_time ( slot ) ;
2015-07-02 05:52:45 +00:00
graphene : : chain : : public_key_type scheduled_key = scheduled_witness ( db ) . signing_key ;
2015-06-08 15:50:35 +00:00
auto is_scheduled = [ & ] ( )
{
// conditions needed to produce a block:
2015-08-18 15:03:44 +00:00
// we must control the witness scheduled to produce the next block.
if ( _witnesses . find ( scheduled_witness ) = = _witnesses . end ( ) ) {
2015-06-08 15:50:35 +00:00
return false ;
}
2015-08-18 15:03:44 +00:00
// we must know the private key corresponding to the witness's
// published block production key.
if ( _private_keys . find ( scheduled_key ) = = _private_keys . end ( ) ) {
elog ( " Not producing block because I don't have the private key for ${id}. " , ( " id " , scheduled_key ) ) ;
2015-07-13 13:47:03 +00:00
return false ;
}
2015-06-08 15:50:35 +00:00
// the next block must be scheduled after the head block.
// if this check fails, the local clock has not advanced far
// enough from the head block.
if ( slot = = 0 ) {
2015-08-18 15:03:44 +00:00
ilog ( " Not producing block because next slot time is in the future (likely a maitenance block). " ) ;
2015-06-08 15:50:35 +00:00
return false ;
}
2015-08-18 15:03:44 +00:00
// block production must be enabled (i.e. witness must be synced)
if ( ! _production_enabled )
{
wlog ( " Not producing block because production is disabled until we receive a recent block (see: --enable-stale-production) " ) ;
return false ;
}
uint32_t prate = db . witness_participation_rate ( ) ;
if ( prate < _required_witness_participation )
{
elog ( " Not producing block because node appears to be on a minority fork with only ${x}% witness participation " ,
( " x " , uint32_t ( 100 * uint64_t ( prate ) / GRAPHENE_1_PERCENT ) ) ) ;
2015-06-08 15:50:35 +00:00
return false ;
}
2015-07-14 19:21:04 +00:00
// the local clock must be at least 1 second ahead of head_block_time.
if ( ( now - db . head_block_time ( ) ) . to_seconds ( ) < GRAPHENE_MIN_BLOCK_INTERVAL ) {
2015-06-08 15:50:35 +00:00
elog ( " Not producing block because head block is less than a second old. " ) ;
return false ;
}
// the local clock must be within 500 milliseconds of
// the scheduled production time.
2015-07-14 19:21:04 +00:00
if ( llabs ( ( scheduled_time - now ) . count ( ) ) > fc : : milliseconds ( 500 ) . count ( ) ) {
2015-07-13 13:47:03 +00:00
elog ( " Not producing block because network time is not within 250ms of scheduled block time. " ) ;
2015-06-08 15:50:35 +00:00
return false ;
}
return true ;
} ;
wdump ( ( slot ) ( scheduled_witness ) ( scheduled_time ) ( now ) ) ;
if ( is_scheduled ( ) )
{
ilog ( " Witness ${id} production slot has arrived; generating a block now... " , ( " id " , scheduled_witness ) ) ;
try
{
2015-07-13 13:47:03 +00:00
FC_ASSERT ( _consecutive_production_enabled | | db . get_dynamic_global_properties ( ) . current_witness ! = scheduled_witness ,
" Last block was generated by the same witness, this node is probably disconnected from the network so block production "
" has been disabled. Disable this check with --allow-consecutive option. " ) ;
2015-06-08 15:50:35 +00:00
auto block = db . generate_block (
scheduled_time ,
scheduled_witness ,
2015-06-18 16:44:26 +00:00
_private_keys [ scheduled_key ] ,
graphene : : chain : : database : : skip_nothing
2015-06-08 15:50:35 +00:00
) ;
ilog ( " Generated block #${n} with timestamp ${t} at time ${c} " ,
( " n " , block . block_num ( ) ) ( " t " , block . timestamp ) ( " c " , now ) ) ;
p2p_node ( ) . broadcast ( net : : block_message ( block ) ) ;
}
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 ( ) ) ) ;
}
}
schedule_next_production ( global_parameters ) ;
}