-
Notifications
You must be signed in to change notification settings - Fork 70
/
qc_chain.cpp
1224 lines (1003 loc) · 49.3 KB
/
qc_chain.cpp
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 <eosio/hotstuff/qc_chain.hpp>
/*
Todo list / notes:
- fork tests in unittests
- network plugin versioning
- handshake_message.network_version
- independant of protocol feature activation
- separate library for hotstuff (look at SHIP libray used by state history plugin )
- boost tests producer plugin test
- regression tests python framework as a base
- performance testing
- complete proposer / leader differentiation
- integration with new bls implementation
- hotstuff as a library with its own tests (model on state history plugin + state_history library )
- unit / integration tests -> producer_plugin + fork_tests tests as a model
- test deterministic sequence
- test non-replica participation
- test finality vioaltion
- test loss of liveness
- test split chain
- store schedules and transition view height, and prune on commit
- integration with fork_db / LIB overhaul
- integration with performance testing
- regression testing ci/cd -> python regression tests
- implement bitset for efficiency
- add APIs for proof data
- add election proposal in block header
- map proposers / finalizers / leader to new host functions
- support pause / resume producer
- keep track of proposals sent to peers
- allow syncing of proposals
- versioning of net protocol version
- protocol feature activation HOTSTUFF_CONSENSUS
- system contract update 1
-- allow BPs to register + prove their aggregate pub key.
-- Allow existing BPs to unreg + reg without new aggregate key.
-- Prevent new BPs from registering without proving aggregate pub key
- system contract update 2 (once all or at least overwhelming majority of BPs added a bls key)
-- skip BPs without a bls key in the selection, new host functions are available
*/
// FIXME/REMOVE: remove all of this tracing
// Enables extra logging to help with debugging
//#define QC_CHAIN_TRACE_DEBUG
namespace eosio { namespace hotstuff {
const hs_proposal_message* qc_chain::get_proposal(const fc::sha256& proposal_id) {
#ifdef QC_CHAIN_SIMPLE_PROPOSAL_STORE
if (proposal_id == NULL_PROPOSAL_ID)
return nullptr;
ph_iterator h_it = _proposal_height.find( proposal_id );
if (h_it == _proposal_height.end())
return nullptr;
uint64_t proposal_height = h_it->second;
ps_height_iterator psh_it = _proposal_stores_by_height.find( proposal_height );
if (psh_it == _proposal_stores_by_height.end())
return nullptr;
proposal_store & pstore = psh_it->second;
ps_iterator ps_it = pstore.find( proposal_id );
if (ps_it == pstore.end())
return nullptr;
const hs_proposal_message & proposal = ps_it->second;
return &proposal;
#else
proposal_store_type::nth_index<0>::type::iterator itr = _proposal_store.get<by_proposal_id>().find( proposal_id );
if (itr == _proposal_store.get<by_proposal_id>().end())
return nullptr;
return &(*itr);
#endif
}
bool qc_chain::insert_proposal(const hs_proposal_message & proposal) {
std::lock_guard g( _state_mutex );
#ifdef QC_CHAIN_SIMPLE_PROPOSAL_STORE
uint64_t proposal_height = proposal.get_height();
ps_height_iterator psh_it = _proposal_stores_by_height.find( proposal_height );
if (psh_it == _proposal_stores_by_height.end()) {
_proposal_stores_by_height.emplace( proposal_height, proposal_store() );
psh_it = _proposal_stores_by_height.find( proposal_height );
}
proposal_store & pstore = psh_it->second;
const fc::sha256 & proposal_id = proposal.proposal_id;
ps_iterator ps_it = pstore.find( proposal_id );
if (ps_it != pstore.end())
return false; // duplicate proposal insertion, so don't change anything actually
_proposal_height.emplace( proposal_id, proposal_height );
pstore.emplace( proposal_id, proposal );
return true;
#else
if (get_proposal( proposal.proposal_id ) != nullptr)
return false;
_proposal_store.insert(proposal); //new proposal
return true;
#endif
}
void qc_chain::get_state( finalizer_state& fs ) const {
std::lock_guard g( _state_mutex );
fs.chained_mode = _chained_mode;
fs.b_leaf = _b_leaf;
fs.b_lock = _b_lock;
fs.b_exec = _b_exec;
fs.b_finality_violation = _b_finality_violation;
fs.block_exec = _block_exec;
fs.pending_proposal_block = _pending_proposal_block;
fs.v_height = _v_height;
fs.high_qc = _high_qc;
fs.current_qc = _current_qc;
fs.schedule = _schedule;
#ifdef QC_CHAIN_SIMPLE_PROPOSAL_STORE
ps_height_iterator psh_it = _proposal_stores_by_height.begin();
while (psh_it != _proposal_stores_by_height.end()) {
proposal_store &pstore = psh_it->second;
ps_iterator ps_it = pstore.begin();
while (ps_it != pstore.end()) {
fs.proposals.insert( *ps_it );
++ps_it;
}
++psh_it;
}
#else
auto hgt_itr = _proposal_store.get<by_proposal_height>().begin();
auto end_itr = _proposal_store.get<by_proposal_height>().end();
while (hgt_itr != end_itr) {
const hs_proposal_message & p = *hgt_itr;
fs.proposals.emplace( p.proposal_id, p );
++hgt_itr;
}
#endif
}
uint32_t qc_chain::positive_bits_count(fc::unsigned_int value){
boost::dynamic_bitset b(21, value);
uint32_t count = 0;
for (boost::dynamic_bitset<>::size_type i = 0; i < b.size(); i++){
if (b[i]==true)count++;
}
return count;
}
fc::unsigned_int qc_chain::update_bitset(fc::unsigned_int value, name finalizer ) {
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === update bitset ${value} ${finalizer}",
("value", value)
("finalizer", finalizer));
#endif
boost::dynamic_bitset b( 21, value );
vector<name> finalizers = _pacemaker->get_finalizers();
for (size_t i = 0; i < finalizers.size();i++) {
if (finalizers[i] == finalizer) {
b.flip(i);
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === finalizer found ${finalizer} new value : ${value}",
("finalizer", finalizer)
("value", b.to_ulong()));
#endif
return b.to_ulong();
}
}
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" *** finalizer not found ${finalizer}",
("finalizer", finalizer));
#endif
throw std::runtime_error("qc_chain internal error: finalizer not found");
}
digest_type qc_chain::get_digest_to_sign(const block_id_type& block_id, uint8_t phase_counter, const fc::sha256& final_on_qc){
digest_type h1 = digest_type::hash( std::make_pair( block_id, phase_counter ) );
digest_type h2 = digest_type::hash( std::make_pair( h1, final_on_qc ) );
return h2;
}
std::vector<hs_proposal_message> qc_chain::get_qc_chain(const fc::sha256& proposal_id) {
std::vector<hs_proposal_message> ret_arr;
const hs_proposal_message *b, *b1, *b2;
b2 = get_proposal( proposal_id );
if (b2 != nullptr) {
ret_arr.push_back( *b2 );
b1 = get_proposal( b2->justify.proposal_id );
if (b1 != nullptr) {
ret_arr.push_back( *b1 );
b = get_proposal( b1->justify.proposal_id );
if (b != nullptr)
ret_arr.push_back( *b );
}
}
return ret_arr;
}
hs_proposal_message qc_chain::new_proposal_candidate(const block_id_type& block_id, uint8_t phase_counter) {
hs_proposal_message b_new;
b_new.block_id = block_id;
b_new.parent_id = _b_leaf;
b_new.phase_counter = phase_counter;
b_new.justify = _high_qc; //or null if no _high_qc upon activation or chain launch
if (b_new.justify.proposal_id != NULL_PROPOSAL_ID){
std::vector<hs_proposal_message> current_qc_chain = get_qc_chain(b_new.justify.proposal_id);
size_t chain_length = std::distance(current_qc_chain.begin(), current_qc_chain.end());
if (chain_length>=2){
auto itr = current_qc_chain.begin();
hs_proposal_message b2 = *itr;
itr++;
hs_proposal_message b1 = *itr;
if (b_new.parent_id == b2.proposal_id && b2.parent_id == b1.proposal_id) b_new.final_on_qc = b1.proposal_id;
else {
const hs_proposal_message *p = get_proposal( b1.parent_id );
//EOS_ASSERT( p != nullptr , chain_exception, "expected hs_proposal ${id} not found", ("id", b1.parent_id) );
if (p != nullptr) {
b_new.final_on_qc = p->final_on_qc;
} else {
if (_errors) ilog(" *** ${id} expected to find proposal in new_proposal_candidate() but not found : ${proposal_id}", ("id",_id)("proposal_id", b1.parent_id));
}
}
}
}
b_new.proposal_id = get_digest_to_sign(b_new.block_id, b_new.phase_counter, b_new.final_on_qc);
if (_log)
ilog(" === ${id} creating new proposal : block_num ${block_num} phase ${phase_counter} : proposal_id ${proposal_id} : parent_id ${parent_id} : justify ${justify}",
("id", _id)
("block_num", b_new.block_num())
("phase_counter", b_new.phase_counter)
("proposal_id", b_new.proposal_id)
("parent_id", b_new.parent_id)
("justify", b_new.justify.proposal_id));
return b_new;
}
void qc_chain::reset_qc(const fc::sha256& proposal_id){
std::lock_guard g( _state_mutex );
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} resetting qc : ${proposal_id}", ("proposal_id" , proposal_id)("id", _id));
#endif
_current_qc.proposal_id = proposal_id;
_current_qc.quorum_met = false;
_current_qc.active_finalizers = 0;
_current_qc.active_agg_sig = fc::crypto::blslib::bls_signature();
}
hs_new_block_message qc_chain::new_block_candidate(const block_id_type& block_id) {
hs_new_block_message b;
b.block_id = block_id;
b.justify = _high_qc; //or null if no _high_qc upon activation or chain launch
return b;
}
bool qc_chain::evaluate_quorum(const extended_schedule & es, fc::unsigned_int finalizers, const fc::crypto::blslib::bls_signature & agg_sig, const hs_proposal_message & proposal){
bool first = true;
if (positive_bits_count(finalizers) < _pacemaker->get_quorum_threshold()){
return false;
}
boost::dynamic_bitset fb(21, finalizers.value);
fc::crypto::blslib::bls_public_key agg_key;
for (boost::dynamic_bitset<>::size_type i = 0; i < fb.size(); i++) {
if (fb[i] == 1){
//adding finalizer's key to the aggregate pub key
if (first) {
first = false;
agg_key = _private_key.get_public_key();
}
else agg_key = fc::crypto::blslib::aggregate({agg_key, _private_key.get_public_key() });
}
}
#warning fix todo
// ****************************************************************************************************
// FIXME/TODO: I removed this since it doesn't seem to be doing anything at the moment
// ****************************************************************************************************
//
//fc::crypto::blslib::bls_signature justification_agg_sig;
//
//if (proposal.justify.proposal_id != NULL_PROPOSAL_ID) justification_agg_sig = proposal.justify.active_agg_sig;
digest_type digest = get_digest_to_sign(proposal.block_id, proposal.phase_counter, proposal.final_on_qc);
std::vector<uint8_t> h = std::vector<uint8_t>(digest.data(), digest.data() + 32);
bool ok = fc::crypto::blslib::verify(agg_key, h, agg_sig);
return ok;
}
bool qc_chain::is_quorum_met(const eosio::chain::quorum_certificate & qc, const extended_schedule & schedule, const hs_proposal_message & proposal){
if (qc.quorum_met) {
return true; //skip evaluation if we've already verified quorum was met
}
else {
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === qc : ${qc}", ("qc", qc));
#endif
// If the caller wants to update the quorum_met flag on its "qc" object, it will have to do so
// based on the return value of this method, since "qc" here is const.
return evaluate_quorum(schedule, qc.active_finalizers, qc.active_agg_sig, proposal);
}
}
qc_chain::qc_chain(name id, base_pacemaker* pacemaker, std::set<name> my_producers, bool info_logging, bool error_logging)
: _id(id),
_pacemaker(pacemaker),
_my_producers(std::move(my_producers)),
_log(info_logging),
_errors(error_logging)
{
if (_log) ilog(" === ${id} qc chain initialized ${my_producers}", ("my_producers", my_producers)("id", _id));
}
bool qc_chain::am_i_proposer(){
name proposer = _pacemaker->get_proposer();
auto prod_itr = std::find_if(_my_producers.begin(), _my_producers.end(), [&](const auto& asp){ return asp == proposer; });
if (prod_itr==_my_producers.end()) return false;
else return true;
}
bool qc_chain::am_i_leader(){
name leader = _pacemaker->get_leader();
auto prod_itr = std::find_if(_my_producers.begin(), _my_producers.end(), [&](const auto& asp){ return asp == leader; });
if (prod_itr==_my_producers.end()) return false;
else return true;
}
bool qc_chain::am_i_finalizer(){
std::vector<name> finalizers = _pacemaker->get_finalizers();
auto mf_itr = _my_producers.begin();
while(mf_itr!=_my_producers.end()){
name n = *mf_itr;
auto prod_itr = std::find_if(finalizers.begin(), finalizers.end(), [&](const auto& f){ return f == n; });
if (prod_itr!=finalizers.end()) return true;
mf_itr++;
}
return false;
}
hs_vote_message qc_chain::sign_proposal(const hs_proposal_message & proposal, name finalizer){
std::unique_lock state_lock( _state_mutex );
_v_height = proposal.get_height();
state_lock.unlock();
digest_type digest = get_digest_to_sign(proposal.block_id, proposal.phase_counter, proposal.final_on_qc);
std::vector<uint8_t> h = std::vector<uint8_t>(digest.data(), digest.data() + 32);
#warning use appropriate private key for each producer
fc::crypto::blslib::bls_signature sig = _private_key.sign(h); //FIXME/TODO: use appropriate private key for each producer
hs_vote_message v_msg = {proposal.proposal_id, finalizer, sig};
return v_msg;
}
void qc_chain::process_proposal(const hs_proposal_message & proposal){
//auto start = fc::time_point::now();
if (proposal.justify.proposal_id != NULL_PROPOSAL_ID){
const hs_proposal_message *jp = get_proposal( proposal.justify.proposal_id );
if (jp == nullptr) {
if (_errors) ilog(" *** ${id} proposal justification unknown : ${proposal_id}", ("id",_id)("proposal_id", proposal.justify.proposal_id));
return; //can't recognize a proposal with an unknown justification
}
}
const hs_proposal_message *p = get_proposal( proposal.proposal_id );
if (p != nullptr) {
if (_errors) ilog(" *** ${id} proposal received twice : ${proposal_id}", ("id",_id)("proposal_id", proposal.proposal_id));
if (p->justify.proposal_id != proposal.justify.proposal_id) {
if (_errors) ilog(" *** ${id} two identical proposals (${proposal_id}) have different justifications : ${justify_1} vs ${justify_2}",
("id",_id)
("proposal_id", proposal.proposal_id)
("justify_1", p->justify.proposal_id)
("justify_2", proposal.justify.proposal_id));
}
return; //already aware of proposal, nothing to do
}
#ifdef QC_CHAIN_SIMPLE_PROPOSAL_STORE
ps_height_iterator psh_it = _proposal_stores_by_height.find( proposal.get_height() );
if (psh_it != _proposal_stores_by_height.end())
{
proposal_store & pstore = psh_it->second;
ps_iterator ps_it = pstore.begin();
while (ps_it != pstore.end())
{
hs_proposal_message & existing_proposal = ps_it->second;
#else
//height is not necessarily unique, so we iterate over all prior proposals at this height
auto hgt_itr = _proposal_store.get<by_proposal_height>().lower_bound( proposal.get_height() );
auto end_itr = _proposal_store.get<by_proposal_height>().upper_bound( proposal.get_height() );
while (hgt_itr != end_itr)
{
const hs_proposal_message & existing_proposal = *hgt_itr;
#endif
if (_errors) ilog(" *** ${id} received a different proposal at the same height (${block_num}, ${phase_counter})",
("id",_id)
("block_num", existing_proposal.block_num())
("phase_counter", existing_proposal.phase_counter));
if (_errors) ilog(" *** Proposal #1 : ${proposal_id_1} Proposal #2 : ${proposal_id_2}",
("proposal_id_1", existing_proposal.proposal_id)
("proposal_id_2", proposal.proposal_id));
#ifdef QC_CHAIN_SIMPLE_PROPOSAL_STORE
++ps_it;
}
}
#else
hgt_itr++;
}
#endif
if (_log) ilog(" === ${id} received new proposal : block_num ${block_num} phase ${phase_counter} : proposal_id ${proposal_id} : parent_id ${parent_id} justify ${justify}",
("id", _id)
("block_num", proposal.block_num())
("phase_counter", proposal.phase_counter)
("proposal_id", proposal.proposal_id)
("parent_id", proposal.parent_id)
("justify", proposal.justify.proposal_id));
bool success = insert_proposal( proposal );
EOS_ASSERT( success , chain_exception, "internal error: duplicate proposal insert attempt" ); // can't happen unless bad mutex somewhere; already checked for this
//if I am a finalizer for this proposal and the safenode predicate for a possible vote is true, sign
bool am_finalizer = am_i_finalizer();
bool node_safe = is_node_safe(proposal);
bool signature_required = am_finalizer && node_safe;
std::vector<hs_vote_message> msgs;
if (signature_required){
//iterate over all my finalizers and sign / broadcast for each that is in the schedule
std::vector<name> finalizers = _pacemaker->get_finalizers();
auto mf_itr = _my_producers.begin();
while(mf_itr!=_my_producers.end()){
auto prod_itr = std::find(finalizers.begin(), finalizers.end(), *mf_itr);
if (prod_itr!=finalizers.end()) {
hs_vote_message v_msg = sign_proposal(proposal, *prod_itr);
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} signed proposal : block_num ${block_num} phase ${phase_counter} : proposal_id ${proposal_id}",
("id", _id)
("block_num", proposal.block_num())
("phase_counter", proposal.phase_counter)
("proposal_id", proposal.proposal_id));
#endif
//send_hs_vote_msg(v_msg);
msgs.push_back(v_msg);
};
mf_itr++;
}
}
#ifdef QC_CHAIN_TRACE_DEBUG
else if (_log) ilog(" === ${id} skipping signature on proposal : block_num ${block_num} phase ${phase_counter} : proposal_id ${proposal_id}",
("id", _id)
("block_num", proposal.block_num())
("phase_counter", proposal.phase_counter)
("proposal_id", proposal.proposal_id));
#endif
//update internal state
update(proposal);
for (auto &msg : msgs) {
send_hs_vote_msg(msg);
}
//check for leader change
leader_rotation_check();
//auto total_time = fc::time_point::now() - start;
//if (_log) ilog(" ... process_proposal() total time : ${total_time}", ("total_time", total_time));
}
void qc_chain::process_vote(const hs_vote_message & vote){
//auto start = fc::time_point::now();
#warning check for duplicate or invalid vote. We will return in either case, but keep proposals for evidence of double signing
//TODO: check for duplicate or invalid vote. We will return in either case, but keep proposals for evidence of double signing
bool am_leader = am_i_leader();
if (!am_leader)
return;
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === Process vote from ${finalizer} : current bitset ${value}" , ("finalizer", vote.finalizer)("value", _current_qc.active_finalizers));
#endif
// only leader need to take action on votes
if (vote.proposal_id != _current_qc.proposal_id)
return;
const hs_proposal_message *p = get_proposal( vote.proposal_id );
if (p == nullptr) {
if (_errors) ilog(" *** ${id} couldn't find proposal", ("id",_id));
if (_errors) ilog(" *** ${id} vote : ${vote}", ("vote", vote)("id",_id));
return;
}
bool quorum_met = _current_qc.quorum_met; //check if quorum already met
// If quorum is already met, we don't need to do anything else. Otherwise, we aggregate the signature.
if (!quorum_met){
std::unique_lock state_lock( _state_mutex );
if (_current_qc.active_finalizers>0)
_current_qc.active_agg_sig = fc::crypto::blslib::aggregate({_current_qc.active_agg_sig, vote.sig });
else
_current_qc.active_agg_sig = vote.sig;
_current_qc.active_finalizers = update_bitset(_current_qc.active_finalizers, vote.finalizer);
state_lock.unlock();
quorum_met = is_quorum_met(_current_qc, _schedule, *p);
if (quorum_met){
if (_log) ilog(" === ${id} quorum met on #${block_num} ${phase_counter} ${proposal_id} ",
("block_num", p->block_num())
("phase_counter", p->phase_counter)
("proposal_id", vote.proposal_id)
("id", _id));
state_lock.lock();
_current_qc.quorum_met = true;
state_lock.unlock();
//ilog(" === update_high_qc : _current_qc ===");
update_high_qc(_current_qc);
//check for leader change
leader_rotation_check();
//if we're operating in event-driven mode and the proposal hasn't reached the decide phase yet
if (_chained_mode == false && p->phase_counter < 3) {
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} phase increment on proposal ${proposal_id}", ("proposal_id", vote.proposal_id)("id", _id));
#endif
hs_proposal_message proposal_candidate;
if (_pending_proposal_block == NULL_BLOCK_ID)
proposal_candidate = new_proposal_candidate( p->block_id, p->phase_counter + 1 );
else
proposal_candidate = new_proposal_candidate( _pending_proposal_block, 0 );
reset_qc(proposal_candidate.proposal_id);
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} setting _pending_proposal_block to null (process_vote)", ("id", _id));
#endif
state_lock.lock();
_pending_proposal_block = NULL_BLOCK_ID;
_b_leaf = proposal_candidate.proposal_id;
state_lock.unlock();
send_hs_proposal_msg(proposal_candidate);
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} _b_leaf updated (process_vote): ${proposal_id}", ("proposal_id", proposal_candidate.proposal_id)("id", _id));
#endif
}
}
}
//auto total_time = fc::time_point::now() - start;
//if (_log) ilog(" ... process_vote() total time : ${total_time}", ("total_time", total_time));
}
void qc_chain::process_new_view(const hs_new_view_message & msg){
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} process_new_view === ${qc}", ("qc", msg.high_qc)("id", _id));
#endif
update_high_qc(msg.high_qc);
}
void qc_chain::process_new_block(const hs_new_block_message & msg){
// If I'm not a leader, I probably don't care about hs-new-block messages.
#warning check for a need to gossip/rebroadcast even if it's not for us (maybe here, maybe somewhere else).
// TODO: check for a need to gossip/rebroadcast even if it's not for us (maybe here, maybe somewhere else).
if (! am_i_leader()) {
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === ${id} process_new_block === discarding because I'm not the leader; block_id : ${bid}, justify : ${just}", ("bid", msg.block_id)("just", msg.justify)("id", _id));
#endif
return;
}
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} process_new_block === am leader; block_id : ${bid}, justify : ${just}", ("bid", msg.block_id)("just", msg.justify)("id", _id));
#endif
#warning What to do with the received msg.justify?
// ------------------------------------------------------------------
//
// FIXME/REVIEW/TODO: What to do with the received msg.justify?
//
// We are the leader, and we got a block_id from a proposer, but
// we should probably do something with the justify QC that
// comes with it (which is the _high_qc of the proposer (?))
//
// ------------------------------------------------------------------
if (_current_qc.proposal_id != NULL_PROPOSAL_ID && _current_qc.quorum_met == false) {
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} pending proposal found ${proposal_id} : quorum met ${quorum_met}",
("id", _id)
("proposal_id", _current_qc.proposal_id)
("quorum_met", _current_qc.quorum_met));
if (_log) ilog(" === ${id} setting _pending_proposal_block to ${block_id} (on_beat)", ("id", _id)("block_id", msg.block_id));
#endif
std::unique_lock state_lock( _state_mutex );
_pending_proposal_block = msg.block_id;
state_lock.unlock();
} else {
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} preparing new proposal ${proposal_id} : quorum met ${quorum_met}",
("id", _id)
("proposal_id", _current_qc.proposal_id)
("quorum_met", _current_qc.quorum_met));
#endif
hs_proposal_message proposal_candidate = new_proposal_candidate( msg.block_id, 0 );
reset_qc(proposal_candidate.proposal_id);
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} setting _pending_proposal_block to null (process_new_block)", ("id", _id));
#endif
std::unique_lock state_lock( _state_mutex );
_pending_proposal_block = NULL_BLOCK_ID;
_b_leaf = proposal_candidate.proposal_id;
state_lock.unlock();
send_hs_proposal_msg(proposal_candidate);
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} _b_leaf updated (on_beat): ${proposal_id}", ("proposal_id", proposal_candidate.proposal_id)("id", _id));
#endif
}
}
void qc_chain::send_hs_proposal_msg(const hs_proposal_message & msg){
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === broadcast_hs_proposal ===");
#endif
_pacemaker->send_hs_proposal_msg(msg, _id);
process_proposal(msg);
}
void qc_chain::send_hs_vote_msg(const hs_vote_message & msg){
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === broadcast_hs_vote ===");
#endif
_pacemaker->send_hs_vote_msg(msg, _id);
process_vote(msg);
}
void qc_chain::send_hs_new_view_msg(const hs_new_view_message & msg){
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === broadcast_hs_new_view ===");
#endif
_pacemaker->send_hs_new_view_msg(msg, _id);
}
void qc_chain::send_hs_new_block_msg(const hs_new_block_message & msg){
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === broadcast_hs_new_block ===");
#endif
_pacemaker->send_hs_new_block_msg(msg, _id);
}
//extends predicate
bool qc_chain::extends(const fc::sha256& descendant, const fc::sha256& ancestor){
#warning confirm the extends predicate never has to verify extension of irreversible blocks, otherwise this function needs to be modified
//TODO: confirm the extends predicate never has to verify extension of irreversible blocks, otherwise this function needs to be modified
uint32_t counter = 0;
const hs_proposal_message *p = get_proposal( descendant );
while (p != nullptr) {
fc::sha256 parent_id = p->parent_id;
p = get_proposal( parent_id );
if (p == nullptr) {
if (_errors) ilog(" *** ${id} cannot find proposal id while looking for ancestor : ${proposal_id}", ("id",_id)("proposal_id", parent_id));
return false;
}
if (p->proposal_id == ancestor) {
if (counter > 25) {
if (_errors) ilog(" *** ${id} took ${counter} iterations to find ancestor ", ("id",_id)("counter", counter));
}
return true;
}
++counter;
}
if (_errors) ilog(" *** ${id} extends returned false : could not find ${d_proposal_id} descending from ${a_proposal_id} ",
("id",_id)
("d_proposal_id", descendant)
("a_proposal_id", ancestor));
return false;
}
// Invoked when we could perhaps make a proposal to the network (or to ourselves, if we are the leader).
void qc_chain::on_beat(){
// Non-proposing leaders do not care about on_beat(), because leaders react to a block proposal
// which comes from processing an incoming new block message from a proposer instead.
// on_beat() is called by the pacemaker, which decides when it's time to check whether we are
// proposers that should check whether as proposers we should propose a new hotstuff block to
// the network (or to ourselves, which is faster and doesn't require the bandwidth of an additional
// gossip round for a new proposed block).
// The current criteria for a leader selecting a proposal among all proposals it receives is to go
// with the first valid one that it receives. So if a proposer is also a leader, it silently goes
// with its own proposal, which is hopefully valid at the point of generation which is also the
// point of consumption.
//
if (! am_i_proposer())
return;
block_id_type current_block_id = _pacemaker->get_current_block_id();
hs_new_block_message block_candidate = new_block_candidate( current_block_id );
if (am_i_leader()) {
// I am the proposer; so this assumes that no additional proposal validation is required.
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === I am a leader-proposer that is proposing a block for itself to lead");
#endif
// Hardwired consumption by self; no networking.
process_new_block( block_candidate );
} else {
// I'm only a proposer and not the leader; send a new-block-proposal message out to
// the network, until it reaches the leader.
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === broadcasting new block = #${block_height} ${proposal_id}", ("proposal_id", block_candidate.block_id)("block_height",compute_block_num(block_candidate.block_id) ));
#endif
send_hs_new_block_msg( block_candidate );
}
}
void qc_chain::update_high_qc(const eosio::chain::quorum_certificate & high_qc){
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === check to update high qc ${proposal_id}", ("proposal_id", high_qc.proposal_id));
#endif
// if new high QC is higher than current, update to new
if (_high_qc.proposal_id == NULL_PROPOSAL_ID){
std::unique_lock state_lock( _state_mutex );
_high_qc = high_qc;
_b_leaf = _high_qc.proposal_id;
state_lock.unlock();
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} _b_leaf updated (update_high_qc) : ${proposal_id}", ("proposal_id", _high_qc.proposal_id)("id", _id));
#endif
} else {
const hs_proposal_message *old_high_qc_prop = get_proposal( _high_qc.proposal_id );
const hs_proposal_message *new_high_qc_prop = get_proposal( high_qc.proposal_id );
if (old_high_qc_prop == nullptr)
return;
if (new_high_qc_prop == nullptr)
return;
if (new_high_qc_prop->get_height() > old_high_qc_prop->get_height()
&& is_quorum_met(high_qc, _schedule, *new_high_qc_prop))
{
// "The caller does not need this updated on their high_qc structure" -- g
//high_qc.quorum_met = true;
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === updated high qc, now is : #${get_height} ${proposal_id}", ("get_height", new_high_qc_prop->get_height())("proposal_id", new_high_qc_prop->proposal_id));
#endif
std::unique_lock state_lock( _state_mutex );
_high_qc = high_qc;
_high_qc.quorum_met = true;
_b_leaf = _high_qc.proposal_id;
state_lock.unlock();
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} _b_leaf updated (update_high_qc) : ${proposal_id}", ("proposal_id", _high_qc.proposal_id)("id", _id));
#endif
}
}
}
void qc_chain::leader_rotation_check(){
//verify if leader changed
name current_leader = _pacemaker->get_leader();
name next_leader = _pacemaker->get_next_leader();
if (current_leader != next_leader){
if (_log) ilog(" /// ${id} rotating leader : ${old_leader} -> ${new_leader} ",
("id", _id)
("old_leader", current_leader)
("new_leader", next_leader));
//leader changed, we send our new_view message
reset_qc(NULL_PROPOSAL_ID);
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} setting _pending_proposal_block to null (leader_rotation_check)", ("id", _id));
#endif
std::unique_lock state_lock( _state_mutex );
_pending_proposal_block = NULL_BLOCK_ID;
state_lock.unlock();
hs_new_view_message new_view;
new_view.high_qc = _high_qc;
send_hs_new_view_msg(new_view);
}
}
//safenode predicate
bool qc_chain::is_node_safe(const hs_proposal_message & proposal){
//ilog(" === is_node_safe ===");
bool monotony_check = false;
bool safety_check = false;
bool liveness_check = false;
bool final_on_qc_check = false;
fc::sha256 upcoming_commit;
if (proposal.justify.proposal_id == NULL_PROPOSAL_ID && _b_lock == NULL_PROPOSAL_ID)
final_on_qc_check = true; //if chain just launched or feature just activated
else {
std::vector<hs_proposal_message> current_qc_chain = get_qc_chain(proposal.justify.proposal_id);
size_t chain_length = std::distance(current_qc_chain.begin(), current_qc_chain.end());
if (chain_length >= 2) {
auto itr = current_qc_chain.begin();
hs_proposal_message b2 = *itr;
++itr;
hs_proposal_message b1 = *itr;
if (proposal.parent_id == b2.proposal_id && b2.parent_id == b1.proposal_id)
upcoming_commit = b1.proposal_id;
else {
const hs_proposal_message *p = get_proposal( b1.parent_id );
//EOS_ASSERT( p != nullptr , chain_exception, "expected hs_proposal ${id} not found", ("id", b1.parent_id) );
if (p != nullptr) {
upcoming_commit = p->final_on_qc;
} else {
if (_errors) ilog(" *** ${id} in is_node_safe did not find expected proposal id: ${proposal_id}", ("id",_id)("proposal_id", b1.parent_id));
}
}
}
//abstracted [...]
if (upcoming_commit == proposal.final_on_qc) {
final_on_qc_check = true;
}
}
if (proposal.get_height() > _v_height) {
monotony_check = true;
}
if (_b_lock != NULL_PROPOSAL_ID){
//Safety check : check if this proposal extends the chain I'm locked on
if (extends(proposal.proposal_id, _b_lock)) {
safety_check = true;
}
//Liveness check : check if the height of this proposal's justification is higher than the height of the proposal I'm locked on. This allows restoration of liveness if a replica is locked on a stale block.
if (proposal.justify.proposal_id == NULL_PROPOSAL_ID && _b_lock == NULL_PROPOSAL_ID) {
liveness_check = true; //if there is no justification on the proposal and I am not locked on anything, means the chain just launched or feature just activated
} else {
const hs_proposal_message *b_lock = get_proposal( _b_lock );
EOS_ASSERT( b_lock != nullptr , chain_exception, "expected hs_proposal ${id} not found", ("id", _b_lock) );
const hs_proposal_message *prop_justification = get_proposal( proposal.justify.proposal_id );
EOS_ASSERT( prop_justification != nullptr , chain_exception, "expected hs_proposal ${id} not found", ("id", proposal.justify.proposal_id) );
if (prop_justification->get_height() > b_lock->get_height()) {
liveness_check = true;
}
}
} else {
//if we're not locked on anything, means the protocol just activated or chain just launched
liveness_check = true;
safety_check = true;
#ifdef QC_CHAIN_TRACE_DEBUG
if (_log) ilog(" === ${id} not locked on anything, liveness and safety are true", ("id", _id));
#endif
}
#ifdef QC_CHAIN_TRACE_DEBUG
ilog(" === final_on_qc_check : ${final_on_qc_check}, monotony_check : ${monotony_check}, liveness_check : ${liveness_check}, safety_check : ${safety_check}",
("final_on_qc_check", final_on_qc_check)
("monotony_check", monotony_check)
("liveness_check", liveness_check)
("safety_check", safety_check));
#endif
bool node_is_safe = final_on_qc_check && monotony_check && (liveness_check || safety_check);
if (!node_is_safe) {
if (_errors)
ilog(" *** node is NOT safe. Checks : final_on_qc: ${final_on_qc}, monotony_check: ${monotony_check}, liveness_check: ${liveness_check}, safety_check: ${safety_check})",
("final_on_qc_check",final_on_qc_check)
("monotony_check",monotony_check)
("liveness_check",liveness_check)
("safety_check",safety_check));
}
//return true if monotony check and at least one of liveness or safety check evaluated successfully
return final_on_qc_check && monotony_check && (liveness_check || safety_check);
}
//on proposal received, called from network thread
void qc_chain::on_hs_proposal_msg(const hs_proposal_message & msg){
process_proposal(msg);
}
//on vote received, called from network thread
void qc_chain::on_hs_vote_msg(const hs_vote_message & msg){
process_vote(msg);
}
//on new view received, called from network thread
void qc_chain::on_hs_new_view_msg(const hs_new_view_message & msg){
process_new_view(msg);
}
//on new block received, called from network thread
void qc_chain::on_hs_new_block_msg(const hs_new_block_message & msg){
process_new_block(msg);
}
void qc_chain::update(const hs_proposal_message & proposal){
//ilog(" === update internal state ===");
//if proposal has no justification, means we either just activated the feature or launched the chain, or the proposal is invalid
if (proposal.justify.proposal_id == NULL_PROPOSAL_ID){
if (_log) ilog(" === ${id} proposal has no justification ${proposal_id}", ("proposal_id", proposal.proposal_id)("id", _id));
return;
}
std::vector<hs_proposal_message> current_qc_chain = get_qc_chain(proposal.justify.proposal_id);
size_t chain_length = std::distance(current_qc_chain.begin(), current_qc_chain.end());
const hs_proposal_message *b_lock = get_proposal( _b_lock );
EOS_ASSERT( b_lock != nullptr || _b_lock == NULL_PROPOSAL_ID , chain_exception, "expected hs_proposal ${id} not found", ("id", _b_lock) );
//ilog(" === update_high_qc : proposal.justify ===");
update_high_qc(proposal.justify);
if (chain_length<1){