2020-07-27 07:54:02 +02:00
# include <array>
# include <mutex>
# include <chrono>
2020-10-24 00:49:42 +02:00
# include "epee/wipeable_string.h"
# include "epee/memwipe.h"
# include "epee/misc_log_ex.h"
2020-08-12 10:22:23 +02:00
# include "common/random.h"
2020-07-27 07:54:02 +02:00
# include "cryptonote_core.h"
# include "cryptonote_basic/hardfork.h"
# include "service_node_list.h"
# include "service_node_quorum_cop.h"
# include "service_node_rules.h"
2020-08-18 03:21:38 +02:00
extern " C "
{
# include <sodium/crypto_generichash.h>
} ;
2020-08-06 09:21:00 +02:00
# undef LOKI_DEFAULT_LOG_CATEGORY
# define LOKI_DEFAULT_LOG_CATEGORY "pulse"
2020-09-18 18:02:09 +02:00
// Deliberately makes pulse communications flakey for testing purposes:
//#define PULSE_TEST_CODE
2020-07-27 07:54:02 +02:00
enum struct round_state
{
2020-08-14 03:46:31 +02:00
null_state ,
2020-08-10 11:37:17 +02:00
wait_for_next_block ,
prepare_for_round ,
2020-07-27 07:54:02 +02:00
wait_for_round ,
2020-08-10 11:37:17 +02:00
2020-08-14 03:46:31 +02:00
send_and_wait_for_handshakes ,
2020-08-10 11:37:17 +02:00
2020-08-14 03:46:31 +02:00
send_handshake_bitsets ,
2020-08-05 09:32:18 +02:00
wait_for_handshake_bitsets ,
2020-08-10 11:37:17 +02:00
2020-08-14 03:46:31 +02:00
send_block_template ,
2020-08-05 06:31:17 +02:00
wait_for_block_template ,
2020-08-11 10:14:17 +02:00
2020-08-14 03:46:31 +02:00
send_and_wait_for_random_value_hashes ,
send_and_wait_for_random_value ,
send_and_wait_for_signed_blocks ,
2020-07-27 07:54:02 +02:00
} ;
2020-08-05 06:31:17 +02:00
constexpr std : : string_view round_state_string ( round_state state )
{
switch ( state )
{
2020-08-14 03:46:31 +02:00
case round_state : : null_state : return " XX Null State " sv ;
2020-08-10 11:37:17 +02:00
case round_state : : wait_for_next_block : return " Wait For Next Block " sv ;
case round_state : : prepare_for_round : return " Prepare For Round " sv ;
2020-08-05 06:31:17 +02:00
case round_state : : wait_for_round : return " Wait For Round " sv ;
2020-08-10 11:37:17 +02:00
2020-08-14 03:46:31 +02:00
case round_state : : send_and_wait_for_handshakes : return " Send & Wait For Handshakes " sv ;
2020-08-10 11:37:17 +02:00
2020-08-14 03:46:31 +02:00
case round_state : : send_handshake_bitsets : return " Send Validator Handshake Bitsets " sv ;
2020-08-05 09:32:18 +02:00
case round_state : : wait_for_handshake_bitsets : return " Wait For Validator Handshake Bitsets " sv ;
2020-08-10 11:37:17 +02:00
2020-08-14 03:46:31 +02:00
case round_state : : send_block_template : return " Send Block Template " sv ;
2020-08-05 06:31:17 +02:00
case round_state : : wait_for_block_template : return " Wait For Block Template " sv ;
2020-08-11 10:14:17 +02:00
2020-08-14 03:46:31 +02:00
case round_state : : send_and_wait_for_random_value_hashes : return " Send & Wait For Random Value Hash " sv ;
case round_state : : send_and_wait_for_random_value : return " Send & Wait For Random Value " sv ;
case round_state : : send_and_wait_for_signed_blocks : return " Send & Wait For Signed Blocks " sv ;
2020-08-05 06:31:17 +02:00
}
2020-08-06 09:21:00 +02:00
return " Invalid2 " sv ;
2020-08-05 06:31:17 +02:00
}
2020-08-10 11:37:17 +02:00
enum struct sn_type
{
none ,
producer ,
validator ,
} ;
2020-08-13 08:19:55 +02:00
enum struct queueing_state
{
2020-08-13 10:38:04 +02:00
empty ,
received ,
processed ,
2020-08-13 08:19:55 +02:00
} ;
2020-08-18 02:51:19 +02:00
template < typename T >
using quorum_array = std : : array < T , service_nodes : : PULSE_QUORUM_NUM_VALIDATORS > ;
2020-08-14 05:27:14 +02:00
// Stores message for quorumnet per stage. Some validators may reach later
// stages before we arrive at that stage. To properly validate messages we also
// need to wait until we arrive at the same stage such that we have received all
// the necessary information to do so on Quorumnet.
2020-08-13 08:19:55 +02:00
struct message_queue
{
2020-08-18 02:51:19 +02:00
quorum_array < std : : pair < pulse : : message , queueing_state > > buffer ;
2020-08-13 08:19:55 +02:00
size_t count ;
} ;
2020-08-13 10:38:04 +02:00
struct pulse_wait_stage
2020-08-13 08:19:55 +02:00
{
2020-08-13 10:38:04 +02:00
message_queue queue ; // For messages from later stages that arrived before we reached that stage
2020-08-13 10:53:11 +02:00
uint16_t bitset ; // Bitset of validators that we received a message from for this stage
2020-08-13 10:38:04 +02:00
uint16_t msgs_received ; // Number of unique messages received in the stage
pulse : : time_point end_time ; // Time at which the stage ends
2020-08-13 08:19:55 +02:00
} ;
2020-08-14 03:46:31 +02:00
template < typename T >
struct pulse_send_stage
{
2020-08-14 05:27:14 +02:00
T data ; // Data that must be sent to Nodes via Quorumnet
bool sent ; // When true, data has been sent via Quorumnet once already.
2020-08-14 03:46:31 +02:00
bool one_time_only ( )
{
if ( sent ) return false ;
sent = true ;
return true ;
}
} ;
2020-09-10 11:09:27 +02:00
struct round_history
{
uint64_t height ;
uint8_t round ;
crypto : : hash top_block_hash ;
service_nodes : : quorum quorum ;
} ;
2020-07-27 07:54:02 +02:00
struct round_context
{
2020-09-10 11:09:27 +02:00
// Store the recent history of quorums in the past to allow validating late
// arriving messages and allow printing out the correct response ('error
// unknown message origin' or 'ok to ignore').
std : : array < round_history , 3 > quorum_history ;
size_t quorum_history_index ;
2020-07-27 07:54:02 +02:00
struct
{
2020-08-14 05:27:14 +02:00
uint64_t height ; // Current blockchain height that Pulse wants to generate a block for
crypto : : hash top_hash ; // Latest block hash included in signatures for rejecting out of date nodes
pulse : : time_point round_0_start_time ; // When round 0 should start and subsequent round timings are derived from.
2020-08-10 11:37:17 +02:00
} wait_for_next_block ;
2020-07-27 07:54:02 +02:00
struct
{
2020-08-14 05:27:14 +02:00
bool queue_for_next_round ; // When set to true, invoking prepare_for_round(...) will wait for (round + 1)
uint8_t round ; // The next round the Pulse ceremony will generate a block for
service_nodes : : quorum quorum ; // The block producer/validator participating in the next round
sn_type participant ; // Is this daemon a block producer, validator or non participant.
size_t my_quorum_position ; // Position in the quorum, 0 if producer or neither, or [0, PULSE_QUORUM_NUM_VALIDATORS) if a validator
std : : string node_name ; // Short-hand string for describing the node in logs, i.e. V[0] for validator 0 or W[0] for the producer.
pulse : : time_point start_time ; // When the round starts
2020-08-10 11:37:17 +02:00
} prepare_for_round ;
2020-07-28 08:56:11 +02:00
2020-08-13 10:38:04 +02:00
struct
{
2020-08-13 13:09:04 +02:00
struct
{
2020-08-18 02:51:19 +02:00
bool sent ; // When true, handshake sent and waiting for other handshakes
quorum_array < bool > data ; // Received data from messages from Quorumnet
2020-08-13 13:09:04 +02:00
pulse_wait_stage stage ;
2020-08-14 03:46:31 +02:00
} send_and_wait_for_handshakes ;
2020-08-13 10:38:04 +02:00
2020-08-13 13:09:04 +02:00
struct
{
2020-08-18 02:51:19 +02:00
quorum_array < std : : optional < uint16_t > > data ;
2020-08-13 13:09:04 +02:00
pulse_wait_stage stage ;
2020-07-27 07:54:02 +02:00
2020-08-14 05:27:14 +02:00
uint16_t best_bitset ; // The most agreed upon validators for participating in rounds. Value is set when all handshake bitsets are received.
uint16_t best_count ; // How many validators agreed upon the best bitset.
2020-08-14 03:46:31 +02:00
} wait_for_handshake_bitsets ;
2020-08-05 06:31:17 +02:00
2020-08-13 13:09:04 +02:00
struct
{
2020-08-14 05:27:14 +02:00
cryptonote : : block block ; // The block template with the best validator bitset and Pulse round applied to it.
2020-08-18 02:51:19 +02:00
pulse_wait_stage stage ;
2020-08-13 13:09:04 +02:00
} wait_for_block_template ;
2020-08-06 09:21:00 +02:00
2020-08-13 13:09:04 +02:00
struct
{
2020-08-14 03:46:31 +02:00
pulse_send_stage < crypto : : hash > send ;
struct
{
2020-08-18 02:51:19 +02:00
quorum_array < std : : optional < crypto : : hash > > data ;
2020-08-14 03:46:31 +02:00
pulse_wait_stage stage ;
} wait ;
} random_value_hashes ;
2020-08-11 10:14:17 +02:00
2020-08-13 13:09:04 +02:00
struct
{
2020-08-14 03:46:31 +02:00
pulse_send_stage < cryptonote : : pulse_random_value > send ;
2020-08-13 10:38:04 +02:00
2020-08-14 03:46:31 +02:00
struct
{
2020-08-18 02:51:19 +02:00
quorum_array < std : : optional < cryptonote : : pulse_random_value > > data ;
2020-08-14 03:46:31 +02:00
pulse_wait_stage stage ;
} wait ;
} random_value ;
2020-08-12 08:30:33 +02:00
2020-08-13 13:09:04 +02:00
struct
{
2020-09-10 06:14:29 +02:00
pulse_send_stage < crypto : : signature > send ;
cryptonote : : block final_block ;
2020-08-12 10:22:23 +02:00
2020-08-14 03:46:31 +02:00
struct
{
2020-08-18 02:51:19 +02:00
quorum_array < std : : optional < crypto : : signature > > data ;
2020-08-14 03:46:31 +02:00
pulse_wait_stage stage ;
} wait ;
} signed_block ;
2020-08-13 13:09:04 +02:00
} transient ;
2020-08-13 10:38:04 +02:00
2020-08-10 11:37:17 +02:00
round_state state ;
2020-07-27 07:54:02 +02:00
} ;
2020-08-10 11:37:17 +02:00
static round_context context ;
2020-07-27 07:54:02 +02:00
namespace
{
2020-08-18 03:30:58 +02:00
crypto : : hash blake2b_hash ( void const * data , size_t size )
{
crypto : : hash result = { } ;
static_assert ( sizeof ( result ) = = crypto_generichash_BYTES ) ;
crypto_generichash ( reinterpret_cast < unsigned char * > ( result . data ) , sizeof ( result ) , reinterpret_cast < unsigned char const * > ( data ) , size , nullptr /*key*/ , 0 /*key length*/ ) ;
return result ;
}
2020-08-06 09:21:00 +02:00
std : : string log_prefix ( round_context const & context )
2020-08-03 09:42:48 +02:00
{
std : : stringstream result ;
2020-08-10 11:37:17 +02:00
result < < " Pulse B " < < context . wait_for_next_block . height < < " R " ;
if ( context . state > = round_state : : prepare_for_round )
result < < + context . prepare_for_round . round ;
else
result < < " 0 " ;
result < < " : " ;
if ( context . prepare_for_round . node_name . size ( ) ) result < < context . prepare_for_round . node_name < < " " ;
2020-08-12 08:30:33 +02:00
result < < " ' " < < round_state_string ( context . state ) < < " ' " ;
2020-08-03 09:42:48 +02:00
return result . str ( ) ;
}
2020-08-14 03:52:03 +02:00
std : : bitset < sizeof ( uint16_t ) * 8 > bitset_view16 ( uint16_t val )
{
std : : bitset < sizeof ( uint16_t ) * 8 > result = val ;
return result ;
}
2020-08-13 10:56:40 +02:00
//
// NOTE: pulse::message Utiliities
//
2020-08-13 12:26:23 +02:00
pulse : : message msg_init_from_context ( round_context const & context )
{
pulse : : message result = { } ;
result . quorum_position = context . prepare_for_round . my_quorum_position ;
result . round = context . prepare_for_round . round ;
return result ;
}
2020-09-10 06:14:29 +02:00
// Generate the hash necessary for signing a message. All fields of the 'msg'
// must have been set for the type of the message except the signature for the
// hash to be generated correctly.
2020-09-10 11:09:27 +02:00
crypto : : hash msg_signature_hash ( crypto : : hash const & top_block_hash , pulse : : message const & msg )
2020-08-11 10:14:17 +02:00
{
crypto : : hash result = { } ;
switch ( msg . type )
{
case pulse : : message_type : : invalid :
assert ( " Invalid Code Path " = = nullptr ) ;
break ;
case pulse : : message_type : : handshake :
{
2020-09-10 11:09:27 +02:00
auto buf = tools : : memcpy_le ( top_block_hash . data , msg . quorum_position , msg . round ) ;
2020-08-18 03:30:58 +02:00
result = blake2b_hash ( buf . data ( ) , buf . size ( ) ) ;
2020-08-11 10:14:17 +02:00
}
break ;
case pulse : : message_type : : handshake_bitset :
{
2020-09-10 11:09:27 +02:00
auto buf = tools : : memcpy_le ( msg . handshakes . validator_bitset , top_block_hash . data , msg . quorum_position , msg . round ) ;
2020-08-18 03:30:58 +02:00
result = blake2b_hash ( buf . data ( ) , buf . size ( ) ) ;
2020-08-11 10:14:17 +02:00
}
break ;
case pulse : : message_type : : block_template :
2020-08-27 04:30:03 +02:00
{
crypto : : hash block_hash = blake2b_hash ( msg . block_template . blob . data ( ) , msg . block_template . blob . size ( ) ) ;
auto buf = tools : : memcpy_le ( msg . round , block_hash . data ) ;
result = blake2b_hash ( buf . data ( ) , buf . size ( ) ) ;
}
2020-08-11 10:14:17 +02:00
break ;
case pulse : : message_type : : random_value_hash :
{
2020-09-10 11:09:27 +02:00
auto buf = tools : : memcpy_le ( top_block_hash . data , msg . quorum_position , msg . round , msg . random_value_hash . hash . data ) ;
2020-08-18 03:30:58 +02:00
result = blake2b_hash ( buf . data ( ) , buf . size ( ) ) ;
2020-08-11 10:14:17 +02:00
}
break ;
2020-08-12 08:30:33 +02:00
case pulse : : message_type : : random_value :
{
2020-09-10 11:09:27 +02:00
auto buf = tools : : memcpy_le ( top_block_hash . data , msg . quorum_position , msg . round , msg . random_value . value . data ) ;
2020-08-18 03:30:58 +02:00
result = blake2b_hash ( buf . data ( ) , buf . size ( ) ) ;
2020-08-12 08:30:33 +02:00
}
break ;
2020-08-12 10:22:23 +02:00
case pulse : : message_type : : signed_block :
2020-09-10 06:14:29 +02:00
{
crypto : : signature const & final_signature = msg . signed_block . signature_of_final_block_hash ;
2020-09-10 11:09:27 +02:00
auto buf = tools : : memcpy_le ( top_block_hash . data , msg . quorum_position , msg . round , final_signature . c . data , final_signature . r . data ) ;
2020-09-10 06:14:29 +02:00
result = blake2b_hash ( buf . data ( ) , buf . size ( ) ) ;
}
2020-08-12 10:22:23 +02:00
break ;
2020-08-11 10:14:17 +02:00
}
return result ;
}
2020-08-13 10:56:40 +02:00
// Generate a helper string that describes the origin of the message, i.e.
2020-08-27 04:30:03 +02:00
// 'Signed Block' at round 2 from 6:f9337ffc8bc30baf3fca92a13fa5a3a7ab7c93e69acb7136906e7feae9d3e769
2020-08-13 10:56:40 +02:00
// or
2020-08-27 04:30:03 +02:00
// <Message Type> at round <Round> from <Validator Index>:<Validator Public Key>
2020-08-13 10:56:40 +02:00
std : : string msg_source_string ( round_context const & context , pulse : : message const & msg )
2020-08-12 08:30:33 +02:00
{
if ( msg . quorum_position > = context . prepare_for_round . quorum . validators . size ( ) ) return " XX " ;
std : : stringstream stream ;
2020-08-27 04:30:03 +02:00
stream < < " ' " < < message_type_string ( msg . type ) < < " at round " < < + msg . round < < " from " < < msg . quorum_position ;
2020-08-12 08:30:33 +02:00
if ( context . state > = round_state : : prepare_for_round )
2020-08-13 12:26:23 +02:00
{
2020-08-14 05:27:14 +02:00
if ( msg . quorum_position < context . prepare_for_round . quorum . validators . size ( ) )
{
crypto : : public_key const & key = context . prepare_for_round . quorum . validators [ msg . quorum_position ] ;
2020-08-14 09:07:01 +02:00
stream < < " : " < < key ;
2020-08-14 05:27:14 +02:00
}
2020-08-13 12:26:23 +02:00
}
2020-08-12 08:30:33 +02:00
return stream . str ( ) ;
}
2020-09-10 11:09:27 +02:00
bool msg_signature_check ( pulse : : message const & msg , crypto : : hash const & top_block_hash , service_nodes : : quorum const & quorum , std : : string * error )
2020-07-27 07:54:02 +02:00
{
2020-09-10 11:09:27 +02:00
std : : stringstream stream ;
LOKI_DEFER {
if ( error ) * error = stream . str ( ) ;
} ;
2020-08-11 10:14:17 +02:00
// Get Service Node Key
2020-08-11 07:56:33 +02:00
crypto : : public_key const * key = nullptr ;
switch ( msg . type )
2020-08-10 11:37:17 +02:00
{
2020-08-11 07:56:33 +02:00
case pulse : : message_type : : invalid :
2020-08-10 11:37:17 +02:00
{
2020-08-11 07:56:33 +02:00
assert ( " Invalid Code Path " = = nullptr ) ;
2020-09-10 11:09:27 +02:00
if ( error ) stream < < log_prefix ( context ) < < " Unhandled message type ' " < < pulse : : message_type_string ( msg . type ) < < " ' can not verify signature. " ;
2020-08-11 07:56:33 +02:00
return false ;
2020-08-10 11:37:17 +02:00
}
2020-08-11 07:56:33 +02:00
break ;
2020-08-10 11:37:17 +02:00
2020-08-18 02:53:34 +02:00
case pulse : : message_type : : handshake : [[fallthrough]] ;
case pulse : : message_type : : handshake_bitset : [[fallthrough]] ;
case pulse : : message_type : : random_value_hash : [[fallthrough]] ;
case pulse : : message_type : : random_value : [[fallthrough]] ;
2020-08-12 10:22:23 +02:00
case pulse : : message_type : : signed_block :
2020-08-10 11:37:17 +02:00
{
2020-08-11 07:56:33 +02:00
if ( msg . quorum_position > = static_cast < int > ( quorum . validators . size ( ) ) )
{
2020-09-10 11:09:27 +02:00
if ( error ) stream < < log_prefix ( context ) < < " Quorum position " < < msg . quorum_position < < " in Pulse message indexes oob " ;
2020-08-11 07:56:33 +02:00
return false ;
}
key = & quorum . validators [ msg . quorum_position ] ;
2020-08-10 11:37:17 +02:00
}
2020-08-11 07:56:33 +02:00
break ;
2020-08-10 11:37:17 +02:00
2020-08-11 07:56:33 +02:00
case pulse : : message_type : : block_template :
2020-08-10 11:37:17 +02:00
{
2020-08-11 07:56:33 +02:00
if ( msg . quorum_position ! = 0 )
{
2020-09-10 11:09:27 +02:00
if ( error ) stream < < log_prefix ( context ) < < " Quorum position " < < msg . quorum_position < < " in Pulse message indexes oob " ;
2020-08-11 07:56:33 +02:00
return false ;
}
2020-08-11 10:14:17 +02:00
key = & context . prepare_for_round . quorum . workers [ 0 ] ;
2020-08-10 11:37:17 +02:00
}
2020-08-11 07:56:33 +02:00
break ;
}
2020-09-10 11:09:27 +02:00
if ( ! crypto : : check_signature ( msg_signature_hash ( top_block_hash , msg ) , * key , msg . signature ) )
2020-08-11 07:56:33 +02:00
{
2020-09-10 11:09:27 +02:00
if ( error ) stream < < log_prefix ( context ) < < " Signature for " < < msg_source_string ( context , msg ) < < " at height " < < context . wait_for_next_block . height < < " ; is invalid " ;
2020-08-11 07:56:33 +02:00
return false ;
2020-08-10 11:37:17 +02:00
}
2020-08-11 07:56:33 +02:00
return true ;
}
2020-08-13 10:56:40 +02:00
//
// NOTE: round_context Utilities
//
// Construct a pulse::message for sending the handshake bit or bitset.
2020-08-11 10:42:18 +02:00
void relay_validator_handshake_bit_or_bitset ( round_context const & context , void * quorumnet_state , service_nodes : : service_node_keys const & key , bool sending_bitset )
{
assert ( context . prepare_for_round . participant = = sn_type : : validator ) ;
2020-08-13 08:19:55 +02:00
// Message
2020-08-13 12:26:23 +02:00
pulse : : message msg = msg_init_from_context ( context ) ;
2020-08-11 10:42:18 +02:00
if ( sending_bitset )
{
2020-08-13 08:19:55 +02:00
msg . type = pulse : : message_type : : handshake_bitset ;
// Generate the bitset from our received handshakes.
2020-08-14 03:46:31 +02:00
auto const & quorum = context . transient . send_and_wait_for_handshakes . data ;
2020-08-13 10:38:04 +02:00
for ( size_t quorum_index = 0 ; quorum_index < quorum . size ( ) ; quorum_index + + )
if ( bool received = quorum [ quorum_index ] ; received )
2020-08-13 08:19:55 +02:00
msg . handshakes . validator_bitset | = ( 1 < < quorum_index ) ;
2020-08-11 10:42:18 +02:00
}
else
{
msg . type = pulse : : message_type : : handshake ;
}
2020-09-10 11:09:27 +02:00
crypto : : generate_signature ( msg_signature_hash ( context . wait_for_next_block . top_hash , msg ) , key . pub , key . key , msg . signature ) ;
2020-08-14 05:07:17 +02:00
handle_message ( quorumnet_state , msg ) ; // Add our own. We receive our own msg for the first time which also triggers us to relay.
2020-08-11 10:42:18 +02:00
}
2020-08-13 10:56:40 +02:00
// Check the stage's queue for any messages that we received early and process
// them if any. Any messages in the queue that we haven't received yet will also
// be relayed to the quorum.
2020-08-13 10:38:04 +02:00
void handle_messages_received_early_for ( pulse_wait_stage & stage , void * quorumnet_state )
2020-08-13 08:40:10 +02:00
{
2020-08-13 10:38:04 +02:00
if ( ! stage . queue . count )
return ;
for ( auto & [ msg , queued ] : stage . queue . buffer )
2020-08-13 08:40:10 +02:00
{
2020-08-13 10:38:04 +02:00
if ( queued = = queueing_state : : received )
2020-08-13 08:40:10 +02:00
{
2020-08-13 10:38:04 +02:00
pulse : : handle_message ( quorumnet_state , msg ) ;
queued = queueing_state : : processed ;
2020-08-13 08:40:10 +02:00
}
}
}
2020-08-13 10:56:40 +02:00
// In Pulse, after the block template and validators are locked in, enforce that
// all participating validators are doing their job in the stage.
2020-08-13 10:53:11 +02:00
bool enforce_validator_participation_and_timeouts ( round_context const & context ,
pulse_wait_stage const & stage ,
2020-09-08 05:56:01 +02:00
service_nodes : : service_node_list & node_list ,
2020-08-13 10:53:11 +02:00
bool timed_out ,
bool all_received )
{
2020-08-14 03:53:52 +02:00
assert ( context . state > round_state : : wait_for_handshake_bitsets ) ;
uint16_t const validator_bitset = context . transient . wait_for_handshake_bitsets . best_bitset ;
2020-08-13 10:53:11 +02:00
if ( timed_out & & ! all_received )
{
2020-09-10 03:09:12 +02:00
MDEBUG ( log_prefix ( context ) < < " Stage timed out: insufficient responses. Expected "
< < " ( " < < bitset_view16 ( validator_bitset ) . count ( ) < < " ) " < < bitset_view16 ( validator_bitset ) < < " received "
< < " ( " < < bitset_view16 ( stage . bitset ) . count ( ) < < " ) " < < bitset_view16 ( stage . bitset ) ) ;
2020-08-13 10:53:11 +02:00
return false ;
}
2020-08-13 10:56:40 +02:00
// NOTE: This is not technically meant to hit, internal invariant checking
2020-09-10 03:09:12 +02:00
// that should have been triggered earlier. Enforce validator participation is
// only called after the stage has ended/timed out.
2020-08-13 10:53:11 +02:00
bool unexpected_items = ( stage . bitset | validator_bitset ) ! = validator_bitset ;
if ( stage . msgs_received = = 0 | | unexpected_items )
{
2020-09-10 03:09:12 +02:00
MERROR ( log_prefix ( context ) < < " Internal error: expected bitset " < < bitset_view16 ( validator_bitset ) < < " , but accepted and received " < < bitset_view16 ( stage . bitset ) ) ;
2020-08-13 10:53:11 +02:00
return false ;
}
return true ;
}
2020-08-11 07:56:33 +02:00
} // anonymous namespace
void pulse : : handle_message ( void * quorumnet_state , pulse : : message const & msg )
{
2020-09-10 06:14:29 +02:00
if ( context . state < round_state : : wait_for_round )
{
// TODO(doyle): Handle this better.
// We are not ready for any messages because we haven't prepared for a round
// yet (don't have the necessary information yet to validate the message).
return ;
}
2020-08-27 04:30:03 +02:00
// TODO(loki): We don't support messages from future rounds. A round
// mismatch will be detected in the signature as the round is included in the
// signature hash.
2020-09-10 11:09:27 +02:00
if ( std : : string sig_check_err ;
! msg_signature_check ( msg , context . wait_for_next_block . top_hash , context . prepare_for_round . quorum , & sig_check_err ) )
{
bool print_err = true ;
size_t iterations = std : : min ( context . quorum_history . size ( ) , context . quorum_history_index ) ;
for ( size_t i = 0 ; i < iterations ; i + + )
{
auto const & past_round = context . quorum_history [ i ] ;
//
// NOTE: We can't do any filtering on the quorums to check against
// (like comparing the round in the message with the past_round's round)
// because the intermediary relayers of this message might have modified
// it maliciously before propagating it.
//
// Hence we check it against all the quorums in history. So keep the
// number of quorums stored in history very small to keep this fast!
//
if ( msg_signature_check ( msg , past_round . top_block_hash , past_round . quorum , nullptr /*error msg*/ ) )
{
// NOTE: This is ok, we detected a round failed earlier than someone else
// and lingering messages are still going around on Quorumnet from
// a round in the past.
//
// i.e. You were the block producer and have submitted the block template,
// in which case your role in the ceremony is done and you sleep until
// the next round/block. Old lingering messages for the block producer
// (you) might still being propagated, and that is ok, and should not be
// marked an error, just ignored.
print_err = false ;
2020-10-12 08:53:30 +02:00
MTRACE ( log_prefix ( context ) < < " Received valid message from the past (round " < < + msg . round < < " ), ignoring " ) ;
2020-09-10 11:09:27 +02:00
break ;
} // else: Message has unknown origins, it is not something we know how to validate.
}
if ( print_err )
MERROR ( sig_check_err ) ;
2020-09-10 06:14:29 +02:00
return ;
2020-09-10 11:09:27 +02:00
}
2020-08-11 07:56:33 +02:00
2020-08-13 10:38:04 +02:00
pulse_wait_stage * stage = nullptr ;
switch ( msg . type )
{
2020-09-10 03:09:12 +02:00
case pulse : : message_type : : invalid :
{
MTRACE ( log_prefix ( context ) < < " Received invalid message type, dropped " ) ;
return ;
}
2020-08-14 03:46:31 +02:00
case pulse : : message_type : : handshake : stage = & context . transient . send_and_wait_for_handshakes . stage ; break ;
2020-08-13 13:09:04 +02:00
case pulse : : message_type : : handshake_bitset : stage = & context . transient . wait_for_handshake_bitsets . stage ; break ;
case pulse : : message_type : : block_template : stage = & context . transient . wait_for_block_template . stage ; break ;
2020-08-14 03:46:31 +02:00
case pulse : : message_type : : random_value_hash : stage = & context . transient . random_value_hashes . wait . stage ; break ;
case pulse : : message_type : : random_value : stage = & context . transient . random_value . wait . stage ; break ;
case pulse : : message_type : : signed_block : stage = & context . transient . signed_block . wait . stage ; break ;
2020-08-13 10:38:04 +02:00
}
bool msg_received_early = false ;
switch ( msg . type )
{
case pulse : : message_type : : invalid : assert ( " Invalid Code Path " ! = nullptr ) ; return ;
2020-08-14 03:46:31 +02:00
case pulse : : message_type : : handshake : msg_received_early = ( context . state < round_state : : send_and_wait_for_handshakes ) ; break ;
case pulse : : message_type : : handshake_bitset : msg_received_early = ( context . state < round_state : : wait_for_handshake_bitsets ) ; break ;
case pulse : : message_type : : block_template : msg_received_early = ( context . state < round_state : : wait_for_block_template ) ; break ;
case pulse : : message_type : : random_value_hash : msg_received_early = ( context . state < round_state : : send_and_wait_for_random_value_hashes ) ; break ;
case pulse : : message_type : : random_value : msg_received_early = ( context . state < round_state : : send_and_wait_for_random_value ) ; break ;
case pulse : : message_type : : signed_block : msg_received_early = ( context . state < round_state : : send_and_wait_for_signed_blocks ) ; break ;
2020-08-13 10:38:04 +02:00
}
if ( msg_received_early ) // Enqueue the message until we're ready to process it
{
auto & [ entry , queued ] = stage - > queue . buffer [ msg . quorum_position ] ;
if ( queued = = queueing_state : : empty )
{
2020-08-14 09:07:01 +02:00
MTRACE ( log_prefix ( context ) < < " Message received early " < < msg_source_string ( context , msg ) < < " , queueing until we're ready. " ) ;
2020-08-13 10:38:04 +02:00
stage - > queue . count + + ;
entry = std : : move ( msg ) ;
queued = queueing_state : : received ;
}
return ;
}
uint16_t const validator_bit = ( 1 < < msg . quorum_position ) ;
2020-08-14 03:46:31 +02:00
if ( context . state > round_state : : wait_for_handshake_bitsets & &
msg . type > pulse : : message_type : : handshake_bitset )
2020-08-13 10:38:04 +02:00
{
2020-08-14 05:27:14 +02:00
// After the validator bitset has been set, the participating validators are
2020-08-13 10:38:04 +02:00
// locked in. Any stray messages from other validators are rejected.
2020-08-14 03:46:31 +02:00
if ( ( validator_bit & context . transient . wait_for_handshake_bitsets . best_bitset ) = = 0 )
2020-08-13 10:38:04 +02:00
{
2020-08-14 03:52:03 +02:00
auto bitset_view = bitset_view16 ( context . transient . wait_for_handshake_bitsets . best_bitset ) ;
2020-08-14 09:07:01 +02:00
MTRACE ( log_prefix ( context ) < < " Dropping " < < msg_source_string ( context , msg ) < < " . Not a locked in participant, bitset is " < < bitset_view ) ;
2020-08-13 10:38:04 +02:00
return ;
}
}
2020-08-14 05:27:14 +02:00
if ( msg . quorum_position > = service_nodes : : PULSE_QUORUM_NUM_VALIDATORS )
{
2020-08-14 09:07:01 +02:00
MTRACE ( log_prefix ( context ) < < " Dropping " < < msg_source_string ( context , msg ) < < " . Message quorum position indexes oob " ) ;
2020-08-14 05:27:14 +02:00
return ;
}
2020-08-13 10:38:04 +02:00
//
// Add Message Data to Pulse Stage
//
2020-08-10 11:37:17 +02:00
switch ( msg . type )
{
2020-08-11 07:56:33 +02:00
case pulse : : message_type : : invalid :
assert ( " Invalid Code Path " ! = nullptr ) ;
2020-08-13 08:19:55 +02:00
return ;
2020-08-10 11:37:17 +02:00
case pulse : : message_type : : handshake :
{
2020-08-14 03:46:31 +02:00
auto & quorum = context . transient . send_and_wait_for_handshakes . data ;
2020-08-13 10:38:04 +02:00
if ( quorum [ msg . quorum_position ] ) return ;
quorum [ msg . quorum_position ] = true ;
2020-08-14 09:07:01 +02:00
MTRACE ( log_prefix ( context ) < < " Received handshake with quorum position bit ( " < < msg . quorum_position < < " ) "
< < bitset_view16 ( validator_bit ) < < " saved to bitset "
< < bitset_view16 ( stage - > bitset ) ) ;
2020-08-10 11:37:17 +02:00
}
break ;
case pulse : : message_type : : handshake_bitset :
{
2020-08-18 02:51:19 +02:00
auto & quorum = context . transient . wait_for_handshake_bitsets . data ;
auto & bitset = quorum [ msg . quorum_position ] ;
if ( bitset ) return ;
bitset = msg . handshakes . validator_bitset ;
2020-08-10 11:37:17 +02:00
}
break ;
case pulse : : message_type : : block_template :
{
2020-08-13 10:38:04 +02:00
if ( stage - > msgs_received = = 1 )
2020-08-10 11:37:17 +02:00
return ;
cryptonote : : block block = { } ;
if ( ! cryptonote : : t_serializable_object_from_blob ( block , msg . block_template . blob ) )
{
2020-08-14 09:07:01 +02:00
MTRACE ( log_prefix ( context ) < < " Received unparsable pulse block template blob " ) ;
2020-08-10 11:37:17 +02:00
return ;
}
if ( block . pulse . round ! = context . prepare_for_round . round )
{
2020-08-14 09:07:01 +02:00
MTRACE ( log_prefix ( context ) < < " Received pulse block template specifying different round " < < + block . pulse . round
< < " , expected " < < + context . prepare_for_round . round ) ;
2020-08-10 11:37:17 +02:00
return ;
}
2020-08-14 03:53:52 +02:00
if ( block . pulse . validator_bitset ! = context . transient . wait_for_handshake_bitsets . best_bitset )
{
auto block_bitset = bitset_view16 ( block . pulse . validator_bitset ) ;
auto our_bitset = bitset_view16 ( context . transient . wait_for_handshake_bitsets . best_bitset ) ;
2020-08-14 09:07:01 +02:00
MTRACE ( log_prefix ( context ) < < " Received pulse block template specifying different validator handshake bitsets " < < block_bitset < < " , expected " < < our_bitset ) ;
2020-08-14 03:53:52 +02:00
return ;
}
2020-08-13 13:09:04 +02:00
context . transient . wait_for_block_template . block = std : : move ( block ) ;
2020-08-10 11:37:17 +02:00
}
break ;
2020-08-11 10:14:17 +02:00
2020-08-13 10:38:04 +02:00
case pulse : : message_type : : random_value_hash :
2020-08-11 10:14:17 +02:00
{
2020-08-18 02:51:19 +02:00
auto & quorum = context . transient . random_value_hashes . wait . data ;
auto & value = quorum [ msg . quorum_position ] ;
if ( value ) return ;
value = msg . random_value_hash . hash ;
2020-08-13 10:38:04 +02:00
}
break ;
2020-08-13 08:19:55 +02:00
2020-08-13 10:38:04 +02:00
case pulse : : message_type : : random_value :
{
2020-08-18 02:51:19 +02:00
auto & quorum = context . transient . random_value . wait . data ;
auto & value = quorum [ msg . quorum_position ] ;
if ( value ) return ;
2020-08-12 08:30:33 +02:00
2020-08-18 02:51:19 +02:00
if ( auto const & hash = context . transient . random_value_hashes . wait . data [ msg . quorum_position ] ; hash )
2020-08-12 08:30:33 +02:00
{
2020-08-18 03:30:58 +02:00
auto derived = blake2b_hash ( msg . random_value . value . data , sizeof ( msg . random_value . value . data ) ) ;
2020-08-18 02:51:19 +02:00
if ( derived ! = * hash )
2020-08-12 08:30:33 +02:00
{
2020-08-14 09:07:01 +02:00
MTRACE ( log_prefix ( context ) < < " Dropping " < < msg_source_string ( context , msg )
< < " . Rederived random value hash " < < derived < < " does not match original hash "
2020-08-18 02:51:19 +02:00
< < * hash ) ;
2020-08-12 08:30:33 +02:00
return ;
}
}
2020-08-13 10:38:04 +02:00
2020-08-18 02:51:19 +02:00
value = msg . random_value . value ;
2020-08-11 10:14:17 +02:00
}
break ;
2020-08-12 10:22:23 +02:00
case pulse : : message_type : : signed_block :
{
2020-09-10 06:14:29 +02:00
// NOTE: The block template with the final random value inserted but no
// Service Node signatures. (Service Node signatures are added in one shot
// after this stage has timed out and all signatures are collected).
cryptonote : : block const & final_block_no_signatures = context . transient . signed_block . final_block ;
crypto : : hash const final_block_hash = cryptonote : : get_block_hash ( final_block_no_signatures ) ;
assert ( msg . quorum_position < context . prepare_for_round . quorum . validators . size ( ) ) ;
crypto : : public_key const & validator_key = context . prepare_for_round . quorum . validators [ msg . quorum_position ] ;
if ( ! crypto : : check_signature ( final_block_hash , validator_key , msg . signed_block . signature_of_final_block_hash ) )
2020-08-13 08:19:55 +02:00
{
2020-09-10 06:14:29 +02:00
MTRACE ( log_prefix ( context ) < < " Dropping " < < msg_source_string ( context , msg )
< < " . Signature signing final block hash "
< < msg . signed_block . signature_of_final_block_hash
< < " does not validate with the Service Node " ) ;
2020-08-12 10:22:23 +02:00
return ;
}
2020-08-18 02:51:19 +02:00
auto & quorum = context . transient . signed_block . wait . data ;
auto & signature = quorum [ msg . quorum_position ] ;
if ( signature ) return ;
2020-09-10 06:14:29 +02:00
signature = msg . signed_block . signature_of_final_block_hash ;
2020-08-12 10:22:23 +02:00
}
break ;
2020-08-10 11:37:17 +02:00
}
2020-08-13 10:38:04 +02:00
stage - > bitset | = validator_bit ;
stage - > msgs_received + + ;
2020-08-13 08:19:55 +02:00
if ( quorumnet_state )
2020-08-10 11:37:17 +02:00
cryptonote : : quorumnet_pulse_relay_message_to_quorum ( quorumnet_state , msg , context . prepare_for_round . quorum , context . prepare_for_round . participant = = sn_type : : producer ) ;
2020-07-27 07:54:02 +02:00
}
2020-10-12 02:17:21 +02:00
// TODO(doyle): Update pulse::perpare_for_round with this function after the hard fork and sanity check it on testnet.
bool pulse : : convert_time_to_round ( pulse : : time_point const & time , pulse : : time_point const & r0_timestamp , uint8_t * round )
{
auto const time_since_round_started = time < = r0_timestamp ? std : : chrono : : seconds ( 0 ) : ( time - r0_timestamp ) ;
size_t result_usize = time_since_round_started / service_nodes : : PULSE_ROUND_TIME ;
if ( round ) * round = static_cast < uint8_t > ( result_usize ) ;
return result_usize < = 255 ;
}
2020-09-09 09:04:25 +02:00
bool pulse : : get_round_timings ( cryptonote : : Blockchain const & blockchain , uint64_t block_height , uint64_t prev_timestamp , pulse : : timings & times )
2020-08-19 06:54:37 +02:00
{
times = { } ;
2020-09-18 21:39:50 +02:00
static uint64_t const hf16_height = blockchain . get_earliest_ideal_height_for_version ( cryptonote : : network_version_16_pulse ) ;
2020-08-19 11:40:33 +02:00
if ( hf16_height = = std : : numeric_limits < uint64_t > : : max ( ) )
return false ;
2020-09-22 08:52:39 +02:00
if ( blockchain . get_current_blockchain_height ( ) < hf16_height )
return false ;
2020-08-28 21:11:25 +02:00
cryptonote : : block genesis_block ;
if ( ! blockchain . get_block_by_height ( hf16_height - 1 , genesis_block ) )
2020-08-19 06:54:37 +02:00
return false ;
2020-09-09 09:04:25 +02:00
uint64_t const delta_height = block_height - cryptonote : : get_block_height ( genesis_block ) ;
2020-08-19 06:54:37 +02:00
times . genesis_timestamp = pulse : : time_point ( std : : chrono : : seconds ( genesis_block . timestamp ) ) ;
2020-09-09 09:04:25 +02:00
times . prev_timestamp = pulse : : time_point ( std : : chrono : : seconds ( prev_timestamp ) ) ;
2020-08-19 06:54:37 +02:00
times . ideal_timestamp = pulse : : time_point ( times . genesis_timestamp + ( TARGET_BLOCK_TIME * delta_height ) ) ;
2020-09-07 09:43:21 +02:00
# if 1
2020-08-19 06:54:37 +02:00
times . r0_timestamp = std : : clamp ( times . ideal_timestamp ,
times . prev_timestamp + service_nodes : : PULSE_MIN_TARGET_BLOCK_TIME ,
times . prev_timestamp + service_nodes : : PULSE_MAX_TARGET_BLOCK_TIME ) ;
# else // NOTE: Debug, make next block start relatively soon
times . r0_timestamp = times . prev_timestamp + service_nodes : : PULSE_ROUND_TIME ;
# endif
2020-09-07 09:43:21 +02:00
times . miner_fallback_timestamp = times . r0_timestamp + ( service_nodes : : PULSE_ROUND_TIME * 255 ) ;
2020-08-19 06:54:37 +02:00
return true ;
}
2020-07-27 11:45:34 +02:00
/*
2020-08-11 03:57:13 +02:00
Pulse progresses via a state - machine that is iterated through job submissions
to 1 dedicated Pulse thread , started by LMQ .
Iterating the state - machine is done by a periodic invocation of
pulse : : main ( . . . ) and messages received via Quorumnet for Pulse , which are
queued in the thread ' s job queue .
Using 1 dedicated thread via LMQ avoids any synchronization required in the
user code when implementing Pulse .
Skip control flow graph for textual description of stages .
+ - - - - - - - - - - - - - - - - - - - - - +
| Wait For Next Block | < - - - - - - - - + - - - - - - - +
+ - - - - - - - - - - - - - - - - - - - - - + | |
| | |
+ - [ Blocks for round acquired ] - - + No |
| | |
| Yes | |
| | |
+ - - - - - - - - - - - - - - - - - - - - - + | |
+ - - - - > | Prepare For Round | | |
| + - - - - - - - - - - - - - - - - - - - - - + | |
| | | |
| [ Enough SN ' s for Pulse ] - - - - - - - - - + No |
| | |
| Yes |
| | |
| + - - - - - - - - - - - - - - - - - - - - - + |
| | Wait For Round | |
| + - - - - - - - - - - - - - - - - - - - - - + |
| | |
| [ Block Height Changed ? ] - - - - - - - - - - - - - - - - - + Yes
| |
| | No
| |
2020-08-25 05:46:38 +02:00
No + - - - - - [ Participating in Quorum ? ]
| |
| | Yes
| |
| |
| [ Validator ? ] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + No ( We are Block Producer )
2020-08-11 03:57:13 +02:00
| | |
| | Yes |
| | |
2020-08-25 05:46:38 +02:00
| + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |
| | Send And Wait For Handshakes | |
| + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |
2020-08-11 08:13:09 +02:00
| | |
2020-08-25 05:46:38 +02:00
Yes + - - - - - [ Quorumnet Comm Failure ] |
| | |
| | No |
| | |
| + - - - - - - - - - - - - - - - - - - - - - - - + |
| | Send Handshake Bitset | |
| + - - - - - - - - - - - - - - - - - - - - - - - + |
2020-08-11 03:57:13 +02:00
| | |
Yes + - - - - - [ Quorumnet Comm Failure ] |
| | |
| | No |
| | |
| + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |
| | Wait For Handshake Bitsets | < - - - - - - - - - - - - - - - - - +
| + - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| |
Yes + - - - - - [ Insufficient Bitsets ]
| |
| | No
| |
2020-08-25 05:46:38 +02:00
| [ Block Producer ? ] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + No ( We are a Validator )
| | |
| | Yes |
| | |
| + - - - - - - - - - - - - - - - - - - - - - + |
| | Send Block Template | |
| + - - - - - - - - - - - - - - - - - - - - - + |
| | |
+ - - - - - - + ( Block Producer ' s role is finished ) |
| |
| |
| + - - - - - - - - - - - - - - - - - - - - - - - - - + |
| | Wait For Block Template | < - - - - - - - - - - - - - - - - - - - - +
| + - - - - - - - - - - - - - - - - - - - - - - - - - +
| |
Yes + - - - - - [ Timed Out Waiting for Template ]
2020-08-11 03:57:13 +02:00
| |
2020-08-25 05:46:38 +02:00
| | No
| |
| + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| | Send And Wait For Random Value Hashes |
| + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| |
Yes + - - - - - [ Insufficient Hashes ]
| |
| | No
| |
| + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| | Send And Wait For Random Value |
| + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| |
Yes + - - - - - [ Insufficient Values ]
| |
| | No
| |
| + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| | Send And Wait For Signed Blocks |
| + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| |
Yes + - - - - - [ Block can not be added to blockchain ]
2020-08-11 03:57:13 +02:00
|
2020-08-25 05:46:38 +02:00
| No
2020-08-11 03:57:13 +02:00
|
2020-08-25 05:46:38 +02:00
+ ( Finished , state machine resets )
2020-08-11 03:57:13 +02:00
Wait For Next Block :
2020-08-25 05:46:38 +02:00
- Waits for the next block in the blockchain to arrive
2020-08-11 03:57:13 +02:00
- Retrieves the blockchain metadata for starting a Pulse Round including the
Genesis Pulse Block for the base timestamp and the top block hash and
height for signatures .
- // TODO(loki): After the Genesis Pulse Block is checkpointed, we can
// remove it from the event loop. Right now we recheck every block incase
// of (the very unlikely event) reorgs that might change the block at the
// hardfork.
2020-08-25 05:46:38 +02:00
- The ideal next block timestamp is determined by
2020-08-11 03:57:13 +02:00
G . Timestamp + ( height * TARGET_BLOCK_TIME )
Where ' G ' is the base Pulse genesis block , i . e . the hardforking block
activating Pulse ( HF16 ) .
2020-07-27 11:45:34 +02:00
2020-08-25 05:46:38 +02:00
The actual next block timestamp is determined by
P . Timestamp + ( TARGET_BLOCK_TIME ± 15 s )
2020-07-27 11:45:34 +02:00
2020-08-25 05:46:38 +02:00
Where ' P ' is the previous block . The block time is adjusted ± 15 s depending
on how close / far away the ideal block time is .
2020-08-11 03:57:13 +02:00
Prepare For Round :
- Generate data for executing the round such as the Quorum and stage
durations depending on the round Pulse is at by comparing the clock with
the ideal block timestamp .
- The state machine * always * reverts to ' Prepare For Round ' when any
subsequent stage fails , except in the cases where Pulse can not proceed
because of an insufficient Service Node network .
2020-07-27 11:45:34 +02:00
2020-08-25 05:46:38 +02:00
- If the next round to prepare for is > 255 , we disable Pulse and re - allow
PoW blocks to be added to the chain , the Pulse state machine resets and
waits for the next block to arrive and re - evaluates if Pulse is possible
again .
Wait For Round ( Block Producer & Validator )
- Checks clock against the next expected Pulse timestamps has arrived ,
otherwise continues sleeping .
2020-08-11 03:57:13 +02:00
- If we are a validator we ' Submit Handshakes ' with other Validators
If we are a block producer we skip to ' Wait For Handshake Bitset ' and
await the final handshake bitsets from all the Validators
2020-08-25 05:46:38 +02:00
Otherwise we return to ' Prepare For Round ' and sleep .
2020-08-11 03:57:13 +02:00
2020-08-25 05:46:38 +02:00
Send And Wait For Handshakes ( Validator )
- On first invocation , we send the handshakes to Validator peers , then waits
for handshakes . Validators handshake to confirm participation in the round
and collect other handshakes .
2020-07-27 11:45:34 +02:00
2020-08-25 05:46:38 +02:00
Send Handshake Bitset ( Validator )
- Send our collected participation bitset to the validators
2020-08-11 03:57:13 +02:00
2020-08-25 05:46:38 +02:00
Wait For Handshake Bitsets ( Block Producer & Validator )
2020-07-27 11:45:34 +02:00
- Upon receipt , the most common agreed upon bitset is used to lock in
participation for the round . The round proceeds if more than 60 % of the
2020-08-11 03:57:13 +02:00
validators are participating , the round fails otherwise and reverts to
' Prepare For Round ' .
2020-08-25 05:46:38 +02:00
- If we are a validator we go to ' Wait For Block Template '
2020-08-11 03:57:13 +02:00
- If we are a block producer we go to ' Submit Block Template '
2020-07-27 11:45:34 +02:00
2020-08-25 05:46:38 +02:00
Submit Block Template ( Block Producer )
2020-08-11 03:57:13 +02:00
- Block producer signs the block template with the validator bitset and
2020-08-25 05:46:38 +02:00
pulse round applied to the block and sends it to the round validators
- The block producer is finished for the round and awaits the next
round ( if any subsequent stage fails ) or block .
Wait For Block Template ( Validator )
- Await the block template and ensure it ' s signed by the block producer , if
not we revert to ' Prepare For Round '
- We generate our part of the random value and prepare the hash of the
random value and proceed to the next stage .
Send And Wait For Random Value Hashes ( Validator )
- On first invcation , send the hash of our random value prepared in the
' Wait For Block Template ' stage , followed by waiting for the other random
value hashes from validators .
2020-08-11 03:57:13 +02:00
2020-08-25 05:46:38 +02:00
- If not all hashes are received according to the locked in validator bitset
in the block , we revert to ' Prepare For Round ' .
2020-07-27 11:45:34 +02:00
2020-08-25 05:46:38 +02:00
Send And Wait For Random Value ( Validator )
- On first invcation , send the random value prepared in the ' Wait For Block
Template ' stage , followed by waiting for the other random values from
validators .
- If not all values are received according to the locked in validator bitset
in the block , we revert to ' Prepare For Round ' .
Send And Wait For Signed Block ( Validator )
- On first invcation , send our signature , signing the block template with
all the random values combined into 1 to other validators and await for
the other signatures to arrive .
- Ensure the signature signs the same block template we received at the
beginning from the Block Producer .
- If not all values are received according to the locked in validator bitset
in the block , we revert to ' Prepare For Round ' .
- Add the block to the blockchain and on success , that will automatically
begin propagating the block via P2P . The signatures in the block are added
in any order , as soon as the first N signatures arrive the block can be
P2P - ed .
2020-07-27 11:45:34 +02:00
*/
2020-08-14 03:46:31 +02:00
round_state goto_preparing_for_next_round ( round_context & context )
2020-08-11 10:14:17 +02:00
{
context . prepare_for_round . queue_for_next_round = true ;
2020-08-14 03:46:31 +02:00
return round_state : : prepare_for_round ;
2020-08-11 10:14:17 +02:00
}
2020-09-10 11:09:27 +02:00
void clear_round_data ( round_context & context )
{
if ( service_nodes : : verify_pulse_quorum_sizes ( context . prepare_for_round . quorum ) )
{
// NOTE: Store the quorum into history before deleting it from memory.
// This way we can verify late arriving messages when we may have progressed
// already into a new round/block.
bool store = true ;
size_t iterations = std : : min ( context . quorum_history . size ( ) , context . quorum_history_index ) ;
for ( size_t i = 0 ; i < iterations ; i + + )
{
auto & quorum = context . quorum_history [ i ] ;
if ( quorum . height = = context . wait_for_next_block . height & &
quorum . round = = context . prepare_for_round . round )
{
store = false ; // Already stored quorum
break ;
}
}
if ( store )
{
size_t real_quorum_history_index = context . quorum_history_index % context . quorum_history . size ( ) ;
context . quorum_history_index + + ;
round_history & entry = context . quorum_history [ real_quorum_history_index ] ;
entry = { } ;
entry . top_block_hash = context . wait_for_next_block . top_hash ;
entry . height = context . wait_for_next_block . height ;
entry . round = context . prepare_for_round . round ;
entry . quorum = std : : move ( context . prepare_for_round . quorum ) ;
}
}
context . transient = { } ;
cryptonote : : pulse_random_value & old_random_value = context . transient . random_value . send . data ;
auto & old_random_values_array = context . transient . random_value . wait . data ;
memwipe ( old_random_value . data , sizeof ( old_random_value ) ) ;
memwipe ( old_random_values_array . data ( ) , old_random_values_array . size ( ) * sizeof ( old_random_values_array [ 0 ] ) ) ;
context . prepare_for_round = { } ;
}
round_state goto_wait_for_next_block_and_clear_round_data ( round_context & context )
{
clear_round_data ( context ) ;
return round_state : : wait_for_next_block ;
}
2020-08-14 03:46:31 +02:00
round_state wait_for_next_block ( uint64_t hf16_height , round_context & context , cryptonote : : Blockchain const & blockchain )
2020-08-10 12:13:13 +02:00
{
//
// NOTE: If already processing pulse for height, wait for next height
//
2020-09-09 09:04:25 +02:00
uint64_t chain_height = blockchain . get_current_blockchain_height ( true /*lock*/ ) ;
if ( context . wait_for_next_block . height = = chain_height )
{
for ( static uint64_t last_height = 0 ; last_height ! = chain_height ; last_height = chain_height )
MDEBUG ( log_prefix ( context ) < < " Network is currently producing block " < < chain_height < < " , waiting until next block " ) ;
return round_state : : wait_for_next_block ;
}
crypto : : hash prev_hash = blockchain . get_block_id_by_height ( chain_height - 1 ) ;
if ( prev_hash = = crypto : : null_hash )
{
for ( static uint64_t last_height = 0 ; last_height ! = chain_height ; last_height = chain_height )
MDEBUG ( log_prefix ( context ) < < " Failed to query the block hash for height " < < chain_height - 1 ) ;
return round_state : : wait_for_next_block ;
}
uint64_t prev_timestamp = 0 ;
try
{
prev_timestamp = blockchain . get_db ( ) . get_block_timestamp ( chain_height - 1 ) ;
}
catch ( std : : exception const & e )
2020-08-10 12:13:13 +02:00
{
2020-09-09 09:04:25 +02:00
for ( static uint64_t last_height = 0 ; last_height ! = chain_height ; last_height = chain_height )
MDEBUG ( log_prefix ( context ) < < " Failed to query the block hash for height " < < chain_height - 1 ) ;
2020-08-14 03:46:31 +02:00
return round_state : : wait_for_next_block ;
2020-08-10 12:13:13 +02:00
}
2020-08-19 06:54:37 +02:00
pulse : : timings times = { } ;
2020-09-09 09:04:25 +02:00
if ( ! get_round_timings ( blockchain , chain_height , prev_timestamp , times ) )
2020-08-10 12:13:13 +02:00
{
2020-09-09 09:04:25 +02:00
for ( static uint64_t last_height = 0 ; last_height ! = chain_height ; last_height = chain_height )
2020-08-19 06:54:37 +02:00
MERROR ( log_prefix ( context ) < < " Failed to query the block data for Pulse timings " ) ;
2020-08-14 03:46:31 +02:00
return round_state : : wait_for_next_block ;
2020-08-10 12:13:13 +02:00
}
2020-08-19 06:54:37 +02:00
context . wait_for_next_block . round_0_start_time = times . r0_timestamp ;
2020-09-09 09:04:25 +02:00
context . wait_for_next_block . height = chain_height ;
context . wait_for_next_block . top_hash = prev_hash ;
2020-08-19 06:54:37 +02:00
context . prepare_for_round = { } ;
2020-08-10 12:13:13 +02:00
2020-08-14 03:46:31 +02:00
return round_state : : prepare_for_round ;
2020-08-10 12:13:13 +02:00
}
2020-08-14 03:46:31 +02:00
round_state prepare_for_round ( round_context & context , service_nodes : : service_node_keys const & key , cryptonote : : Blockchain const & blockchain )
2020-08-10 12:13:13 +02:00
{
2020-09-10 11:09:27 +02:00
//
// NOTE: Clear Round Data
//
2020-08-14 06:16:11 +02:00
{
2020-08-19 07:24:11 +02:00
// Store values
2020-09-10 11:09:27 +02:00
uint8_t round = context . prepare_for_round . round ;
bool queue_for_next_round = context . prepare_for_round . queue_for_next_round ;
2020-08-19 07:24:11 +02:00
2020-09-10 11:09:27 +02:00
clear_round_data ( context ) ;
2020-08-19 07:24:11 +02:00
// Restore values
context . prepare_for_round . round = round ;
2020-09-10 11:09:27 +02:00
context . prepare_for_round . queue_for_next_round = queue_for_next_round ;
2020-08-14 06:16:11 +02:00
}
2020-08-10 12:13:13 +02:00
if ( context . prepare_for_round . queue_for_next_round )
{
2020-08-19 07:52:10 +02:00
if ( context . prepare_for_round . round > = 255 )
2020-08-19 06:54:37 +02:00
{
// If the next round overflows, we consider the network stalled. Wait for
// the next block and allow PoW to return.
2020-09-10 11:09:27 +02:00
return goto_wait_for_next_block_and_clear_round_data ( context ) ;
2020-08-19 06:54:37 +02:00
}
2020-08-10 12:13:13 +02:00
// Also check if the blockchain has changed, in which case we stop and
// restart Pulse stages.
if ( context . wait_for_next_block . height ! = blockchain . get_current_blockchain_height ( true /*lock*/ ) )
2020-09-10 11:09:27 +02:00
return goto_wait_for_next_block_and_clear_round_data ( context ) ;
// 'queue_for_next_round' is set when an intermediate Pulse stage has failed
// and the caller requests us to wait until the next round to occur.
context . prepare_for_round . queue_for_next_round = false ;
context . prepare_for_round . round + + ;
2020-08-10 12:13:13 +02:00
}
//
// NOTE: Check Current Round
//
{
auto now = pulse : : clock : : now ( ) ;
auto const time_since_block = now < = context . wait_for_next_block . round_0_start_time ? std : : chrono : : seconds ( 0 ) : ( now - context . wait_for_next_block . round_0_start_time ) ;
size_t round_usize = time_since_block / service_nodes : : PULSE_ROUND_TIME ;
2020-08-19 06:54:37 +02:00
if ( round_usize > 255 ) // Network stalled
2020-08-19 10:24:10 +02:00
{
MINFO ( log_prefix ( context ) < < " Pulse has timed out, reverting to accepting miner blocks only. " ) ;
2020-09-10 11:09:27 +02:00
return goto_wait_for_next_block_and_clear_round_data ( context ) ;
2020-08-19 10:24:10 +02:00
}
2020-08-10 12:13:13 +02:00
2020-08-19 07:52:10 +02:00
uint8_t curr_round = static_cast < uint8_t > ( round_usize ) ;
2020-08-10 12:13:13 +02:00
if ( curr_round > context . prepare_for_round . round )
context . prepare_for_round . round = curr_round ;
}
2020-08-14 03:46:31 +02:00
{
using namespace service_nodes ;
context . prepare_for_round . start_time = context . wait_for_next_block . round_0_start_time + ( context . prepare_for_round . round * PULSE_ROUND_TIME ) ;
context . transient . send_and_wait_for_handshakes . stage . end_time = context . prepare_for_round . start_time + PULSE_WAIT_FOR_HANDSHAKES_DURATION ;
context . transient . wait_for_handshake_bitsets . stage . end_time = context . transient . send_and_wait_for_handshakes . stage . end_time + PULSE_WAIT_FOR_OTHER_VALIDATOR_HANDSHAKES_DURATION ;
context . transient . wait_for_block_template . stage . end_time = context . transient . wait_for_handshake_bitsets . stage . end_time + PULSE_WAIT_FOR_BLOCK_TEMPLATE_DURATION ;
context . transient . random_value_hashes . wait . stage . end_time = context . transient . wait_for_block_template . stage . end_time + PULSE_WAIT_FOR_RANDOM_VALUE_HASH_DURATION ;
context . transient . random_value . wait . stage . end_time = context . transient . random_value_hashes . wait . stage . end_time + PULSE_WAIT_FOR_RANDOM_VALUE_DURATION ;
2020-08-19 07:52:10 +02:00
context . transient . signed_block . wait . stage . end_time = context . transient . random_value . wait . stage . end_time + PULSE_WAIT_FOR_SIGNED_BLOCK_DURATION ;
2020-08-14 03:46:31 +02:00
}
2020-08-10 12:13:13 +02:00
2020-09-10 11:09:27 +02:00
std : : vector < crypto : : hash > const entropy = service_nodes : : get_pulse_entropy_for_next_block ( blockchain . get_db ( ) , context . wait_for_next_block . top_hash , context . prepare_for_round . round ) ;
auto const active_node_list = blockchain . get_service_node_list ( ) . active_service_nodes_infos ( ) ;
uint8_t const hf_version = blockchain . get_current_hard_fork_version ( ) ;
crypto : : public_key const & block_leader = blockchain . get_service_node_list ( ) . get_block_leader ( ) . key ;
2020-08-10 12:13:13 +02:00
context . prepare_for_round . quorum =
service_nodes : : generate_pulse_quorum ( blockchain . nettype ( ) ,
2020-09-10 11:09:27 +02:00
block_leader ,
hf_version ,
active_node_list ,
2020-09-04 10:09:17 +02:00
entropy ,
2020-08-10 12:13:13 +02:00
context . prepare_for_round . round ) ;
if ( ! service_nodes : : verify_pulse_quorum_sizes ( context . prepare_for_round . quorum ) )
{
MINFO ( log_prefix ( context ) < < " Insufficient Service Nodes to execute Pulse on height " < < context . wait_for_next_block . height < < " , we require a PoW miner block. Sleeping until next block. " ) ;
2020-09-10 11:09:27 +02:00
return goto_wait_for_next_block_and_clear_round_data ( context ) ;
2020-08-10 12:13:13 +02:00
}
2020-08-19 06:54:37 +02:00
MDEBUG ( log_prefix ( context ) < < " Generate Pulse quorum: " < < context . prepare_for_round . quorum ) ;
2020-08-10 12:13:13 +02:00
//
// NOTE: Quorum participation
//
if ( key . pub = = context . prepare_for_round . quorum . workers [ 0 ] )
{
// NOTE: Producer doesn't send handshakes, they only collect the
// handshake bitsets from the other validators to determine who to
// lock in for this round in the block template.
context . prepare_for_round . participant = sn_type : : producer ;
context . prepare_for_round . node_name = " W[0] " ;
}
else
{
for ( size_t index = 0 ; index < context . prepare_for_round . quorum . validators . size ( ) ; index + + )
{
auto const & validator_key = context . prepare_for_round . quorum . validators [ index ] ;
if ( validator_key = = key . pub )
{
context . prepare_for_round . participant = sn_type : : validator ;
context . prepare_for_round . my_quorum_position = index ;
context . prepare_for_round . node_name = " V[ " + std : : to_string ( context . prepare_for_round . my_quorum_position ) + " ] " ;
break ;
}
}
}
2020-08-14 03:46:31 +02:00
return round_state : : wait_for_round ;
2020-08-10 12:13:13 +02:00
}
2020-08-14 03:46:31 +02:00
round_state wait_for_round ( round_context & context , cryptonote : : Blockchain const & blockchain )
2020-08-10 12:13:13 +02:00
{
2020-09-12 21:37:58 +02:00
const auto curr_height = blockchain . get_current_blockchain_height ( true /*lock*/ ) ;
if ( context . wait_for_next_block . height ! = curr_height )
2020-08-11 03:57:13 +02:00
{
2020-08-14 09:07:01 +02:00
MDEBUG ( log_prefix ( context ) < < " Block height changed whilst waiting for round " < < + context . prepare_for_round . round < < " , restarting Pulse stages " ) ;
2020-09-10 11:09:27 +02:00
return goto_wait_for_next_block_and_clear_round_data ( context ) ;
2020-08-11 03:57:13 +02:00
}
2020-08-14 03:46:31 +02:00
auto start_time = context . prepare_for_round . start_time ;
2020-08-10 12:13:13 +02:00
if ( auto now = pulse : : clock : : now ( ) ; now < start_time )
{
for ( static uint64_t last_height = 0 ; last_height ! = context . wait_for_next_block . height ; last_height = context . wait_for_next_block . height )
2020-08-20 05:05:02 +02:00
MINFO ( log_prefix ( context ) < < " Waiting for round " < < + context . prepare_for_round . round < < " to start in " < < tools : : friendly_duration ( start_time - now ) ) ;
2020-08-14 03:46:31 +02:00
return round_state : : wait_for_round ;
2020-08-10 12:13:13 +02:00
}
2020-09-18 18:02:09 +02:00
# ifdef PULSE_TEST_CODE
2020-09-12 21:37:58 +02:00
// For testing purposes: we apply possible random non-response and random delays to half of all
// blocks; we go in batches of 10: 10 maybe-faulty blocks followed by 10 well-behaved blocks.
// (Faulty blocks have an odd second-last height digit).
if ( curr_height % 20 > = 10 )
2020-09-11 10:01:31 +02:00
{
2020-09-12 21:37:58 +02:00
size_t faulty_chance = tools : : uniform_distribution_portable ( tools : : rng , 100 ) ;
if ( faulty_chance < 10 )
{
MDEBUG ( log_prefix ( context ) < < " FAULTY NODE ACTIVATED " ) ;
return goto_preparing_for_next_round ( context ) ;
}
2020-09-11 10:01:31 +02:00
2020-09-12 21:37:58 +02:00
size_t sleep_chance = tools : : uniform_distribution_portable ( tools : : rng , 100 ) ;
if ( sleep_chance < 10 )
{
auto sleep_time = std : : chrono : : seconds ( tools : : uniform_distribution_portable ( tools : : rng , 20 ) ) ;
std : : this_thread : : sleep_for ( sleep_time ) ;
MDEBUG ( log_prefix ( context ) < < " SLEEP TIME ACTIVATED " < < tools : : to_seconds ( sleep_time ) < < " s " ) ;
}
2020-09-11 10:01:31 +02:00
}
# endif
2020-08-10 12:13:13 +02:00
if ( context . prepare_for_round . participant = = sn_type : : validator )
{
2020-08-20 04:48:43 +02:00
MINFO ( log_prefix ( context ) < < " We are a pulse validator, sending handshake bit and collecting other handshakes. " ) ;
2020-08-14 03:46:31 +02:00
return round_state : : send_and_wait_for_handshakes ;
2020-08-10 12:13:13 +02:00
}
2020-08-20 04:48:43 +02:00
else if ( context . prepare_for_round . participant = = sn_type : : producer )
2020-08-10 12:13:13 +02:00
{
2020-08-20 04:48:43 +02:00
MINFO ( log_prefix ( context ) < < " We are the block producer for height " < < context . wait_for_next_block . height < < " in round " < < + context . prepare_for_round . round < < " , awaiting handshake bitsets. " ) ;
2020-08-14 03:46:31 +02:00
return round_state : : wait_for_handshake_bitsets ;
2020-08-10 12:13:13 +02:00
}
2020-08-20 04:48:43 +02:00
else
{
MDEBUG ( log_prefix ( context ) < < " Non-participant for round, waiting on next round or block. " ) ;
return goto_preparing_for_next_round ( context ) ;
}
2020-08-10 12:13:13 +02:00
}
2020-08-14 03:46:31 +02:00
round_state send_and_wait_for_handshakes ( round_context & context , void * quorumnet_state , service_nodes : : service_node_keys const & key )
2020-08-10 12:13:13 +02:00
{
2020-08-14 03:46:31 +02:00
//
// NOTE: Send
//
2020-08-10 12:13:13 +02:00
assert ( context . prepare_for_round . participant = = sn_type : : validator ) ;
2020-08-14 03:46:31 +02:00
if ( ! context . transient . send_and_wait_for_handshakes . sent )
2020-08-10 12:13:13 +02:00
{
2020-08-14 03:46:31 +02:00
context . transient . send_and_wait_for_handshakes . sent = true ;
try
{
relay_validator_handshake_bit_or_bitset ( context , quorumnet_state , key , false /*sending_bitset*/ ) ;
}
catch ( std : : exception const & e )
{
MERROR ( log_prefix ( context ) < < " Attempting to invoke and send a Pulse participation handshake unexpectedly failed. " < < e . what ( ) ) ;
return goto_preparing_for_next_round ( context ) ;
}
2020-08-10 12:13:13 +02:00
}
2020-08-14 03:46:31 +02:00
//
// NOTE: Wait
//
handle_messages_received_early_for ( context . transient . send_and_wait_for_handshakes . stage , quorumnet_state ) ;
pulse_wait_stage const & stage = context . transient . send_and_wait_for_handshakes . stage ;
2020-08-13 10:38:04 +02:00
2020-08-14 03:46:31 +02:00
auto const & quorum = context . transient . send_and_wait_for_handshakes . data ;
2020-08-13 10:38:04 +02:00
bool const timed_out = pulse : : clock : : now ( ) > = stage . end_time ;
bool const all_handshakes = stage . msgs_received = = quorum . size ( ) ;
2020-08-10 12:13:13 +02:00
assert ( context . prepare_for_round . participant = = sn_type : : validator ) ;
2020-08-13 10:38:04 +02:00
assert ( context . prepare_for_round . my_quorum_position < quorum . size ( ) ) ;
2020-08-10 12:13:13 +02:00
if ( all_handshakes | | timed_out )
{
2020-08-13 08:19:55 +02:00
bool missing_handshakes = timed_out & & ! all_handshakes ;
2020-08-14 03:52:03 +02:00
MINFO ( log_prefix ( context ) < < " Collected validator handshakes " < < bitset_view16 ( stage . bitset ) < < ( missing_handshakes ? " , we timed out and some handshakes were not seen! " : " . " ) < < " Sending handshake bitset and collecting other validator bitsets. " ) ;
2020-08-14 03:46:31 +02:00
return round_state : : send_handshake_bitsets ;
}
else
{
return round_state : : send_and_wait_for_handshakes ;
2020-08-10 12:13:13 +02:00
}
}
2020-08-14 03:46:31 +02:00
round_state send_handshake_bitsets ( round_context & context , void * quorumnet_state , service_nodes : : service_node_keys const & key )
2020-08-11 08:13:09 +02:00
{
try
{
2020-08-13 08:19:55 +02:00
relay_validator_handshake_bit_or_bitset ( context , quorumnet_state , key , true /*sending_bitset*/ ) ;
2020-08-14 03:46:31 +02:00
return round_state : : wait_for_handshake_bitsets ;
2020-08-11 08:13:09 +02:00
}
catch ( std : : exception const & e )
{
MERROR ( log_prefix ( context ) < < " Attempting to invoke and send a Pulse validator bitset unexpectedly failed. " < < e . what ( ) ) ;
2020-08-11 10:14:17 +02:00
return goto_preparing_for_next_round ( context ) ;
2020-08-11 08:13:09 +02:00
}
}
2020-09-08 05:56:01 +02:00
round_state wait_for_handshake_bitsets ( round_context & context , service_nodes : : service_node_list & node_list , void * quorumnet_state , service_nodes : : service_node_keys const & key )
2020-08-10 12:13:13 +02:00
{
2020-08-13 13:09:04 +02:00
handle_messages_received_early_for ( context . transient . wait_for_handshake_bitsets . stage , quorumnet_state ) ;
pulse_wait_stage const & stage = context . transient . wait_for_handshake_bitsets . stage ;
2020-08-13 10:38:04 +02:00
2020-09-08 05:56:01 +02:00
auto const & quorum = context . transient . wait_for_handshake_bitsets . data ;
bool const timed_out = pulse : : clock : : now ( ) > = stage . end_time ;
bool const all_bitsets = stage . msgs_received = = quorum . size ( ) ;
2020-08-13 10:38:04 +02:00
2020-08-10 12:13:13 +02:00
if ( timed_out | | all_bitsets )
{
2020-08-13 08:19:55 +02:00
std : : map < uint16_t , int > most_common_bitset ;
uint16_t best_bitset = 0 ;
2020-08-14 04:33:56 +02:00
size_t count = 0 ;
2020-08-13 10:38:04 +02:00
for ( size_t quorum_index = 0 ; quorum_index < quorum . size ( ) ; quorum_index + + )
2020-08-10 12:13:13 +02:00
{
2020-08-18 02:51:19 +02:00
auto & bitset = quorum [ quorum_index ] ;
if ( bitset )
2020-08-10 12:13:13 +02:00
{
2020-08-18 02:51:19 +02:00
uint16_t num = + + most_common_bitset [ * bitset ] ;
if ( num > count )
{
best_bitset = * bitset ;
count = num ;
}
MTRACE ( log_prefix ( context ) < < " Collected from V[ " < < quorum_index < < " ], handshake bitset " < < bitset_view16 ( * bitset ) ) ;
2020-08-10 12:13:13 +02:00
}
}
2020-09-08 06:13:33 +02:00
bool i_am_not_participating = false ;
2020-09-11 10:01:31 +02:00
if ( best_bitset ! = 0 & & context . prepare_for_round . participant = = sn_type : : validator )
2020-09-08 06:13:33 +02:00
i_am_not_participating = ( ( best_bitset & ( 1 < < context . prepare_for_round . my_quorum_position ) ) = = 0 ) ;
if ( count < service_nodes : : PULSE_BLOCK_REQUIRED_SIGNATURES | | best_bitset = = 0 | | i_am_not_participating )
2020-08-10 12:13:13 +02:00
{
2020-08-13 08:19:55 +02:00
if ( best_bitset = = 0 )
2020-08-10 12:13:13 +02:00
{
2020-09-08 06:13:33 +02:00
// Less than the threshold of the validators can come to agreement about
// which validators are online, we wait until the next round.
MDEBUG ( log_prefix ( context ) < < count < < " / " < < quorum . size ( )
< < " validators did not send any handshake bitset or sent an empty handshake "
" bitset and have failed to come to agreement. Waiting until next round. " ) ;
}
else if ( i_am_not_participating )
{
MDEBUG ( log_prefix ( context ) < < " The participating validator bitset " < < bitset_view16 ( best_bitset )
< < " does not include us (quorum index " < < context . prepare_for_round . my_quorum_position < < " ). Waiting until next round. " ) ;
2020-08-10 12:13:13 +02:00
}
else
{
2020-09-08 06:13:33 +02:00
// Can't come to agreement, see threshold comment above
2020-08-14 09:07:01 +02:00
MDEBUG ( log_prefix ( context ) < < " We heard back from less than " < < service_nodes : : PULSE_BLOCK_REQUIRED_SIGNATURES < < " of the validators ( "
2020-09-10 06:14:29 +02:00
< < count < < " / " < < quorum . size ( ) < < " ). Waiting until next round. " ) ;
2020-08-10 12:13:13 +02:00
}
2020-08-11 10:14:17 +02:00
return goto_preparing_for_next_round ( context ) ;
2020-08-10 12:13:13 +02:00
}
2020-08-14 03:46:31 +02:00
context . transient . wait_for_handshake_bitsets . best_bitset = best_bitset ;
context . transient . wait_for_handshake_bitsets . best_count = count ;
2020-08-13 10:38:04 +02:00
MINFO ( log_prefix ( context ) < < count < < " / " < < quorum . size ( )
2020-08-14 03:52:03 +02:00
< < " validators agreed on the participating nodes in the quorum " < < bitset_view16 ( best_bitset )
2020-08-13 08:19:55 +02:00
< < ( context . prepare_for_round . participant = = sn_type : : producer
? " "
: " . Awaiting block template from block producer " ) ) ;
2020-08-11 10:14:17 +02:00
if ( context . prepare_for_round . participant = = sn_type : : producer )
2020-08-14 03:46:31 +02:00
return round_state : : send_block_template ;
2020-08-11 10:14:17 +02:00
else
2020-08-14 03:46:31 +02:00
return round_state : : wait_for_block_template ;
2020-08-10 12:13:13 +02:00
}
2020-08-14 03:46:31 +02:00
return round_state : : wait_for_handshake_bitsets ;
2020-08-10 12:13:13 +02:00
}
2020-08-14 03:46:31 +02:00
round_state send_block_template ( round_context & context , void * quorumnet_state , service_nodes : : service_node_keys const & key , cryptonote : : Blockchain & blockchain )
2020-08-10 12:13:13 +02:00
{
assert ( context . prepare_for_round . participant = = sn_type : : producer ) ;
std : : vector < service_nodes : : service_node_pubkey_info > list_state = blockchain . get_service_node_list ( ) . get_service_node_list_state ( { key . pub } ) ;
2020-08-11 10:24:15 +02:00
// Invariants
2020-08-10 12:13:13 +02:00
if ( list_state . empty ( ) )
{
2020-08-14 09:07:01 +02:00
MWARNING ( log_prefix ( context ) < < " Block producer (us) is not available on the service node list, waiting until next round " ) ;
2020-08-11 10:14:17 +02:00
return goto_preparing_for_next_round ( context ) ;
2020-08-10 12:13:13 +02:00
}
std : : shared_ptr < const service_nodes : : service_node_info > info = list_state [ 0 ] . info ;
if ( ! info - > is_active ( ) )
{
2020-08-14 09:07:01 +02:00
MWARNING ( log_prefix ( context ) < < " Block producer (us) is not an active service node, waiting until next round " ) ;
2020-08-11 10:14:17 +02:00
return goto_preparing_for_next_round ( context ) ;
2020-08-10 12:13:13 +02:00
}
2020-08-11 10:24:15 +02:00
// Block
2020-08-10 12:13:13 +02:00
cryptonote : : block block = { } ;
2020-08-11 10:24:15 +02:00
{
2020-08-25 06:56:30 +02:00
uint64_t height = 0 ;
2020-08-11 10:24:15 +02:00
service_nodes : : payout block_producer_payouts = service_nodes : : service_node_info_to_payout ( key . pub , * info ) ;
2020-09-24 07:32:58 +02:00
if ( ! blockchain . create_next_pulse_block_template ( block ,
block_producer_payouts ,
context . prepare_for_round . round ,
context . transient . wait_for_handshake_bitsets . best_bitset ,
height ) )
{
MERROR ( log_prefix ( context ) < < " Failed to generate a block template, waiting until next round " ) ;
return goto_preparing_for_next_round ( context ) ;
}
2020-08-10 12:13:13 +02:00
2020-08-25 06:56:30 +02:00
if ( context . wait_for_next_block . height ! = height )
{
MDEBUG ( log_prefix ( context ) < < " Block height changed whilst preparing block template for round " < < + context . prepare_for_round . round < < " , restarting Pulse stages " ) ;
2020-09-10 11:09:27 +02:00
return goto_wait_for_next_block_and_clear_round_data ( context ) ;
2020-08-25 06:56:30 +02:00
}
2020-08-11 10:24:15 +02:00
}
2020-08-10 12:13:13 +02:00
2020-08-11 10:24:15 +02:00
// Message
2020-08-13 12:26:23 +02:00
pulse : : message msg = msg_init_from_context ( context ) ;
2020-08-11 10:24:15 +02:00
msg . type = pulse : : message_type : : block_template ;
msg . block_template . blob = cryptonote : : t_serializable_object_to_blob ( block ) ;
2020-09-10 11:09:27 +02:00
crypto : : generate_signature ( msg_signature_hash ( context . wait_for_next_block . top_hash , msg ) , key . pub , key . key , msg . signature ) ;
2020-08-10 12:13:13 +02:00
2020-08-11 10:24:15 +02:00
// Send
2020-08-10 12:13:13 +02:00
MINFO ( log_prefix ( context ) < < " Validators are handshaken and ready, sending block template from producer (us) to validators. \n " < < cryptonote : : obj_to_json_str ( block ) ) ;
2020-08-11 10:24:15 +02:00
cryptonote : : quorumnet_pulse_relay_message_to_quorum ( quorumnet_state , msg , context . prepare_for_round . quorum , true /*block_producer*/ ) ;
2020-08-14 03:46:31 +02:00
return goto_preparing_for_next_round ( context ) ;
2020-08-10 12:13:13 +02:00
}
2020-09-08 05:56:01 +02:00
round_state wait_for_block_template ( round_context & context , service_nodes : : service_node_list & node_list , void * quorumnet_state , service_nodes : : service_node_keys const & key , cryptonote : : Blockchain & blockchain )
2020-08-10 12:13:13 +02:00
{
2020-08-13 13:09:04 +02:00
handle_messages_received_early_for ( context . transient . wait_for_block_template . stage , quorumnet_state ) ;
pulse_wait_stage const & stage = context . transient . wait_for_block_template . stage ;
2020-08-13 10:38:04 +02:00
2020-08-10 12:13:13 +02:00
assert ( context . prepare_for_round . participant = = sn_type : : validator ) ;
2020-08-13 13:09:04 +02:00
bool timed_out = pulse : : clock : : now ( ) > = context . transient . wait_for_block_template . stage . end_time ;
2020-09-08 05:56:01 +02:00
bool received = context . transient . wait_for_block_template . stage . msgs_received = = 1 ;
if ( timed_out | | received )
2020-08-10 12:13:13 +02:00
{
2020-09-08 05:56:01 +02:00
if ( received )
2020-08-10 12:13:13 +02:00
{
2020-08-13 13:09:04 +02:00
cryptonote : : block const & block = context . transient . wait_for_block_template . block ;
2020-08-14 03:53:52 +02:00
MINFO ( log_prefix ( context ) < < " Valid block received: " < < cryptonote : : obj_to_json_str ( context . transient . wait_for_block_template . block ) ) ;
2020-08-14 03:46:31 +02:00
2020-08-14 03:53:52 +02:00
// Generate my random value and its hash
crypto : : generate_random_bytes_thread_safe ( sizeof ( context . transient . random_value . send . data ) , context . transient . random_value . send . data . data ) ;
2020-08-18 03:30:58 +02:00
context . transient . random_value_hashes . send . data = blake2b_hash ( & context . transient . random_value . send . data , sizeof ( context . transient . random_value . send . data ) ) ;
2020-08-14 03:53:52 +02:00
return round_state : : send_and_wait_for_random_value_hashes ;
2020-08-10 12:13:13 +02:00
}
else
{
MINFO ( log_prefix ( context ) < < " Timed out, block template was not received " ) ;
2020-08-14 03:46:31 +02:00
return goto_preparing_for_next_round ( context ) ;
2020-08-10 12:13:13 +02:00
}
2020-08-11 10:14:17 +02:00
}
2020-08-14 03:46:31 +02:00
return round_state : : wait_for_block_template ;
2020-08-11 10:14:17 +02:00
}
2020-09-08 05:56:01 +02:00
round_state send_and_wait_for_random_value_hashes ( round_context & context , service_nodes : : service_node_list & node_list , void * quorumnet_state , service_nodes : : service_node_keys const & key )
2020-08-11 10:14:17 +02:00
{
assert ( context . prepare_for_round . participant = = sn_type : : validator ) ;
2020-08-14 03:46:31 +02:00
//
// NOTE: Send
//
if ( context . transient . random_value_hashes . send . one_time_only ( ) )
{
// Message
pulse : : message msg = msg_init_from_context ( context ) ;
msg . type = pulse : : message_type : : random_value_hash ;
msg . random_value_hash . hash = context . transient . random_value_hashes . send . data ;
2020-09-10 11:09:27 +02:00
crypto : : generate_signature ( msg_signature_hash ( context . wait_for_next_block . top_hash , msg ) , key . pub , key . key , msg . signature ) ;
2020-08-14 05:07:17 +02:00
handle_message ( quorumnet_state , msg ) ; // Add our own. We receive our own msg for the first time which also triggers us to relay.
2020-08-14 03:46:31 +02:00
}
2020-08-11 10:14:17 +02:00
2020-08-14 03:46:31 +02:00
//
// NOTE: Wait
//
handle_messages_received_early_for ( context . transient . random_value_hashes . wait . stage , quorumnet_state ) ;
pulse_wait_stage const & stage = context . transient . random_value_hashes . wait . stage ;
2020-08-13 10:38:04 +02:00
2020-08-14 03:46:31 +02:00
auto const & quorum = context . transient . random_value_hashes . wait . data ;
2020-08-13 10:38:04 +02:00
bool const timed_out = pulse : : clock : : now ( ) > = stage . end_time ;
2020-09-10 03:09:12 +02:00
bool const all_hashes = stage . bitset = = context . transient . wait_for_handshake_bitsets . best_bitset ;
2020-08-13 10:38:04 +02:00
2020-08-12 08:30:33 +02:00
if ( timed_out | | all_hashes )
2020-08-11 10:14:17 +02:00
{
2020-09-08 05:56:01 +02:00
if ( ! enforce_validator_participation_and_timeouts ( context , stage , node_list , timed_out , all_hashes ) )
2020-08-12 04:21:19 +02:00
return goto_preparing_for_next_round ( context ) ;
2020-08-11 10:14:17 +02:00
2020-09-10 03:09:12 +02:00
MINFO ( log_prefix ( context ) < < " Received " < < bitset_view16 ( stage . bitset ) . count ( ) < < " random value hashes from " < < bitset_view16 ( stage . bitset ) < < ( timed_out ? " . We timed out and some hashes are missing " : " " ) ) ;
2020-08-14 03:46:31 +02:00
return round_state : : send_and_wait_for_random_value ;
2020-08-10 12:13:13 +02:00
}
2020-08-14 03:46:31 +02:00
return round_state : : send_and_wait_for_random_value_hashes ;
2020-08-10 12:13:13 +02:00
}
2020-09-08 05:56:01 +02:00
round_state send_and_wait_for_random_value ( round_context & context , service_nodes : : service_node_list & node_list , void * quorumnet_state , service_nodes : : service_node_keys const & key )
2020-08-12 08:30:33 +02:00
{
2020-08-14 03:46:31 +02:00
//
// NOTE: Send
//
2020-08-12 08:30:33 +02:00
assert ( context . prepare_for_round . participant = = sn_type : : validator ) ;
2020-08-14 03:46:31 +02:00
if ( context . transient . random_value . send . one_time_only ( ) )
{
// Message
pulse : : message msg = msg_init_from_context ( context ) ;
msg . type = pulse : : message_type : : random_value ;
msg . random_value . value = context . transient . random_value . send . data ;
2020-09-10 11:09:27 +02:00
crypto : : generate_signature ( msg_signature_hash ( context . wait_for_next_block . top_hash , msg ) , key . pub , key . key , msg . signature ) ;
2020-08-14 05:07:17 +02:00
handle_message ( quorumnet_state , msg ) ; // Add our own. We receive our own msg for the first time which also triggers us to relay.
2020-08-14 03:46:31 +02:00
}
2020-08-12 08:30:33 +02:00
2020-08-14 03:46:31 +02:00
//
// NOTE: Wait
//
handle_messages_received_early_for ( context . transient . random_value . wait . stage , quorumnet_state ) ;
pulse_wait_stage const & stage = context . transient . random_value . wait . stage ;
2020-08-13 08:19:55 +02:00
2020-08-14 03:46:31 +02:00
auto const & quorum = context . transient . random_value . wait . data ;
2020-08-13 10:38:04 +02:00
bool const timed_out = pulse : : clock : : now ( ) > = stage . end_time ;
2020-09-10 03:09:12 +02:00
bool const all_values = stage . bitset = = context . transient . wait_for_handshake_bitsets . best_bitset ;
2020-08-12 08:30:33 +02:00
if ( timed_out | | all_values )
{
2020-09-08 05:56:01 +02:00
if ( ! enforce_validator_participation_and_timeouts ( context , stage , node_list , timed_out , all_values ) )
2020-08-12 08:30:33 +02:00
return goto_preparing_for_next_round ( context ) ;
// Generate Final Random Value
crypto : : hash final_hash = { } ;
{
2020-08-18 03:21:38 +02:00
unsigned char constexpr key [ crypto_generichash_KEYBYTES ] = { } ;
crypto_generichash_state state = { } ;
crypto_generichash_init ( & state , key , sizeof ( key ) , sizeof ( final_hash ) ) ;
for ( size_t index = 0 ; index < quorum . size ( ) ; index + + )
2020-08-12 08:30:33 +02:00
{
2020-08-18 03:21:38 +02:00
if ( auto & random_value = quorum [ index ] ; random_value )
{
epee : : wipeable_string string = lokimq : : to_hex ( tools : : view_guts ( random_value - > data ) ) ;
2020-08-14 06:16:11 +02:00
# if defined(NDEBUG)
2020-08-18 03:21:38 +02:00
// Mask the random value generated incase someone is snooping logs
// trying to derive the Service Node rng seed.
for ( int i = 2 ; i < static_cast < int > ( string . size ( ) ) - 2 ; i + + )
string . data ( ) [ i ] = ' . ' ;
2020-08-14 06:16:11 +02:00
# endif
2020-08-18 03:21:38 +02:00
MDEBUG ( log_prefix ( context ) < < " Final random value seeding with V[ " < < index < < " ] " < < string . view ( ) ) ;
crypto_generichash_update ( & state , random_value - > data , sizeof ( random_value - > data ) ) ;
}
2020-08-12 08:30:33 +02:00
}
2020-08-18 03:21:38 +02:00
crypto_generichash_final ( & state , reinterpret_cast < unsigned char * > ( final_hash . data ) , sizeof ( final_hash ) ) ;
2020-08-12 08:30:33 +02:00
}
2020-09-10 06:14:29 +02:00
// Add final random value to the block
context . transient . signed_block . final_block = std : : move ( context . transient . wait_for_block_template . block ) ;
cryptonote : : block & final_block = context . transient . signed_block . final_block ;
static_assert ( sizeof ( final_hash ) > = sizeof ( final_block . pulse . random_value . data ) ) ;
std : : memcpy ( final_block . pulse . random_value . data , final_hash . data , sizeof ( final_block . pulse . random_value . data ) ) ;
// Generate the signature of the final block (without any of the other
// Service Node signatures because we allow the first
// 'PULSE_BLOCK_REQUIRED_SIGNATURES' that arrive to be added. These can be
// inconsistent between syncing clients, as long as the signature itself is
// valid against the quorum Service Nodes).
crypto : : hash const & final_block_hash = cryptonote : : get_block_hash ( final_block ) ;
crypto : : generate_signature ( final_block_hash , key . pub , key . key , context . transient . signed_block . send . data ) ;
MINFO ( log_prefix ( context ) < < " Block final random value " < < lokimq : : to_hex ( tools : : view_guts ( final_block . pulse . random_value . data ) ) < < " generated from validators " < < bitset_view16 ( stage . bitset ) ) ;
2020-08-14 03:46:31 +02:00
return round_state : : send_and_wait_for_signed_blocks ;
2020-08-12 08:30:33 +02:00
}
2020-08-14 03:46:31 +02:00
return round_state : : send_and_wait_for_random_value ;
2020-08-12 10:22:23 +02:00
}
2020-09-08 05:56:01 +02:00
round_state send_and_wait_for_signed_blocks ( round_context & context , service_nodes : : service_node_list & node_list , void * quorumnet_state , service_nodes : : service_node_keys const & key , cryptonote : : core & core )
2020-08-12 10:22:23 +02:00
{
assert ( context . prepare_for_round . participant = = sn_type : : validator ) ;
2020-08-14 03:46:31 +02:00
//
// NOTE: Send
//
if ( context . transient . signed_block . send . one_time_only ( ) )
{
// Message
2020-09-10 06:14:29 +02:00
pulse : : message msg = msg_init_from_context ( context ) ;
msg . type = pulse : : message_type : : signed_block ;
msg . signed_block . signature_of_final_block_hash = context . transient . signed_block . send . data ;
2020-09-10 11:09:27 +02:00
crypto : : generate_signature ( msg_signature_hash ( context . wait_for_next_block . top_hash , msg ) , key . pub , key . key , msg . signature ) ;
2020-08-14 05:07:17 +02:00
handle_message ( quorumnet_state , msg ) ; // Add our own. We receive our own msg for the first time which also triggers us to relay.
2020-08-14 03:46:31 +02:00
}
2020-08-12 08:30:33 +02:00
2020-08-14 03:46:31 +02:00
//
// NOTE: Wait
//
handle_messages_received_early_for ( context . transient . signed_block . wait . stage , quorumnet_state ) ;
pulse_wait_stage const & stage = context . transient . signed_block . wait . stage ;
2020-08-13 08:19:55 +02:00
2020-08-14 03:46:31 +02:00
auto const & quorum = context . transient . signed_block . wait . data ;
2020-08-13 10:38:04 +02:00
bool const timed_out = pulse : : clock : : now ( ) > = stage . end_time ;
2020-09-10 03:09:12 +02:00
bool const enough = stage . bitset > = context . transient . wait_for_handshake_bitsets . best_bitset ;
2020-08-12 10:22:23 +02:00
if ( timed_out | | enough )
{
2020-09-08 05:56:01 +02:00
if ( ! enforce_validator_participation_and_timeouts ( context , stage , node_list , timed_out , enough ) )
2020-08-12 10:22:23 +02:00
return goto_preparing_for_next_round ( context ) ;
// Select signatures randomly so we don't always just take the first N required signatures.
// Then sort just the first N required signatures, so signatures are added
// to the block in sorted order, but were chosen randomly.
std : : array < size_t , service_nodes : : PULSE_QUORUM_NUM_VALIDATORS > indices = { } ;
2020-08-14 04:33:56 +02:00
size_t indices_count = 0 ;
// Pull out indices where we've received a signature
for ( size_t index = 0 ; index < quorum . size ( ) ; index + + )
2020-08-18 02:51:19 +02:00
if ( quorum [ index ] )
indices [ indices_count + + ] = index ;
2020-08-14 04:33:56 +02:00
2020-08-18 03:05:10 +02:00
// Random select from first 'N' PULSE_BLOCK_REQUIRED_SIGNATURES from indices_count entries.
2020-08-14 04:33:56 +02:00
assert ( indices_count > = service_nodes : : PULSE_BLOCK_REQUIRED_SIGNATURES ) ;
2020-08-18 03:05:10 +02:00
std : : array < size_t , service_nodes : : PULSE_BLOCK_REQUIRED_SIGNATURES > selected = { } ;
std : : sample ( indices . begin ( ) , indices . begin ( ) + indices_count , selected . begin ( ) , selected . size ( ) , tools : : rng ) ;
2020-08-12 10:22:23 +02:00
// Add Signatures
2020-09-10 06:14:29 +02:00
cryptonote : : block & final_block = context . transient . signed_block . final_block ;
2020-08-12 10:22:23 +02:00
for ( size_t index = 0 ; index < service_nodes : : PULSE_BLOCK_REQUIRED_SIGNATURES ; index + + )
{
2020-08-18 02:51:19 +02:00
uint16_t validator_index = indices [ index ] ;
auto const & signature = quorum [ validator_index ] ;
assert ( signature ) ;
MDEBUG ( log_prefix ( context ) < < " Signature added: " < < validator_index < < " : " < < context . prepare_for_round . quorum . validators [ validator_index ] < < " , " < < * signature ) ;
final_block . signatures . emplace_back ( validator_index , * signature ) ;
2020-08-12 10:22:23 +02:00
}
// Propagate Final Block
2020-08-14 09:07:01 +02:00
MDEBUG ( log_prefix ( context ) < < " Final signed block constructed \n " < < cryptonote : : obj_to_json_str ( final_block ) ) ;
2020-08-12 10:22:23 +02:00
cryptonote : : block_verification_context bvc = { } ;
2020-08-14 01:30:09 +02:00
if ( ! core . handle_block_found ( final_block , bvc ) )
return goto_preparing_for_next_round ( context ) ;
2020-08-12 10:22:23 +02:00
2020-09-10 11:09:27 +02:00
return goto_wait_for_next_block_and_clear_round_data ( context ) ;
2020-08-12 10:22:23 +02:00
}
2020-08-14 03:46:31 +02:00
return round_state : : send_and_wait_for_signed_blocks ;
2020-08-12 10:22:23 +02:00
}
2020-08-10 11:37:17 +02:00
void pulse : : main ( void * quorumnet_state , cryptonote : : core & core )
2020-07-27 07:54:02 +02:00
{
cryptonote : : Blockchain & blockchain = core . get_blockchain_storage ( ) ;
service_nodes : : service_node_keys const & key = core . get_service_keys ( ) ;
2020-07-28 08:56:11 +02:00
//
2020-08-10 11:37:17 +02:00
// NOTE: Early exit if too early
2020-07-28 08:56:11 +02:00
//
2020-09-18 21:39:50 +02:00
static uint64_t const hf16_height = cryptonote : : HardFork : : get_hardcoded_hard_fork_height ( blockchain . nettype ( ) , cryptonote : : network_version_16_pulse ) ;
2020-08-10 11:37:17 +02:00
if ( hf16_height = = cryptonote : : HardFork : : INVALID_HF_VERSION_HEIGHT )
2020-07-28 08:56:11 +02:00
{
2020-08-10 11:37:17 +02:00
for ( static bool once = true ; once ; once = ! once )
MERROR ( " Pulse: HF16 is not defined, pulse worker waiting " ) ;
return ;
}
2020-07-27 07:54:02 +02:00
2020-08-10 11:37:17 +02:00
if ( uint64_t height = blockchain . get_current_blockchain_height ( true /*lock*/ ) ; height < hf16_height )
{
for ( static bool once = true ; once ; once = ! once )
2020-08-14 09:07:01 +02:00
MDEBUG ( " Pulse: Network at block " < < height < < " is not ready for Pulse until block " < < hf16_height < < " , waiting " ) ;
2020-08-10 11:37:17 +02:00
return ;
2020-07-28 08:56:11 +02:00
}
2020-07-27 07:54:02 +02:00
2020-09-08 05:56:01 +02:00
service_nodes : : service_node_list & node_list = core . get_service_node_list ( ) ;
2020-08-14 03:46:31 +02:00
for ( auto last_state = round_state : : null_state ;
2020-08-19 07:17:17 +02:00
last_state ! = context . state | | last_state = = round_state : : null_state ; )
2020-07-27 07:54:02 +02:00
{
2020-08-19 07:17:17 +02:00
last_state = context . state ;
2020-08-10 11:37:17 +02:00
switch ( context . state )
2020-07-27 07:54:02 +02:00
{
2020-08-14 03:46:31 +02:00
case round_state : : null_state :
context . state = round_state : : wait_for_next_block ;
break ;
2020-08-10 11:37:17 +02:00
case round_state : : wait_for_next_block :
2020-08-14 03:46:31 +02:00
context . state = wait_for_next_block ( hf16_height , context , blockchain ) ;
2020-08-10 12:13:13 +02:00
break ;
2020-07-27 07:54:02 +02:00
2020-08-10 11:37:17 +02:00
case round_state : : prepare_for_round :
2020-08-14 03:46:31 +02:00
context . state = prepare_for_round ( context , key , blockchain ) ;
2020-08-10 12:13:13 +02:00
break ;
2020-08-10 11:37:17 +02:00
case round_state : : wait_for_round :
2020-08-14 03:46:31 +02:00
context . state = wait_for_round ( context , blockchain ) ;
2020-08-10 12:13:13 +02:00
break ;
2020-07-27 07:54:02 +02:00
2020-08-14 03:46:31 +02:00
case round_state : : send_and_wait_for_handshakes :
context . state = send_and_wait_for_handshakes ( context , quorumnet_state , key ) ;
2020-08-11 08:13:09 +02:00
break ;
2020-08-14 03:46:31 +02:00
case round_state : : send_handshake_bitsets :
context . state = send_handshake_bitsets ( context , quorumnet_state , key ) ;
2020-08-10 12:13:13 +02:00
break ;
2020-07-27 07:54:02 +02:00
2020-08-05 09:32:18 +02:00
case round_state : : wait_for_handshake_bitsets :
2020-09-08 05:56:01 +02:00
context . state = wait_for_handshake_bitsets ( context , node_list , quorumnet_state , key ) ;
2020-08-10 12:13:13 +02:00
break ;
2020-08-05 06:31:17 +02:00
case round_state : : wait_for_block_template :
2020-09-08 05:56:01 +02:00
context . state = wait_for_block_template ( context , node_list , quorumnet_state , key , blockchain ) ;
2020-08-11 10:14:17 +02:00
break ;
2020-08-12 08:30:33 +02:00
2020-08-14 03:46:31 +02:00
case round_state : : send_block_template :
context . state = send_block_template ( context , quorumnet_state , key , blockchain ) ;
2020-08-12 08:30:33 +02:00
break ;
2020-08-14 03:46:31 +02:00
case round_state : : send_and_wait_for_random_value_hashes :
2020-09-08 05:56:01 +02:00
context . state = send_and_wait_for_random_value_hashes ( context , node_list , quorumnet_state , key ) ;
2020-08-12 08:30:33 +02:00
break ;
2020-08-12 10:22:23 +02:00
2020-08-14 03:46:31 +02:00
case round_state : : send_and_wait_for_random_value :
2020-09-08 05:56:01 +02:00
context . state = send_and_wait_for_random_value ( context , node_list , quorumnet_state , key ) ;
2020-08-12 10:22:23 +02:00
break ;
2020-08-14 03:46:31 +02:00
case round_state : : send_and_wait_for_signed_blocks :
2020-09-08 05:56:01 +02:00
context . state = send_and_wait_for_signed_blocks ( context , node_list , quorumnet_state , key , core ) ;
2020-08-12 10:22:23 +02:00
break ;
2020-07-27 07:54:02 +02:00
}
}
}
2020-08-10 11:37:17 +02:00