Skip to content

Commit 2ea4285

Browse files
authored
Merge pull request #17428 from MinaProtocol/lyh/converting-ledger-and-tests
Add converting ledger with test
2 parents 9ff8ec9 + e806e3d commit 2ea4285

File tree

8 files changed

+227
-11
lines changed

8 files changed

+227
-11
lines changed

src/lib/merkle_ledger/database.ml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
open Core_kernel
22

33
module Make (Inputs : Intf.Inputs.DATABASE) = struct
4-
(* The max depth of a merkle tree can never be greater than 253. *)
4+
(* The max depth of a merkle tree can never be greater than 253,
5+
due to the way we encode locations. *)
56
open Inputs
67

78
module Db_error = struct
@@ -43,7 +44,7 @@ module Make (Inputs : Intf.Inputs.DATABASE) = struct
4344

4445
let depth t = t.depth
4546

46-
let create ?directory_name ~depth () =
47+
let create ?directory_name ?(fresh = false) ~depth () =
4748
let open Core in
4849
(* for ^/ and Unix below *)
4950
assert (depth < 0xfe) ;
@@ -58,6 +59,7 @@ module Make (Inputs : Intf.Inputs.DATABASE) = struct
5859
| Some name ->
5960
name
6061
in
62+
if fresh then Mina_stdlib_unix.File_system.rmrf directory ;
6163
Unix.mkdir_p directory ;
6264
let kvdb = Kvdb.create directory in
6365
{ uuid

src/lib/merkle_ledger/dune

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
;; local libraries
2525
cache_dir
2626
mina_stdlib
27+
mina_stdlib_unix
2728
key_value_database
2829
merkle_address
2930
ppx_version.runtime

src/lib/merkle_ledger/intf.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ module Ledger = struct
473473
module type DATABASE = sig
474474
include S
475475

476-
val create : ?directory_name:string -> depth:int -> unit -> t
476+
val create : ?directory_name:string -> ?fresh:bool -> depth:int -> unit -> t
477477

478478
(** create_checkpoint would create the checkpoint and open a db connection to that checkpoint *)
479479
val create_checkpoint : t -> directory_name:string -> unit -> t

src/lib/merkle_ledger/location.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ module T = struct
3838
* and node locations, the bitstring represents the path in the tree where that node exists.
3939
* For all other locations (generic locations), the prefix is 0xff. Generic locations can contain
4040
* any bitstring.
41+
* Hence, we can have at most (2^(253 - 1)) accounts, where 253 is just 0xfd.
4142
*)
4243

4344
module Addr = Merkle_address

src/lib/merkle_ledger/merkle_path.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ module type S = sig
88
val elem_hash : elem -> hash
99

1010
type t = elem list [@@deriving sexp, equal]
11+
(* In the absense of hash clashing, a merkle path uniquely identifies a node
12+
in a merkle tree, and how to reach such path from the merkle root. *)
1113

1214
val implied_root : t -> hash -> hash
1315

src/lib/mina_base/account.ml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,9 @@ let deriver obj =
10371037
~zkapp:!.(option ~js_type:Or_undefined (Zkapp_account.deriver @@ o ()))
10381038
obj
10391039

