forked from monero-project/monero
-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathpos.cpp
More file actions
executable file
·1776 lines (1518 loc) · 72.3 KB
/
Copy pathpos.cpp
File metadata and controls
executable file
·1776 lines (1518 loc) · 72.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include <array>
#include <mutex>
#include <chrono>
#include "epee/wipeable_string.h"
#include "epee/memwipe.h"
#include "epee/misc_log_ex.h"
#include "common/random.h"
#include "cryptonote_core.h"
#include "cryptonote_basic/hardfork.h"
#include "master_node_list.h"
#include "master_node_quorum_cop.h"
#include "master_node_rules.h"
extern "C"
{
#include <sodium/crypto_generichash.h>
};
#undef BELDEX_DEFAULT_LOG_CATEGORY
#define BELDEX_DEFAULT_LOG_CATEGORY "POS"
// Deliberately makes POS communications flakey for testing purposes:
//#define POS_TEST_CODE
enum struct round_state
{
null_state,
wait_for_next_block,
prepare_for_round,
wait_for_round,
send_and_wait_for_handshakes,
send_handshake_bitsets,
wait_for_handshake_bitsets,
send_block_template,
wait_for_block_template,
send_and_wait_for_random_value_hashes,
send_and_wait_for_random_value,
send_and_wait_for_signed_blocks,
};
constexpr std::string_view round_state_string(round_state state)
{
switch(state)
{
case round_state::null_state: return "XX Null State"sv;
case round_state::wait_for_next_block: return "Wait For Next Block"sv;
case round_state::prepare_for_round: return "Prepare For Round"sv;
case round_state::wait_for_round: return "Wait For Round"sv;
case round_state::send_and_wait_for_handshakes: return "Send & Wait For Handshakes"sv;
case round_state::send_handshake_bitsets: return "Send Validator Handshake Bitsets"sv;
case round_state::wait_for_handshake_bitsets: return "Wait For Validator Handshake Bitsets"sv;
case round_state::send_block_template: return "Send Block Template"sv;
case round_state::wait_for_block_template: return "Wait For Block Template"sv;
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;
}
return "Invalid2"sv;
}
enum struct mn_type
{
none,
producer,
validator,
};
enum struct queueing_state
{
empty,
received,
processed,
};
template <typename T>
using quorum_array = std::array<T, master_nodes::POS_QUORUM_NUM_VALIDATORS>;
// 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.
struct message_queue
{
quorum_array<std::pair<POS::message, queueing_state>> buffer;
size_t count;
};
struct POS_wait_stage
{
message_queue queue; // For messages from later stages that arrived before we reached that stage
uint16_t bitset; // Bitset of validators that we received a message from for this stage
uint16_t msgs_received; // Number of unique messages received in the stage
POS::time_point end_time; // Time at which the stage ends
};
template <typename T>
struct POS_send_stage
{
T data; // Data that must be sent to Nodes via Quorumnet
bool sent; // When true, data has been sent via Quorumnet once already.
bool one_time_only()
{
if (sent) return false;
sent = true;
return true;
}
};
struct round_history
{
uint64_t height;
uint8_t round;
crypto::hash top_block_hash;
master_nodes::quorum quorum;
};
struct round_context
{
// 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;
struct
{
uint64_t height; // Current blockchain height that POS wants to generate a block for
crypto::hash top_hash; // Latest block hash included in signatures for rejecting out of date nodes
POS::time_point round_0_start_time; // When round 0 should start and subsequent round timings are derived from.
} wait_for_next_block;
struct
{
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 POS ceremony will generate a block for
master_nodes::quorum quorum; // The block producer/validator participating in the next round
mn_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, POS_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.
POS::time_point start_time; // When the round starts
} prepare_for_round;
struct
{
struct
{
bool sent; // When true, handshake sent and waiting for other handshakes
quorum_array<bool> data; // Received data from messages from Quorumnet
POS_wait_stage stage;
} send_and_wait_for_handshakes;
struct
{
quorum_array<std::optional<uint16_t>> data;
POS_wait_stage stage;
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.
} wait_for_handshake_bitsets;
struct
{
cryptonote::block block; // The block template with the best validator bitset and POS round applied to it.
POS_wait_stage stage;
} wait_for_block_template;
struct
{
POS_send_stage<crypto::hash> send;
struct
{
quorum_array<std::optional<crypto::hash>> data;
POS_wait_stage stage;
} wait;
} random_value_hashes;
struct
{
POS_send_stage<cryptonote::POS_random_value> send;
struct
{
quorum_array<std::optional<cryptonote::POS_random_value>> data;
POS_wait_stage stage;
} wait;
} random_value;
struct
{
POS_send_stage<crypto::signature> send;
cryptonote::block final_block;
struct
{
quorum_array<std::optional<crypto::signature>> data;
POS_wait_stage stage;
} wait;
} signed_block;
} transient;
round_state state;
};
static round_context context;
namespace
{
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;
}
std::string log_prefix(round_context const &context)
{
std::stringstream result;
result << "POS 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 << " ";
result << "'" << round_state_string(context.state) << "' ";
return result.str();
}
std::bitset<sizeof(uint16_t) * 8> bitset_view16(uint16_t val)
{
std::bitset<sizeof(uint16_t) * 8> result = val;
return result;
}
//
// NOTE: POS::message Utilities
//
POS::message msg_init_from_context(round_context const &context)
{
POS::message result = {};
result.quorum_position = context.prepare_for_round.my_quorum_position;
result.round = context.prepare_for_round.round;
return result;
}
// 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.
crypto::hash msg_signature_hash(crypto::hash const &top_block_hash, POS::message const &msg)
{
crypto::hash result = {};
switch(msg.type)
{
case POS::message_type::invalid:
assert("Invalid Code Path" == nullptr);
break;
case POS::message_type::handshake:
{
auto buf = tools::memcpy_le(top_block_hash.data, msg.quorum_position, msg.round);
result = blake2b_hash(buf.data(), buf.size());
}
break;
case POS::message_type::handshake_bitset:
{
auto buf = tools::memcpy_le(msg.handshakes.validator_bitset, top_block_hash.data, msg.quorum_position, msg.round);
result = blake2b_hash(buf.data(), buf.size());
}
break;
case POS::message_type::block_template:
{
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());
}
break;
case POS::message_type::random_value_hash:
{
auto buf = tools::memcpy_le(top_block_hash.data, msg.quorum_position, msg.round, msg.random_value_hash.hash.data);
result = blake2b_hash(buf.data(), buf.size());
}
break;
case POS::message_type::random_value:
{
auto buf = tools::memcpy_le(top_block_hash.data, msg.quorum_position, msg.round, msg.random_value.value.data);
result = blake2b_hash(buf.data(), buf.size());
}
break;
case POS::message_type::signed_block:
{
crypto::signature const &final_signature = msg.signed_block.signature_of_final_block_hash;
auto buf = tools::memcpy_le(top_block_hash.data, msg.quorum_position, msg.round, final_signature.c.data, final_signature.r.data);
result = blake2b_hash(buf.data(), buf.size());
}
break;
}
return result;
}
// Generate a helper string that describes the origin of the message, i.e.
// 'Signed Block' at round 2 from 6:f9337ffc8bc30baf3fca92a13fa5a3a7ab7c93e69acb7136906e7feae9d3e769
// or
// <Message Type> at round <Round> from <Validator Index>:<Validator Public Key>
std::string msg_source_string(round_context const &context, POS::message const &msg)
{
if (msg.quorum_position >= context.prepare_for_round.quorum.validators.size()) return "XX";
std::stringstream stream;
stream << "'" << message_type_string(msg.type) << " at round " << +msg.round << " from " << msg.quorum_position;
if (context.state >= round_state::prepare_for_round)
{
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];
stream << ":" << key;
}
}
return stream.str();
}
bool msg_signature_check(POS::message const &msg, crypto::hash const &top_block_hash, master_nodes::quorum const &quorum, std::string *error)
{
std::stringstream stream;
BELDEX_DEFER {
if (error) *error = stream.str();
};
// Get Master Node Key
crypto::public_key const *key = nullptr;
switch (msg.type)
{
case POS::message_type::invalid:
{
assert("Invalid Code Path" == nullptr);
if (error) stream << log_prefix(context) << "Unhandled message type '" << POS::message_type_string(msg.type) << "' can not verify signature.";
return false;
}
break;
case POS::message_type::handshake: [[fallthrough]];
case POS::message_type::handshake_bitset: [[fallthrough]];
case POS::message_type::random_value_hash: [[fallthrough]];
case POS::message_type::random_value: [[fallthrough]];
case POS::message_type::signed_block:
{
if (msg.quorum_position >= static_cast<int>(quorum.validators.size()))
{
if (error) stream << log_prefix(context) << "Quorum position " << msg.quorum_position << " in POS message indexes oob";
return false;
}
key = &quorum.validators[msg.quorum_position];
}
break;
case POS::message_type::block_template:
{
if (msg.quorum_position != 0)
{
if (error) stream << log_prefix(context) << "Quorum position " << msg.quorum_position << " in POS message indexes oob";
return false;
}
key = &context.prepare_for_round.quorum.workers[0];
}
break;
}
if (!crypto::check_signature(msg_signature_hash(top_block_hash, msg), *key, msg.signature))
{
if (error) stream << log_prefix(context) << "Signature for " << msg_source_string(context, msg) << " at height " << context.wait_for_next_block.height << "; is invalid";
return false;
}
return true;
}
//
// NOTE: round_context Utilities
//
// Construct a POS::message for sending the handshake bit or bitset.
void relay_validator_handshake_bit_or_bitset(round_context const &context, void *quorumnet_state, master_nodes::master_node_keys const &key, bool sending_bitset)
{
assert(context.prepare_for_round.participant == mn_type::validator);
// Message
POS::message msg = msg_init_from_context(context);
if (sending_bitset)
{
msg.type = POS::message_type::handshake_bitset;
// Generate the bitset from our received handshakes.
auto const &quorum = context.transient.send_and_wait_for_handshakes.data;
for (size_t quorum_index = 0; quorum_index < quorum.size(); quorum_index++)
if (bool received = quorum[quorum_index]; received)
msg.handshakes.validator_bitset |= (1 << quorum_index);
}
else
{
msg.type = POS::message_type::handshake;
}
crypto::generate_signature(msg_signature_hash(context.wait_for_next_block.top_hash, msg), key.pub, key.key, msg.signature);
handle_message(quorumnet_state, msg); // Add our own. We receive our own msg for the first time which also triggers us to relay.
}
// 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.
void handle_messages_received_early_for(POS_wait_stage &stage, void *quorumnet_state)
{
if (!stage.queue.count)
return;
for (auto &[msg, queued] : stage.queue.buffer)
{
if (queued == queueing_state::received)
{
POS::handle_message(quorumnet_state, msg);
queued = queueing_state::processed;
}
}
}
// In POS, after the block template and validators are locked in, enforce that
// all participating validators are doing their job in the stage.
bool enforce_validator_participation_and_timeouts(round_context const &context,
POS_wait_stage const &stage,
master_nodes::master_node_list &node_list,
bool timed_out,
bool all_received)
{
assert(context.state > round_state::wait_for_handshake_bitsets);
uint16_t const validator_bitset = context.transient.wait_for_handshake_bitsets.best_bitset;
if (timed_out && !all_received)
{
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));
return false;
}
// NOTE: This is not technically meant to hit, internal invariant checking
// that should have been triggered earlier. Enforce validator participation is
// only called after the stage has ended/timed out.
bool unexpected_items = (stage.bitset | validator_bitset) != validator_bitset;
if (stage.msgs_received == 0 || unexpected_items)
{
MERROR(log_prefix(context) << "Internal error: expected bitset " << bitset_view16(validator_bitset) << ", but accepted and received " << bitset_view16(stage.bitset));
return false;
}
return true;
}
} // anonymous namespace
void POS::handle_message(void *quorumnet_state, POS::message const &msg)
{
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;
}
// TODO(beldex): 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.
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;
MTRACE(log_prefix(context) << "Received valid message from the past (round " << +msg.round << "), ignoring");
break;
} // else: Message has unknown origins, it is not something we know how to validate.
}
if (print_err)
MERROR(sig_check_err);
return;
}
POS_wait_stage *stage = nullptr;
switch(msg.type)
{
case POS::message_type::invalid:
{
MTRACE(log_prefix(context) << "Received invalid message type, dropped");
return;
}
case POS::message_type::handshake: stage = &context.transient.send_and_wait_for_handshakes.stage; break;
case POS::message_type::handshake_bitset: stage = &context.transient.wait_for_handshake_bitsets.stage; break;
case POS::message_type::block_template: stage = &context.transient.wait_for_block_template.stage; break;
case POS::message_type::random_value_hash: stage = &context.transient.random_value_hashes.wait.stage; break;
case POS::message_type::random_value: stage = &context.transient.random_value.wait.stage; break;
case POS::message_type::signed_block: stage = &context.transient.signed_block.wait.stage; break;
}
bool msg_received_early = false;
switch(msg.type)
{
case POS::message_type::invalid: assert("Invalid Code Path" != nullptr); return;
case POS::message_type::handshake: msg_received_early = (context.state < round_state::send_and_wait_for_handshakes); break;
case POS::message_type::handshake_bitset: msg_received_early = (context.state < round_state::wait_for_handshake_bitsets); break;
case POS::message_type::block_template: msg_received_early = (context.state < round_state::wait_for_block_template); break;
case POS::message_type::random_value_hash: msg_received_early = (context.state < round_state::send_and_wait_for_random_value_hashes); break;
case POS::message_type::random_value: msg_received_early = (context.state < round_state::send_and_wait_for_random_value); break;
case POS::message_type::signed_block: msg_received_early = (context.state < round_state::send_and_wait_for_signed_blocks); break;
}
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)
{
MTRACE(log_prefix(context) << "Message received early " << msg_source_string(context, msg) << ", queueing until we're ready.");
stage->queue.count++;
entry = std::move(msg);
queued = queueing_state::received;
}
return;
}
uint16_t const validator_bit = (1 << msg.quorum_position);
if (context.state > round_state::wait_for_handshake_bitsets &&
msg.type > POS::message_type::handshake_bitset)
{
// After the validator bitset has been set, the participating validators are
// locked in. Any stray messages from other validators are rejected.
if ((validator_bit & context.transient.wait_for_handshake_bitsets.best_bitset) == 0)
{
auto bitset_view = bitset_view16(context.transient.wait_for_handshake_bitsets.best_bitset);
MTRACE(log_prefix(context) << "Dropping " << msg_source_string(context, msg) << ". Not a locked in participant, bitset is " << bitset_view);
return;
}
}
if (msg.quorum_position >= master_nodes::POS_QUORUM_NUM_VALIDATORS)
{
MTRACE(log_prefix(context) << "Dropping " << msg_source_string(context, msg) << ". Message quorum position indexes oob");
return;
}
//
// Add Message Data to POS Stage
//
switch(msg.type)
{
case POS::message_type::invalid:
assert("Invalid Code Path" != nullptr);
return;
case POS::message_type::handshake:
{
auto &quorum = context.transient.send_and_wait_for_handshakes.data;
if (quorum[msg.quorum_position]) return;
quorum[msg.quorum_position] = true;
MTRACE(log_prefix(context) << "Received handshake with quorum position bit (" << msg.quorum_position << ") "
<< bitset_view16(validator_bit) << " saved to bitset "
<< bitset_view16(stage->bitset));
}
break;
case POS::message_type::handshake_bitset:
{
auto &quorum = context.transient.wait_for_handshake_bitsets.data;
auto &bitset = quorum[msg.quorum_position];
if (bitset) return;
bitset = msg.handshakes.validator_bitset;
}
break;
case POS::message_type::block_template:
{
if (stage->msgs_received == 1)
return;
cryptonote::block block = {};
if (!cryptonote::t_serializable_object_from_blob(block, msg.block_template.blob))
{
MTRACE(log_prefix(context) << "Received unparsable POS block template blob");
return;
}
if (block.POS.round != context.prepare_for_round.round)
{
MTRACE(log_prefix(context) << "Received POS block template specifying different round " << +block.POS.round
<< ", expected " << +context.prepare_for_round.round);
return;
}
if (block.POS.validator_bitset != context.transient.wait_for_handshake_bitsets.best_bitset)
{
auto block_bitset = bitset_view16(block.POS.validator_bitset);
auto our_bitset = bitset_view16(context.transient.wait_for_handshake_bitsets.best_bitset);
MTRACE(log_prefix(context) << "Received POS block template specifying different validator handshake bitsets " << block_bitset << ", expected " << our_bitset);
return;
}
context.transient.wait_for_block_template.block = std::move(block);
}
break;
case POS::message_type::random_value_hash:
{
auto &quorum = context.transient.random_value_hashes.wait.data;
auto &value = quorum[msg.quorum_position];
if (value) return;
value = msg.random_value_hash.hash;
}
break;
case POS::message_type::random_value:
{
auto &quorum = context.transient.random_value.wait.data;
auto &value = quorum[msg.quorum_position];
if (value) return;
if (auto const &hash = context.transient.random_value_hashes.wait.data[msg.quorum_position]; hash)
{
auto derived = blake2b_hash(msg.random_value.value.data, sizeof(msg.random_value.value.data));
if (derived != *hash)
{
MTRACE(log_prefix(context) << "Dropping " << msg_source_string(context, msg)
<< ". Rederived random value hash " << derived << " does not match original hash "
<< *hash);
return;
}
}
value = msg.random_value.value;
}
break;
case POS::message_type::signed_block:
{
// NOTE: The block template with the final random value inserted but no
// Master Node signatures. (Master 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))
{
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 Master Node");
return;
}
auto &quorum = context.transient.signed_block.wait.data;
auto &signature = quorum[msg.quorum_position];
if (signature) return;
signature = msg.signed_block.signature_of_final_block_hash;
}
break;
}
stage->bitset |= validator_bit;
stage->msgs_received++;
if (quorumnet_state)
cryptonote::quorumnet_POS_relay_message_to_quorum(quorumnet_state, msg, context.prepare_for_round.quorum, context.prepare_for_round.participant == mn_type::producer);
}
// TODO(doyle): Update POS::prepare_for_round with this function after the hard fork and sanity check it on testnet.
bool POS::convert_time_to_round(POS::time_point const& time, POS::time_point const& r0_timestamp, uint8_t* round)
{
const auto time_since_round_started = time <= r0_timestamp ? 0s : (time - r0_timestamp);
size_t result_usize = time_since_round_started / master_nodes::POS_ROUND_TIME;
if (round) *round = static_cast<uint8_t>(result_usize);
return result_usize <= master_nodes::POS_MAX_ROUNDS_BEFORE_NETWORK_STALLED;
}
bool POS::get_round_timings(cryptonote::Blockchain const &blockchain, uint64_t block_height, uint64_t prev_timestamp, POS::timings ×)
{
times = {};
auto hf17 = hard_fork_begins(blockchain.nettype(), cryptonote::hf::hf17_POS);
if (!hf17 || blockchain.get_current_blockchain_height() < *hf17)
return false;
cryptonote::block POS_genesis_block;
if (!blockchain.get_block_by_height(*hf17 - 1, POS_genesis_block))
return false;
uint64_t const delta_height = block_height - cryptonote::get_block_height(POS_genesis_block);
times.genesis_timestamp = POS::time_point(std::chrono::seconds(POS_genesis_block.timestamp));
times.prev_timestamp = POS::time_point(std::chrono::seconds(prev_timestamp));
times.ideal_timestamp = POS::time_point(times.genesis_timestamp + (cryptonote::TARGET_BLOCK_TIME * delta_height)); //only for POS
#if 1
times.r0_timestamp = std::clamp(times.ideal_timestamp,
times.prev_timestamp + master_nodes::POS_MIN_TARGET_BLOCK_TIME,
times.prev_timestamp + master_nodes::POS_MAX_TARGET_BLOCK_TIME);
#else // NOTE: Debug, make next block start relatively soon
times.r0_timestamp = times.prev_timestamp + master_nodes::POS_ROUND_TIME;
#endif
times.miner_fallback_timestamp = times.r0_timestamp + (master_nodes::POS_ROUND_TIME * master_nodes::POS_MAX_ROUNDS_BEFORE_NETWORK_STALLED);
return true;
}
/*
POS progresses via a state-machine that is iterated through job submissions
to 1 dedicated POS thread, started by OMQ.
Iterating the state-machine is done by a periodic invocation of
POS::main(...) and messages received via Quorumnet for POS, which are
queued in the thread's job queue.
Using 1 dedicated thread via OMQ avoids any synchronization required in the
user code when implementing POS.
Skip control flow graph for textual description of stages.
+---------------------+
| Wait For Next Block |<--------+-------+
+---------------------+ | |
| | |
+-[Blocks for round acquired]--+ No |
| | |
| Yes | |
| | |
+---------------------+ | |
+---->| Prepare For Round | | |
| +---------------------+ | |
| | | |
| [Enough MN's for POS]---------+ No |
| | |
| Yes |
| | |
| +---------------------+ |
| | Wait For Round | |
| +---------------------+ |
| | |
| [Block Height Changed?]-----------------+ Yes
| |
| | No
| |
No +-----[Participating in Quorum?]
| |
| | Yes
| |
| |
| [Validator?]------------------------------------+ No (We are Block Producer)
| | |
| | Yes |
| | |
| +-----------------------------+ |
| | Send And Wait For Handshakes| |
| +-----------------------------+ |
| | |
Yes +-----[Quorumnet Comm Failure] |
| | |
| | No |
| | |
| +-----------------------+ |
| | Send Handshake Bitset | |
| +-----------------------+ |
| | |
Yes +-----[Quorumnet Comm Failure] |
| | |
| | No |
| | |
| +----------------------------+ |
| | Wait For Handshake Bitsets |<-----------------+
| +----------------------------+
| |
Yes +-----[Insufficient Bitsets]
| |
| | No
| |
| [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]
| |
| | 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]
|
| No
|
+ (Finished, state machine resets)
Wait For Next Block:
- Waits for the next block in the blockchain to arrive
- Retrieves the blockchain metadata for starting a POS Round including the
Genesis POS Block for the base timestamp and the top block hash and
height for signatures.
- // TODO(beldex): After the Genesis POS 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.
- The ideal next block timestamp is determined by
G.Timestamp + (height * TARGET_BLOCK_TIME)
Where 'G' is the base POS genesis block, i.e. the hardforking block
activating POS (HF17).
The actual next block timestamp is determined by
P.Timestamp + (TARGET_BLOCK_TIME ±15s)
Where 'P' is the previous block. The block time is adjusted ±15s depending
on how close/far away the ideal block time is.
Prepare For Round:
- Generate data for executing the round such as the Quorum and stage
durations depending on the round POS 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 POS can not proceed
because of an insufficient Master Node network.
- If the next round to prepare for is >POS_MAX_ROUNDS_BEFORE_NETWORK_STALLED, we disable POS and re-allow
PoW blocks to be added to the chain, the POS state machine resets and
waits for the next block to arrive and re-evaluates if POS is possible
again.
Wait For Round (Block Producer & Validator)
- Checks clock against the next expected POS timestamps has arrived,
otherwise continues sleeping.
- 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
Otherwise we return to 'Prepare For Round' and sleep.
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.
Send Handshake Bitset (Validator)
- Send our collected participation bitset to the validators
Wait For Handshake Bitsets (Block Producer & Validator)
- 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
validators are participating, the round fails otherwise and reverts to
'Prepare For Round'.
- If we are a validator we go to 'Wait For Block Template'
- If we are a block producer we go to 'Submit Block Template'
Submit Block Template (Block Producer)
- Block producer signs the block template with the validator bitset and
POS 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 invocation, 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.
- If not all hashes are received according to the locked in validator bitset
in the block, we revert to 'Prepare For Round'.
Send And Wait For Random Value (Validator)
- On first invocation, 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 invocation, 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.
*/
round_state goto_preparing_for_next_round(round_context &context)
{
context.prepare_for_round.queue_for_next_round = true;
return round_state::prepare_for_round;
}
void clear_round_data(round_context &context)
{
if (master_nodes::verify_POS_quorum_sizes(context.prepare_for_round.quorum))
{