-
Notifications
You must be signed in to change notification settings - Fork 547
/
Copy pathproof_of_stake.ml
4659 lines (4336 loc) · 190 KB
/
proof_of_stake.ml
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
open Async_kernel
open Core_kernel
open Signed
open Unsigned
open Currency
open Fold_lib
open Signature_lib
open Snark_params
open Num_util
module Segment_id = Mina_numbers.Nat.Make32 ()
module Wire_types = Mina_wire_types.Consensus_proof_of_stake
module Make_sig (A : Wire_types.Types.S) = struct
module type S =
Proof_of_stake_intf.Full
with type Data.Consensus_state.Value.Stable.V2.t =
A.Data.Consensus_state.Value.V2.t
end
module Make_str (A : Wire_types.Concrete) = struct
module Time = Block_time
module Run = Snark_params.Tick.Run
module Length = Mina_numbers.Length
module type CONTEXT = sig
val logger : Logger.t
val constraint_constants : Genesis_constants.Constraint_constants.t
val consensus_constants : Constants.t
end
let make_checked t = Snark_params.Tick.Run.make_checked t
let name = "proof_of_stake"
let genesis_ledger_total_currency ~ledger =
Mina_ledger.Ledger.foldi ~init:Amount.zero (Lazy.force ledger)
~f:(fun _addr sum (account : Mina_base.Account.t) ->
(* only default token matters for total currency used to determine stake *)
if Mina_base.(Token_id.equal account.token_id Token_id.default) then
Amount.add sum (Balance.to_amount @@ account.balance)
|> Option.value_exn ?here:None ?error:None
~message:"failed to calculate total currency in genesis ledger"
else sum )
let genesis_ledger_hash ~ledger =
Mina_ledger.Ledger.merkle_root (Lazy.force ledger)
|> Mina_base.Frozen_ledger_hash.of_ledger_hash
let compute_delegatee_table keys ~iter_accounts =
let open Mina_base in
let outer_table = Public_key.Compressed.Table.create () in
iter_accounts (fun i (acct : Account.t) ->
if
Option.is_some acct.delegate
(* Only default tokens may delegate. *)
&& Token_id.equal acct.token_id Token_id.default
&& Public_key.Compressed.Set.mem keys (Option.value_exn acct.delegate)
then
Public_key.Compressed.Table.update outer_table
(Option.value_exn acct.delegate) ~f:(function
| None ->
Account.Index.Table.of_alist_exn [ (i, acct) ]
| Some table ->
Account.Index.Table.add_exn table ~key:i ~data:acct ;
table ) ) ;
(* TODO: this metric tracking currently assumes that the result of
compute_delegatee_table is called with the full set of block production
keypairs every time the set changes, which is true right now, but this
should be control flow should be refactored to make this clearer *)
let num_delegators =
Public_key.Compressed.Table.fold outer_table ~init:0
~f:(fun ~key:_ ~data sum -> sum + Account.Index.Table.length data)
in
Mina_metrics.Gauge.set Mina_metrics.Consensus.staking_keypairs
(Float.of_int @@ Public_key.Compressed.Set.length keys) ;
Mina_metrics.Gauge.set Mina_metrics.Consensus.stake_delegators
(Float.of_int num_delegators) ;
outer_table
let compute_delegatee_table_ledger_db keys ledger =
O1trace.sync_thread "compute_delegatee_table_ledger_db" (fun () ->
compute_delegatee_table keys ~iter_accounts:(fun f ->
Mina_ledger.Ledger.Db.iteri ledger ~f:(fun i acct -> f i acct) ) )
let compute_delegatee_table_genesis_ledger keys ledger =
O1trace.sync_thread "compute_delegatee_table_genesis_ledger" (fun () ->
compute_delegatee_table keys ~iter_accounts:(fun f ->
Mina_ledger.Ledger.iteri ledger ~f:(fun i acct -> f i acct) ) )
module Typ = Snark_params.Tick.Typ
module Configuration = struct
[%%versioned
module Stable = struct
module V1 = struct
type t =
{ delta : int
; k : int
; slots_per_epoch : int
; slot_duration : int
; epoch_duration : int
; genesis_state_timestamp : Block_time.Stable.V1.t
; acceptable_network_delay : int
}
[@@deriving yojson, fields]
let to_latest = Fn.id
end
end]
let t ~constraint_constants ~protocol_constants =
let constants =
Constants.create ~constraint_constants ~protocol_constants
in
let of_int32 = UInt32.to_int in
let of_span = Fn.compose Int64.to_int Block_time.Span.to_ms in
{ delta = of_int32 constants.delta
; k = of_int32 constants.k
; slots_per_epoch = of_int32 constants.slots_per_epoch
; slot_duration = of_span constants.slot_duration_ms
; epoch_duration = of_span constants.epoch_duration
; genesis_state_timestamp = constants.genesis_state_timestamp
; acceptable_network_delay = of_span constants.delta_duration
}
end
module Constants = Constants
module Genesis_epoch_data = Genesis_epoch_data
module Data = struct
module Epoch_seed = struct
include Mina_base.Epoch_seed
let initial : t = of_hash Outside_hash_image.t
let update (seed : t) vrf_result =
let open Random_oracle in
hash ~init:Hash_prefix_states.epoch_seed
[| (seed :> Tick.Field.t); vrf_result |]
|> of_hash
let update_var (seed : var) vrf_result =
let open Random_oracle.Checked in
make_checked (fun () ->
hash ~init:Hash_prefix_states.epoch_seed
[| var_to_hash_packed seed; vrf_result |]
|> var_of_hash_packed )
end
module Epoch_and_slot = struct
type t = Epoch.t * Slot.t [@@deriving sexp]
let of_time_exn ~(constants : Constants.t) tm : t =
let epoch = Epoch.of_time_exn tm ~constants in
let time_since_epoch =
Time.diff tm (Epoch.start_time epoch ~constants)
in
let slot =
uint32_of_int64
@@ Int64.Infix.(
Time.Span.to_ms time_since_epoch
/ Time.Span.to_ms constants.slot_duration_ms)
in
(epoch, slot)
end
module Block_data = struct
type t =
{ stake_proof : Stake_proof.t
; global_slot : Mina_numbers.Global_slot_since_hard_fork.t
; global_slot_since_genesis : Mina_numbers.Global_slot_since_genesis.t
; vrf_result : Random_oracle.Digest.t
}
let prover_state { stake_proof; _ } = stake_proof
let global_slot { global_slot; _ } = global_slot
let epoch_ledger { stake_proof; _ } = stake_proof.ledger
let global_slot_since_genesis { global_slot_since_genesis; _ } =
global_slot_since_genesis
let coinbase_receiver { stake_proof; _ } =
stake_proof.coinbase_receiver_pk
end
module Epoch_data_for_vrf = struct
[%%versioned
module Stable = struct
[@@@no_toplevel_latest_type]
module V2 = struct
type t =
{ epoch_ledger : Mina_base.Epoch_ledger.Value.Stable.V1.t
; epoch_seed : Mina_base.Epoch_seed.Stable.V1.t
; epoch : Mina_numbers.Length.Stable.V1.t
; global_slot : Mina_numbers.Global_slot_since_hard_fork.Stable.V1.t
; global_slot_since_genesis :
Mina_numbers.Global_slot_since_genesis.Stable.V1.t
; delegatee_table :
Mina_base.Account.Stable.V2.t
Mina_base.Account.Index.Stable.V1.Table.t
Public_key.Compressed.Stable.V1.Table.t
}
[@@deriving sexp]
let to_latest = Fn.id
end
end]
type t = Stable.Latest.t =
{ epoch_ledger : Mina_base.Epoch_ledger.Value.t
; epoch_seed : Mina_base.Epoch_seed.t
; epoch : Mina_numbers.Length.t
; global_slot : Mina_numbers.Global_slot_since_hard_fork.t
; global_slot_since_genesis : Mina_numbers.Global_slot_since_genesis.t
; delegatee_table :
Mina_base.Account.t Mina_base.Account.Index.Table.t
Public_key.Compressed.Table.t
}
[@@deriving sexp]
end
module Slot_won = struct
[%%versioned
module Stable = struct
[@@@no_toplevel_latest_type]
module V2 = struct
type t =
{ delegator :
Public_key.Compressed.Stable.V1.t
* Mina_base.Account.Index.Stable.V1.t
; producer : Keypair.Stable.V1.t
; global_slot : Mina_numbers.Global_slot_since_hard_fork.Stable.V1.t
; global_slot_since_genesis :
Mina_numbers.Global_slot_since_genesis.Stable.V1.t
; vrf_result : Consensus_vrf.Output_hash.Stable.V1.t
}
[@@deriving sexp]
let to_latest = Fn.id
end
end]
type t = Stable.Latest.t =
{ delegator : Public_key.Compressed.t * Mina_base.Account.Index.t
; producer : Keypair.t
; global_slot : Mina_numbers.Global_slot_since_hard_fork.t
; global_slot_since_genesis : Mina_numbers.Global_slot_since_genesis.t
; vrf_result : Consensus_vrf.Output_hash.t
}
[@@deriving sexp]
end
module Local_state = struct
module Snapshot = struct
module Ledger_snapshot = struct
type t =
| Genesis_epoch_ledger of Mina_ledger.Ledger.t
| Ledger_db of Mina_ledger.Ledger.Db.t
let merkle_root = function
| Genesis_epoch_ledger ledger ->
Mina_ledger.Ledger.merkle_root ledger
| Ledger_db ledger ->
Mina_ledger.Ledger.Db.merkle_root ledger
let compute_delegatee_table keys ledger =
match ledger with
| Genesis_epoch_ledger ledger ->
compute_delegatee_table_genesis_ledger keys ledger
| Ledger_db ledger ->
compute_delegatee_table_ledger_db keys ledger
let close = function
| Genesis_epoch_ledger _ ->
()
| Ledger_db ledger ->
Mina_ledger.Ledger.Db.close ledger
let remove ~location = function
| Genesis_epoch_ledger _ ->
()
| Ledger_db ledger ->
Mina_ledger.Ledger.Db.close ledger ;
File_system.rmrf location
let ledger_subset keys ledger =
let open Mina_ledger in
match ledger with
| Genesis_epoch_ledger ledger ->
Sparse_ledger.of_ledger_subset_exn ledger keys
| Ledger_db db_ledger ->
let ledger = Ledger.of_database db_ledger in
let subset_ledger =
Sparse_ledger.of_ledger_subset_exn ledger keys
in
ignore
( Ledger.unregister_mask_exn ~loc:__LOC__ ledger
: Ledger.unattached_mask ) ;
subset_ledger
end
type t =
{ ledger : Ledger_snapshot.t
; delegatee_table :
Mina_base.Account.t Mina_base.Account.Index.Table.t
Public_key.Compressed.Table.t
}
let delegators t key =
Public_key.Compressed.Table.find t.delegatee_table key
let to_yojson { ledger; delegatee_table } =
`Assoc
[ ( "ledger_hash"
, Ledger_snapshot.merkle_root ledger
|> Mina_base.Ledger_hash.to_yojson )
; ( "delegators"
, `Assoc
( Hashtbl.to_alist delegatee_table
|> List.map ~f:(fun (key, delegators) ->
( Public_key.Compressed.to_string key
, `Assoc
( Hashtbl.to_alist delegators
|> List.map ~f:(fun (addr, account) ->
( Int.to_string addr
, Mina_base.Account.to_yojson account ) ) )
) ) ) )
]
let ledger t = t.ledger
end
module Data = struct
type epoch_ledger_uuids =
{ staking : Uuid.t
; next : Uuid.t
; genesis_state_hash : Mina_base.State_hash.t
}
(* Invariant: Snapshot's delegators are taken from accounts in block_production_pubkeys *)
type t =
{ mutable staking_epoch_snapshot : Snapshot.t
; mutable next_epoch_snapshot : Snapshot.t
; last_checked_slot_and_epoch :
(Epoch.t * Slot.t) Public_key.Compressed.Table.t
; mutable last_epoch_delegatee_table :
Mina_base.Account.t Mina_base.Account.Index.Table.t
Public_key.Compressed.Table.t
Option.t
; mutable epoch_ledger_uuids : epoch_ledger_uuids
; epoch_ledger_location : string
}
let to_yojson t =
`Assoc
[ ( "staking_epoch_snapshot"
, [%to_yojson: Snapshot.t] t.staking_epoch_snapshot )
; ( "next_epoch_snapshot"
, [%to_yojson: Snapshot.t] t.next_epoch_snapshot )
; ( "last_checked_slot_and_epoch"
, `Assoc
( Public_key.Compressed.Table.to_alist
t.last_checked_slot_and_epoch
|> List.map ~f:(fun (key, epoch_and_slot) ->
( Public_key.Compressed.to_string key
, [%to_yojson: Epoch.t * Slot.t] epoch_and_slot ) ) )
)
]
end
(* The outer ref changes whenever we swap in new staker set; all the snapshots are recomputed *)
type t = Data.t ref [@@deriving to_yojson]
let staking_epoch_ledger_location (t : t) =
!t.epoch_ledger_location ^ Uuid.to_string !t.epoch_ledger_uuids.staking
let next_epoch_ledger_location (t : t) =
!t.epoch_ledger_location ^ Uuid.to_string !t.epoch_ledger_uuids.next
let current_epoch_delegatee_table ~(local_state : t) =
!local_state.staking_epoch_snapshot.delegatee_table
let last_epoch_delegatee_table ~(local_state : t) =
!local_state.last_epoch_delegatee_table
let current_block_production_keys t =
Public_key.Compressed.Table.keys !t.Data.last_checked_slot_and_epoch
|> Public_key.Compressed.Set.of_list
let make_last_checked_slot_and_epoch_table old_table new_keys ~default =
let module Set = Public_key.Compressed.Set in
let module Table = Public_key.Compressed.Table in
let last_checked_slot_and_epoch = Table.create () in
Set.iter new_keys ~f:(fun pk ->
let data = Option.value (Table.find old_table pk) ~default in
Table.add_exn last_checked_slot_and_epoch ~key:pk ~data ) ;
last_checked_slot_and_epoch
let epoch_ledger_uuids_to_yojson
Data.{ staking; next; genesis_state_hash } =
`Assoc
[ ("staking", `String (Uuid.to_string staking))
; ("next", `String (Uuid.to_string next))
; ( "genesis_state_hash"
, Mina_base.State_hash.to_yojson genesis_state_hash )
]
let epoch_ledger_uuids_from_file location =
let open Yojson.Safe.Util in
let open Result.Let_syntax in
let json = Yojson.Safe.from_file location in
let uuid str =
Result.(
map_error
(try_with (fun () -> Uuid.of_string str))
~f:(fun ex -> Exn.to_string ex))
in
let%bind staking = json |> member "staking" |> to_string |> uuid in
let%bind next = json |> member "next" |> to_string |> uuid in
let%map genesis_state_hash =
json |> member "genesis_state_hash" |> Mina_base.State_hash.of_yojson
in
Data.{ staking; next; genesis_state_hash }
let create_epoch_ledger ~location ~context:(module Context : CONTEXT)
~genesis_epoch_ledger =
let open Context in
if Sys.file_exists location then (
[%log info]
~metadata:[ ("location", `String location) ]
"Loading epoch ledger from disk: $location" ;
Snapshot.Ledger_snapshot.Ledger_db
(Mina_ledger.Ledger.Db.create ~directory_name:location
~depth:constraint_constants.ledger_depth () ) )
else Genesis_epoch_ledger (Lazy.force genesis_epoch_ledger)
let create block_producer_pubkeys ~context:(module Context : CONTEXT)
~genesis_ledger ~genesis_epoch_data ~epoch_ledger_location
~genesis_state_hash =
let open Context in
(* TODO: remove this duplicate of the genesis ledger *)
let genesis_epoch_ledger_staking, genesis_epoch_ledger_next =
Option.value_map genesis_epoch_data
~default:(genesis_ledger, genesis_ledger)
~f:(fun { Genesis_epoch_data.staking; next } ->
( staking.ledger
, Option.value_map next ~default:staking.ledger ~f:(fun next ->
next.ledger ) ) )
in
let epoch_ledger_uuids_location = epoch_ledger_location ^ ".json" in
let create_new_uuids () =
let epoch_ledger_uuids =
Data.
{ staking = Uuid_unix.create ()
; next = Uuid_unix.create ()
; genesis_state_hash
}
in
Yojson.Safe.to_file epoch_ledger_uuids_location
(epoch_ledger_uuids_to_yojson epoch_ledger_uuids) ;
epoch_ledger_uuids
in
let ledger_location uuid =
epoch_ledger_location ^ Uuid.to_string uuid
in
let epoch_ledger_uuids =
if Sys.file_exists epoch_ledger_uuids_location then (
let epoch_ledger_uuids =
match
epoch_ledger_uuids_from_file epoch_ledger_uuids_location
with
| Ok res ->
res
| Error str ->
[%log error]
"Failed to read epoch ledger uuids from file $path: \
$error. Creating new uuids.."
~metadata:
[ ("path", `String epoch_ledger_uuids_location)
; ("error", `String str)
] ;
create_new_uuids ()
in
(*If the genesis hash matches and both the files are present. If only one of them is present then it could be stale data and might cause the node to never be able to bootstrap*)
if
Mina_base.State_hash.equal epoch_ledger_uuids.genesis_state_hash
genesis_state_hash
then epoch_ledger_uuids
else
(*Clean-up outdated epoch ledgers*)
let staking_ledger_location =
ledger_location epoch_ledger_uuids.staking
in
let next_ledger_location =
ledger_location epoch_ledger_uuids.next
in
[%log info]
"Cleaning up old epoch ledgers with genesis state $state_hash \
at locations $staking and $next"
~metadata:
[ ( "state_hash"
, Mina_base.State_hash.to_yojson
epoch_ledger_uuids.genesis_state_hash )
; ("staking", `String staking_ledger_location)
; ("next", `String next_ledger_location)
] ;
File_system.rmrf staking_ledger_location ;
File_system.rmrf next_ledger_location ;
create_new_uuids () )
else create_new_uuids ()
in
let staking_epoch_ledger_location =
ledger_location epoch_ledger_uuids.staking
in
let staking_epoch_ledger =
create_epoch_ledger ~location:staking_epoch_ledger_location
~context:(module Context)
~genesis_epoch_ledger:genesis_epoch_ledger_staking
in
let next_epoch_ledger_location =
ledger_location epoch_ledger_uuids.next
in
let next_epoch_ledger =
create_epoch_ledger ~location:next_epoch_ledger_location
~context:(module Context)
~genesis_epoch_ledger:genesis_epoch_ledger_next
in
ref
{ Data.staking_epoch_snapshot =
{ Snapshot.ledger = staking_epoch_ledger
; delegatee_table =
Snapshot.Ledger_snapshot.compute_delegatee_table
block_producer_pubkeys staking_epoch_ledger
}
; next_epoch_snapshot =
{ Snapshot.ledger = next_epoch_ledger
; delegatee_table =
Snapshot.Ledger_snapshot.compute_delegatee_table
block_producer_pubkeys next_epoch_ledger
}
; last_checked_slot_and_epoch =
make_last_checked_slot_and_epoch_table
(Public_key.Compressed.Table.create ())
block_producer_pubkeys ~default:(Epoch.zero, Slot.zero)
; last_epoch_delegatee_table = None
; epoch_ledger_uuids
; epoch_ledger_location
}
let block_production_keys_swap ~(constants : Constants.t) t
block_production_pubkeys now =
let old : Data.t = !t in
let s { Snapshot.ledger; delegatee_table = _ } =
{ Snapshot.ledger
; delegatee_table =
Snapshot.Ledger_snapshot.compute_delegatee_table
block_production_pubkeys ledger
}
in
t :=
{ Data.staking_epoch_snapshot = s old.staking_epoch_snapshot
; next_epoch_snapshot =
s old.next_epoch_snapshot
(* assume these keys are different and therefore we haven't checked any
* slots or epochs *)
; last_checked_slot_and_epoch =
make_last_checked_slot_and_epoch_table
!t.Data.last_checked_slot_and_epoch block_production_pubkeys
~default:
((* TODO: Be smarter so that we don't have to look at the slot before again *)
let epoch, slot =
Epoch_and_slot.of_time_exn now ~constants
in
( epoch
, UInt32.(
if compare slot zero > 0 then sub slot one else slot) )
)
; last_epoch_delegatee_table = None
; epoch_ledger_uuids = old.epoch_ledger_uuids
; epoch_ledger_location = old.epoch_ledger_location
}
type snapshot_identifier = Staking_epoch_snapshot | Next_epoch_snapshot
[@@deriving to_yojson, equal]
let get_snapshot (t : t) id =
match id with
| Staking_epoch_snapshot ->
!t.staking_epoch_snapshot
| Next_epoch_snapshot ->
!t.next_epoch_snapshot
let set_snapshot (t : t) id v =
match id with
| Staking_epoch_snapshot ->
!t.staking_epoch_snapshot <- v
| Next_epoch_snapshot ->
!t.next_epoch_snapshot <- v
let reset_snapshot (t : t) id ledger =
let delegatee_table =
compute_delegatee_table_ledger_db
(current_block_production_keys t)
ledger
in
match id with
| Staking_epoch_snapshot ->
!t.staking_epoch_snapshot <-
{ delegatee_table
; ledger = Snapshot.Ledger_snapshot.Ledger_db ledger
}
| Next_epoch_snapshot ->
!t.next_epoch_snapshot <-
{ delegatee_table
; ledger = Snapshot.Ledger_snapshot.Ledger_db ledger
}
let next_epoch_ledger (t : t) =
Snapshot.ledger @@ get_snapshot t Next_epoch_snapshot
let staking_epoch_ledger (t : t) =
Snapshot.ledger @@ get_snapshot t Staking_epoch_snapshot
let _seen_slot (t : t) epoch slot =
let module Table = Public_key.Compressed.Table in
let unseens =
Table.to_alist !t.last_checked_slot_and_epoch
|> List.filter_map ~f:(fun (pk, last_checked_epoch_and_slot) ->
let i =
Tuple2.compare ~cmp1:Epoch.compare ~cmp2:Slot.compare
last_checked_epoch_and_slot (epoch, slot)
in
if i > 0 then None
else if i = 0 then
(*vrf evaluation was stopped at this point because it was either the end of the epoch or the key won this slot; re-check this slot when staking keys are reset so that we don't skip producing block. This will not occur in the normal flow because [slot] will be greater than the last-checked-slot*)
Some pk
else (
Table.set !t.last_checked_slot_and_epoch ~key:pk
~data:(epoch, slot) ;
Some pk ) )
in
match unseens with
| [] ->
`All_seen
| nel ->
`Unseen (Public_key.Compressed.Set.of_list nel)
module For_tests = struct
type nonrec snapshot_identifier = snapshot_identifier =
| Staking_epoch_snapshot
| Next_epoch_snapshot
let set_snapshot = set_snapshot
(* if all we're testing is the ledger sync, empty delegatee table sufficient *)
let snapshot_of_ledger (ledger : Snapshot.Ledger_snapshot.t) :
Snapshot.t =
{ ledger; delegatee_table = Public_key.Compressed.Table.create () }
end
end
module Epoch_ledger = struct
include Mina_base.Epoch_ledger
let genesis ~ledger =
{ Poly.hash = genesis_ledger_hash ~ledger
; total_currency = genesis_ledger_total_currency ~ledger
}
let graphql_type () : ('ctx, Value.t option) Graphql_async.Schema.typ =
let open Graphql_async in
let open Schema in
obj "epochLedger" ~fields:(fun _ ->
[ field "hash"
~typ:
(non_null @@ Mina_base_unix.Graphql_scalars.LedgerHash.typ ())
~args:Arg.[]
~resolve:(fun _ { Poly.hash; _ } -> hash)
; field "totalCurrency"
~typ:(non_null @@ Currency_unix.Graphql_scalars.Amount.typ ())
~args:Arg.[]
~resolve:(fun _ { Poly.total_currency; _ } -> total_currency)
] )
end
module Vrf = struct
include Consensus_vrf
module T = Integrated
type _ Snarky_backendless.Request.t +=
| Winner_address :
Mina_base.Account.Index.t Snarky_backendless.Request.t
| Winner_pk : Public_key.Compressed.t Snarky_backendless.Request.t
| Coinbase_receiver_pk :
Public_key.Compressed.t Snarky_backendless.Request.t
| Producer_private_key : Scalar.value Snarky_backendless.Request.t
| Producer_public_key : Public_key.t Snarky_backendless.Request.t
let%snarkydef.Snark_params.Tick get_vrf_evaluation
~(constraint_constants : Genesis_constants.Constraint_constants.t)
shifted ~block_stake_winner ~block_creator ~ledger ~message =
let open Mina_base in
let open Snark_params.Tick in
let%bind private_key =
request_witness Scalar.typ (As_prover.return Producer_private_key)
in
let staker_addr = message.Message.delegator in
let%bind account =
with_label __LOC__ (fun () ->
Frozen_ledger_hash.get ~depth:constraint_constants.ledger_depth
ledger staker_addr )
in
let%bind () =
[%with_label_ "Account is for the default token"] (fun () ->
make_checked (fun () ->
Token_id.(
Checked.Assert.equal account.token_id
(Checked.constant default)) ) )
in
let%bind () =
[%with_label_ "Block stake winner matches account pk"] (fun () ->
Public_key.Compressed.Checked.Assert.equal block_stake_winner
account.public_key )
in
let%bind () =
[%with_label_ "Block creator matches delegate pk"] (fun () ->
Public_key.Compressed.Checked.Assert.equal block_creator
account.delegate )
in
let%bind delegate =
[%with_label_ "Decompress delegate pk"] (fun () ->
Public_key.decompress_var account.delegate )
in
let%map evaluation =
with_label __LOC__ (fun () ->
T.Checked.eval_and_check_public_key shifted ~private_key
~public_key:delegate message )
in
(evaluation, account)
module Checked = struct
let%snarkydef.Tick check
~(constraint_constants : Genesis_constants.Constraint_constants.t)
shifted ~(epoch_ledger : Epoch_ledger.var) ~block_stake_winner
~block_creator ~global_slot ~seed =
let open Snark_params.Tick in
let%bind winner_addr =
request_witness
(Mina_base.Account.Index.Unpacked.typ
~ledger_depth:constraint_constants.ledger_depth )
(As_prover.return Winner_address)
in
let%bind result, winner_account =
get_vrf_evaluation ~constraint_constants shifted
~ledger:epoch_ledger.hash ~block_stake_winner ~block_creator
~message:{ Message.global_slot; seed; delegator = winner_addr }
in
let my_stake = winner_account.balance in
let%bind truncated_result = Output.Checked.truncate result in
let%map satisifed =
Threshold.Checked.is_satisfied ~my_stake
~total_stake:epoch_ledger.total_currency truncated_result
in
(satisifed, result, truncated_result, winner_account)
end
let eval = T.eval
module Precomputed = struct
let keypairs = Lazy.force Key_gen.Sample_keypairs.keypairs
let genesis_winner = keypairs.(0)
let genesis_stake_proof :
genesis_epoch_ledger:Mina_ledger.Ledger.t Lazy.t -> Stake_proof.t =
fun ~genesis_epoch_ledger ->
let pk, sk = genesis_winner in
let dummy_sparse_ledger =
Mina_ledger.Sparse_ledger.of_ledger_subset_exn
(Lazy.force genesis_epoch_ledger)
[ Mina_base.(Account_id.create pk Token_id.default) ]
in
{ delegator = 0
; delegator_pk = pk
; coinbase_receiver_pk = pk
; ledger = dummy_sparse_ledger
; producer_private_key = sk
; producer_public_key = Public_key.decompress_exn pk
}
let handler :
constraint_constants:Genesis_constants.Constraint_constants.t
-> genesis_epoch_ledger:Mina_ledger.Ledger.t Lazy.t
-> Snark_params.Tick.Handler.t =
fun ~constraint_constants ~genesis_epoch_ledger ->
let pk, sk = genesis_winner in
let dummy_sparse_ledger =
Mina_ledger.Sparse_ledger.of_ledger_subset_exn
(Lazy.force genesis_epoch_ledger)
[ Mina_base.(Account_id.create pk Token_id.default) ]
in
let empty_pending_coinbase =
Mina_base.Pending_coinbase.create
~depth:constraint_constants.pending_coinbase_depth ()
|> Or_error.ok_exn
in
let ledger_handler =
unstage (Mina_ledger.Sparse_ledger.handler dummy_sparse_ledger)
in
let pending_coinbase_handler =
unstage
(Mina_base.Pending_coinbase.handler
~depth:constraint_constants.pending_coinbase_depth
empty_pending_coinbase ~is_new_stack:true )
in
let handlers =
Snarky_backendless.Request.Handler.(
push
(push fail (create_single pending_coinbase_handler))
(create_single ledger_handler))
in
fun (With { request; respond }) ->
match request with
| Winner_address ->
respond (Provide 0)
| Winner_pk ->
respond (Provide pk)
| Coinbase_receiver_pk ->
respond (Provide pk)
| Producer_private_key ->
respond (Provide sk)
| Producer_public_key ->
respond (Provide (Public_key.decompress_exn pk))
| _ ->
respond
(Provide
(Option.value_exn ~message:"unhandled request"
(Snarky_backendless.Request.Handler.run handlers request) )
)
end
let check ~context:(module Context : CONTEXT)
~(global_slot : Mina_numbers.Global_slot_since_hard_fork.t) ~seed
~producer_private_key ~producer_public_key ~total_stake
~(get_delegators :
Public_key.Compressed.t
-> Mina_base.Account.t Mina_base.Account.Index.Table.t option ) =
let open Context in
let open Message in
let open Interruptible.Let_syntax in
let delegators =
get_delegators producer_public_key
|> Option.value_map ~f:Hashtbl.to_alist ~default:[]
in
let rec go acc = function
| [] ->
Interruptible.return acc
| (delegator, (account : Mina_base.Account.t)) :: delegators ->
let%bind () = Interruptible.return () in
let vrf_result =
T.eval ~constraint_constants ~private_key:producer_private_key
{ global_slot; seed; delegator }
in
let truncated_vrf_result = Output.truncate vrf_result in
[%log debug]
"VRF result for delegator: $delegator, balance: $balance, \
amount: $amount, result: $result"
~metadata:
[ ( "delegator"
, `Int (Mina_base.Account.Index.to_int delegator) )
; ( "delegator_pk"
, Public_key.Compressed.to_yojson account.public_key )
; ("balance", `Int (Balance.to_nanomina_int account.balance))
; ("amount", `Int (Amount.to_nanomina_int total_stake))
; ( "result"
, `String
(* use sexp representation; int might be too small *)
( Fold.string_bits truncated_vrf_result
|> Bignum_bigint.of_bit_fold_lsb
|> Bignum_bigint.sexp_of_t |> Sexp.to_string ) )
] ;
Mina_metrics.Counter.inc_one
Mina_metrics.Consensus.vrf_evaluations ;
if
Threshold.is_satisfied ~my_stake:account.balance ~total_stake
truncated_vrf_result
then
let string_of_blake2 =
Blake2.(Fn.compose to_raw_string digest_string)
in
let vrf_eval = string_of_blake2 truncated_vrf_result in
let this_vrf () =
go
(Some
( `Vrf_eval vrf_eval
, `Vrf_output vrf_result
, `Delegator (account.public_key, delegator) ) )
delegators
in
match acc with
| Some (`Vrf_eval prev_best_vrf_eval, _, _) ->
if String.compare prev_best_vrf_eval vrf_eval < 0 then
this_vrf ()
else go acc delegators
| None ->
this_vrf ()
else go acc delegators
in
go None delegators
end
module Optional_state_hash = struct
[%%versioned
module Stable = struct
module V1 = struct
type t = Mina_base.State_hash.Stable.V1.t option
[@@deriving sexp, compare, hash, to_yojson]
let to_latest = Fn.id
end
end]
end
module Epoch_data = struct
include Mina_base.Epoch_data
module Make (Lock_checkpoint : sig
type t [@@deriving sexp, compare, hash, to_yojson]
val typ : (Mina_base.State_hash.var, t) Typ.t
type graphql_type
val graphql_type : unit -> ('ctx, graphql_type) Graphql_async.Schema.typ
val resolve : t -> graphql_type
val to_input :
t -> Snark_params.Tick.Field.t Random_oracle.Input.Chunked.t
val null : t
end) =
struct
open Snark_params
module Value = struct
type t =
( Epoch_ledger.Value.t
, Epoch_seed.t
, Mina_base.State_hash.t
, Lock_checkpoint.t
, Length.t )
Poly.t
[@@deriving sexp, compare, hash, to_yojson]
end
let typ : (var, Value.t) Typ.t =
Typ.of_hlistable
[ Epoch_ledger.typ
; Epoch_seed.typ
; Mina_base.State_hash.typ
; Lock_checkpoint.typ
; Length.typ
]
~var_to_hlist:Poly.to_hlist ~var_of_hlist:Poly.of_hlist
~value_to_hlist:Poly.to_hlist ~value_of_hlist:Poly.of_hlist
let graphql_type name =
let open Graphql_async in
let open Schema in
obj name ~fields:(fun _ ->
[ field "ledger"
~typ:(non_null @@ Epoch_ledger.graphql_type ())
~args:Arg.[]
~resolve:(fun _ { Poly.ledger; _ } -> ledger)
; field "seed"
~typ:
(non_null @@ Mina_base_unix.Graphql_scalars.EpochSeed.typ ())
~args:Arg.[]
~resolve:(fun _ { Poly.seed; _ } -> seed)
; field "startCheckpoint"
~typ:
(non_null @@ Mina_base_unix.Graphql_scalars.StateHash.typ ())
~args:Arg.[]
~resolve:(fun _ { Poly.start_checkpoint; _ } ->
start_checkpoint )
; field "lockCheckpoint"
~typ:(Lock_checkpoint.graphql_type ())
~args:Arg.[]
~resolve:(fun _ { Poly.lock_checkpoint; _ } ->
Lock_checkpoint.resolve lock_checkpoint )
; field "epochLength"