1040+
(* An unstable account is needed when we're doing ledger migration. The main
1041+
idea is we provide a function converting from Stable account to this type,
1042+
and storing all writes to the original ledger to the new ledger. *)
10401043
module Unstable = struct
10411044
type t =
10421045
{ public_key : Public_key.Compressed.Stable.V1.t
@@ -1098,4 +1101,19 @@ module Unstable = struct
10981101
(Random_oracle.pack_input (to_input t))
10991102

11001103
let empty_digest = lazy (digest empty)
1104+
1105+
let of_stable (account : Stable.Latest.t) : t =
1106+
{ public_key = account.public_key
1107+
; token_id = account.token_id
1108+
; token_symbol = account.token_symbol
1109+
; balance = account.balance
1110+
; nonce = account.nonce
1111+
; receipt_chain_hash = account.receipt_chain_hash
1112+
; delegate = account.delegate
1113+
; voting_for = account.voting_for
1114+
; timing = account.timing
1115+
; permissions = account.permissions
1116+
; zkapp = account.zkapp
1117+
; unstable_field = account.nonce
1118+
}
11011119
end

src/lib/mina_ledger/dune

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
ppx_base
1414
ppx_deriving_yojson
1515
ppx_inline_test
16-
ppx_assert))
16+
ppx_assert
17+
ppx_mina))
1718
(libraries
1819
;; opam libraries
1920
rocks

src/lib/mina_ledger/ledger.ml

Lines changed: 198 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ module Ledger_inner = struct
3939
[@@deriving sexp, compare, hash, bin_io_unversioned]
4040
end
4141

42+
module type Intf = sig
43+
type t [@@deriving sexp, of_sexp, hash, equal, compare, yojson]
44+
45+
type account
46+
47+
include Binable.S with type t := t
48+
49+
include module type of Hashable.Make_binable (Arg)
50+
51+
val to_base58_check : t -> string
52+
53+
val merge : height:int -> t -> t -> t
54+
55+
val hash_account : account -> t
56+
57+
val empty_account : t
58+
end
59+
4260
[%%versioned
4361
module Stable = struct
4462
module V1 = struct
@@ -61,9 +79,40 @@ module Ledger_inner = struct
6179
Ledger_hash.of_digest (Lazy.force Account.empty_digest)
6280
end
6381
end]
82+
83+
module Unstable = struct
84+
type t = Ledger_hash.Stable.V1.t
85+
[@@deriving sexp, compare, hash, equal, yojson, bin_io_unversioned]
86+
87+
include Hashable.Make_binable (Arg)
88+
89+
let to_base58_check = Ledger_hash.to_base58_check
90+
91+
let merge = Ledger_hash.merge
92+
93+
let hash_account =
94+
Fn.compose Ledger_hash.of_digest Mina_base.Account.Unstable.digest
95+
96+
let empty_account =
97+
Ledger_hash.of_digest (Lazy.force Account.Unstable.empty_digest)
98+
end
6499
end
65100

66101
module Account = struct
102+
module type Intf = sig
103+
type t [@@deriving sexp, of_sexp, equal, compare]
104+
105+
include Binable.S with type t := t
106+
107+
val balance : t -> Currency.Balance.t
108+
109+
val empty : t
110+
111+
val identifier : t -> Account_id.t
112+
113+
val token : t -> Token_id.t
114+
end
115+
67116
[%%versioned
68117
module Stable = struct
69118
module V2 = struct
@@ -84,9 +133,18 @@ module Ledger_inner = struct
84133
let empty = Stable.Latest.empty
85134

86135
let initialize = Account.initialize
136+
137+
module Unstable = struct
138+
include Mina_base.Account.Unstable
139+
140+
let token = token_id
141+
end
87142
end
88143

89-
module Inputs = struct
144+
module Make_inputs
145+
(Account : Account.Intf)
146+
(Hash : Hash.Intf with type account := Account.t) =
147+
struct
90148
module Key = Public_key.Compressed
91149
module Token_id = Token_id
92150
module Account_id = Account_id
@@ -97,15 +155,15 @@ module Ledger_inner = struct
97155
let to_int = to_nanomina_int
98156
end
99157

100-
module Account = Account.Stable.Latest
101-
module Hash = Hash.Stable.Latest
158+
module Account = Account
159+
module Hash = Hash
102160
module Kvdb = Kvdb
103161
module Location = Location_at_depth
104162
module Location_binable = Location_binable
105163
module Storage_locations = Storage_locations
106164
end
107165

108-
module Db :
166+
module type Account_Db =
109167
Merkle_ledger.Intf.Ledger.DATABASE
110168
with module Location = Location_at_depth
111169
with module Addr = Location_at_depth.Addr
@@ -114,10 +172,16 @@ module Ledger_inner = struct
114172
and type key := Public_key.Compressed.t
115173
and type token_id := Token_id.t
116174
and type token_id_set := Token_id.Set.t
117-
and type account := Account.t
118175
and type account_id_set := Account_id.Set.t
119-
and type account_id := Account_id.t =
120-
Database.Make (Inputs)
176+
and type account_id := Account_id.t
177+
178+
module Inputs = Make_inputs (Account.Stable.Latest) (Hash.Stable.Latest)
179+
module Unstable_inputs = Make_inputs (Account.Unstable) (Hash.Unstable)
180+
181+
module Db : Account_Db with type account := Account.t = Database.Make (Inputs)
182+
183+
module Unstable_db : Account_Db with type account := Account.Unstable.t =
184+
Database.Make (Unstable_inputs)
121185

122186
module Null = Null_ledger.Make (Inputs)
123187

@@ -180,6 +244,18 @@ module Ledger_inner = struct
180244

181245
type maskable_ledger = t
182246

247+
module Converting_ledger =
248+
Converting_merkle_tree.Make
249+
(struct
250+
type converted_account = Account.Unstable.t
251+
252+
let convert = Account.Unstable.of_stable
253+
254+
include Inputs
255+
end)
256+
(Db)
257+
(Unstable_db)
258+
183259
let of_database db =
184260
let casted = Any_ledger.cast (module Db) db in
185261
let mask = Mask.create ~depth:(Db.depth db) () in
@@ -201,6 +277,60 @@ module Ledger_inner = struct
201277
let _base, mask = create_ephemeral_with_base ~depth () in
202278
mask
203279

280+
type converting_config =
281+
{ primary_directory_name : string option
282+
; converting_directory_name : string option
283+
}
284+
285+
let default_converting_directory_name primary_directory_name =
286+
primary_directory_name ^ "_converting"
287+
288+
let converting_directory_name ~cfg ~primary_directory =
289+
Option.first_some cfg.converting_directory_name
290+
@@ Option.map primary_directory ~f:default_converting_directory_name
291+
292+
let empty_converting_config : converting_config =
293+
{ primary_directory_name = None; converting_directory_name = None }
294+
295+
let create_converting ?(cfg = empty_converting_config) ~logger ~depth () =
296+
let db1 = Db.create ?directory_name:cfg.primary_directory_name ~depth () in
297+
let db2_directory_name =
298+
converting_directory_name ~cfg ~primary_directory:(Db.get_directory db1)
299+
in
300+
let db2 = Unstable_db.create ?directory_name:db2_directory_name ~depth () in
301+
let converting_ledger =
302+
if Unstable_db.num_accounts db2 = 0 then
303+
Converting_ledger.create_with_migration db1 db2
304+
else
305+
let is_synced = ref true in
306+
Db.iteri db1 ~f:(fun idx stable_account ->
307+
let expected_unstable_account =
308+
Account.Unstable.of_stable stable_account
309+
in
310+
let actual_unstable_account =
311+
Unstable_db.get_at_index_exn db2 idx
312+
in
313+
if
314+
not
315+
(Account.Unstable.equal expected_unstable_account
316+
actual_unstable_account )
317+
then is_synced := false ) ;
318+
if !is_synced then Converting_ledger.create db1 db2
319+
else (
320+
[%log warn]
321+
"Migrating DB desync, cleaning up unstable DB and remigrating..." ;
322+
Unstable_db.close db2 ;
323+
let db2 =
324+
Unstable_db.create ?directory_name:db2_directory_name ~fresh:true
325+
~depth ()
326+
in
327+
Converting_ledger.create_with_migration db1 db2 )
328+
in
329+
let casted = Any_ledger.cast (module Converting_ledger) converting_ledger in
330+
let mask = Mask.create ~depth () in
331+
( Maskable.register_mask casted mask
332+
, Converting_ledger.converting_ledger converting_ledger )
333+
204334
(** Create a new empty ledger.
205335
206336
Warning: This skips mask registration, for use in transaction logic,
@@ -272,6 +402,22 @@ module Ledger_inner = struct
272402

273403
let packed t = Any_ledger.cast (module Mask.Attached) t
274404

405+
let with_converting_ledger ~logger ~depth ~f =
406+
let cfg : converting_config =
407+
{ primary_directory_name = None; converting_directory_name = None }
408+
in
409+
let ledger = create_converting ~logger ~cfg ~depth () in
410+
try
411+
let result = f ledger in
412+
close (fst ledger) ;
413+
Ok result
414+
with exn ->
415+
close (fst ledger) ;
416+
Error (Error.of_exn exn)
417+
418+
let with_converting_ledger_exn ~logger ~depth ~f =
419+
with_converting_ledger ~logger ~depth ~f |> Or_error.ok_exn
420+
275421
let register_mask t mask =
276422
let accumulated = Mask.Attached.to_accumulated t in
277423
Maskable.register_mask ~accumulated (packed t) mask
@@ -741,3 +887,48 @@ let%test_unit "zkapp_command application on masked ledger" =
741887
assert (not (Ledger_hash.equal init_merkle_root (L.merkle_root l))) ;
742888
(*Parent updates reflected in child masks*)
743889
assert (Ledger_hash.equal (L.merkle_root l) (L.merkle_root m)) ) )
890+
891+
let%test_unit "user_command application on converting ledger" =
892+
let open Mina_transaction_logic.For_tests in
893+
let module L = Ledger_inner in
894+
let constraint_constants =
895+
{ Genesis_constants.For_unit_tests.Constraint_constants.t with
896+
account_creation_fee = Currency.Fee.of_nanomina_int_exn 1
897+
}
898+
in
899+
let logger = Logger.create () in
900+
Quickcheck.test ~trials:1 Test_spec.gen ~f:(fun { init_ledger; specs } ->
901+
let cmds = List.map specs ~f:command_send in
902+
L.with_converting_ledger_exn ~logger ~depth ~f:(fun (l, cl) ->
903+
Init_ledger.init (module L) init_ledger l ;
904+
let init_merkle_root = L.merkle_root l in
905+
let init_cl_merkle_root = Unstable_db.merkle_root cl in
906+
let () =
907+
iter_err cmds
908+
~f:
909+
(apply_user_command_unchecked ~constraint_constants
910+
~txn_global_slot l )
911+
|> Or_error.ok_exn
912+
in
913+
(* Assert that the ledger and the converting ledger are non-empty *)
914+
assert (not (Ledger_hash.equal init_merkle_root (L.merkle_root l))) ;
915+
L.commit l ;
916+
assert (
917+
not
918+
(Ledger_hash.equal init_cl_merkle_root
919+
(Unstable_db.merkle_root cl) ) ) ;
920+
(* Assert that the converted ledger has the same accounts as the first one, up to the new field*)
921+
L.iteri l ~f:(fun index account ->
922+
let account_converted = Unstable_db.get_at_index_exn cl index in
923+
assert (
924+
Mina_base.Account.Key.(
925+
equal account.public_key account_converted.public_key) ) ;
926+
assert (
927+
Mina_base.Account.Nonce.(
928+
equal account_converted.nonce account_converted.unstable_field) ) ) ;
929+
(* Assert that the converted ledger doesn't have anything "extra" compared to the primary ledger *)
930+
Unstable_db.iteri cl ~f:(fun index account_converted ->
931+
let account = L.get_at_index_exn l index in
932+
assert (
933+
Mina_base.Account.Key.(
934+
equal account.public_key account_converted.public_key) ) ) ) )

0 commit comments

Comments
 (0)