Skip to content

Commit 51adc48

Browse files
authored
Merge pull request #17934 from MinaProtocol/cjjdespres/gradual-ledger-migration
Add more backing-agnostic methods to Root
2 parents 6f3f3ed + f2aaf27 commit 51adc48

File tree

3 files changed

+125
-5
lines changed

3 files changed

+125
-5
lines changed

src/lib/mina_ledger/root.ml

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,14 @@ struct
172172
| Converting_db db ->
173173
Converting_ledger.merkle_root db
174174

175-
let create ~logger ~config ~depth () =
175+
let create ~logger ~config ~depth ?(assert_synced = false) () =
176176
match config with
177177
| Config.Stable_db_config directory_name ->
178178
Stable_db (Stable_db.create ~directory_name ~depth ())
179179
| Converting_db_config config ->
180180
Converting_db
181181
(Converting_ledger.create ~config:(In_directories config) ~logger
182-
~depth () )
182+
~depth ~assert_synced () )
183183

184184
let create_temporary ~logger ~backing_type ~depth () =
185185
match backing_type with
@@ -215,6 +215,64 @@ struct
215215
; backing_2 = Config.backing_of_config config
216216
} )
217217

218+
let create_checkpoint_with_directory t ~directory_name =
219+
let backing_type =
220+
match t with
221+
| Stable_db _db ->
222+
Config.Stable_db
223+
| Converting_db _db ->
224+
Config.Converting_db
225+
in
226+
let config = Config.with_directory ~backing_type ~directory_name in
227+
create_checkpoint t ~config ()
228+
229+
(** Migrate the accounts in the ledger database [stable_db] and store them in
230+
[empty_hardfork_db]. The accounts are set in the target database in chunks
231+
so the daemon is still responsive during this operation; the daemon would
232+
otherwise stop everything as it hashed every account in the list. *)
233+
let chunked_migration ?(chunk_size = 1 lsl 6) stable_locations_and_accounts
234+
empty_migrated_db =
235+
let open Async.Deferred.Let_syntax in
236+
let ledger_depth = Migrated_db.depth empty_migrated_db in
237+
let addrs_and_accounts =
238+
List.mapi stable_locations_and_accounts ~f:(fun i acct ->
239+
( Migrated_db.Addr.of_int_exn ~ledger_depth i
240+
, Account.Hardfork.of_stable acct ) )
241+
in
242+
let rec set_chunks accounts =
243+
let%bind () = Async_unix.Scheduler.yield () in
244+
let chunk, accounts' = List.split_n accounts chunk_size in
245+
if List.is_empty chunk then return empty_migrated_db
246+
else (
247+
Migrated_db.set_batch_accounts empty_migrated_db chunk ;
248+
set_chunks accounts' )
249+
in
250+
set_chunks addrs_and_accounts
251+
252+
let make_converting t =
253+
let open Async.Deferred.Let_syntax in
254+
match t with
255+
| Converting_db _db ->
256+
return t
257+
| Stable_db db ->
258+
let directory_name =
259+
Stable_db.get_directory db
260+
|> Option.value_exn
261+
~message:"Invariant: database must be in a directory"
262+
in
263+
let converting_config =
264+
Converting_ledger.Config.with_primary ~directory_name
265+
in
266+
let migrated_db =
267+
Migrated_db.create
268+
~directory_name:converting_config.converting_directory
269+
~depth:(Stable_db.depth db) ()
270+
in
271+
let%map migrated_db =
272+
chunked_migration (Stable_db.to_list_sequential db) migrated_db
273+
in
274+
Converting_db (Converting_ledger.of_ledgers db migrated_db)
275+
218276
let as_unmasked t =
219277
match t with
220278
| Stable_db db ->
@@ -270,4 +328,12 @@ struct
270328
Stable_db.get_all_accounts_rooted_at_exn db
271329
| Converting_db db ->
272330
Converting_ledger.get_all_accounts_rooted_at_exn db
331+
332+
let unsafely_decompose_root t =
333+
match t with
334+
| Stable_db db ->
335+
(db, None)
336+
| Converting_db db ->
337+
( Converting_ledger.primary_ledger db
338+
, Some (Converting_ledger.converting_ledger db) )
273339
end

src/lib/mina_ledger/root.mli

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,13 @@ module Make
114114

115115
(** Create a root ledger backed by a single database in the given
116116
directory. *)
117-
val create : logger:Logger.t -> config:Config.t -> depth:int -> unit -> t
117+
val create :
118+
logger:Logger.t
119+
-> config:Config.t
120+
-> depth:int
121+
-> ?assert_synced:bool
122+
-> unit
123+
-> t
118124

119125
val create_temporary :
120126
logger:Logger.t
@@ -124,12 +130,22 @@ module Make
124130
-> t
125131

126132
(** Make a checkpoint of the root ledger and return a new root ledger backed
127-
by that checkpoint *)
133+
by that checkpoint. Throws an exception if the config does not match the
134+
backing type of the root. *)
128135
val create_checkpoint : t -> config:Config.t -> unit -> t
129136

130-
(** Make a checkpoint of the root ledger *)
137+
(** Make a checkpoint of the root ledger. Throws an exception if the config
138+
does not match the backing type of the root. *)
131139
val make_checkpoint : t -> config:Config.t -> unit
132140

141+
(** Make a checkpoint of the root ledger [t] of the same backing type using
142+
[directory_name] as a template for its location. Return a new root ledger
143+
backed by that checkpoint. *)
144+
val create_checkpoint_with_directory : t -> directory_name:string -> t
145+
146+
(** Convert a root backed by a [Config.Stable_db] to *)
147+
val make_converting : t -> t Async.Deferred.t
148+
133149
(** View the root ledger as an unmasked [Any_ledger] so it can be used by code
134150
that does not need to know how the root is implemented *)
135151
val as_unmasked : t -> Any_ledger.witness
@@ -160,4 +176,10 @@ module Make
160176
(** Get all of the accounts that are in a subtree of the underlying Merkle
161177
tree rooted at `address`. The accounts are ordered by their addresses. *)
162178
val get_all_accounts_rooted_at_exn : t -> addr -> (addr * account) list
179+
180+
(** Decompose a root into its components parts. Users of this method must be
181+
careful to ensure that either the underlying databases remain in sync, or
182+
that they are not later used to back a root ledger. Use this on temporary
183+
copies of root ledgers if possible. *)
184+
val unsafely_decompose_root : t -> Stable_db.t * Migrated_db.t option
163185
end

src/lib/mina_ledger/test/test_mina_ledger.ml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,36 @@ module Root_test = struct
180180
~logger ~depth:ledger_depth ~assert_synced:true ()
181181
|> close) ) ;
182182
Deferred.unit )
183+
184+
(** Test that a root created with a stable backing and then made converting
185+
has the expected database states *)
186+
let test_root_make_converting ~random () =
187+
Mina_stdlib_unix.File_system.with_temp_dir "root_gradual_migration"
188+
~f:(fun cwd ->
189+
let cfg =
190+
L.Root.Config.with_directory ~directory_name:(cwd ^/ "ledger")
191+
in
192+
let root =
193+
L.Root.create ~logger
194+
~config:(cfg ~backing_type:Stable_db)
195+
~depth:ledger_depth ()
196+
in
197+
let loc_with_accounts =
198+
populate_with_random_accounts ~num:num_accounts ~root ~random
199+
in
200+
let%bind root = L.Root.make_converting root in
201+
(* Make sure the stable accounts are all still present *)
202+
assert_accounts ~loc_with_accounts ~root ;
203+
L.Root.close root ;
204+
(* Re-open the root as converting to check that the databases are in
205+
sync *)
206+
let converting_root =
207+
L.Root.create ~logger
208+
~config:(cfg ~backing_type:Converting_db)
209+
~depth:ledger_depth ~assert_synced:true ()
210+
in
211+
L.Root.close converting_root ;
212+
Deferred.unit )
183213
end
184214

185215
let () =
@@ -195,5 +225,7 @@ let () =
195225
(Root_test.test_root_moving ~random)
196226
; Alcotest_async.test_case "make checkpointing a root" `Quick
197227
(Root_test.test_root_make_checkpointing ~random)
228+
; Alcotest_async.test_case "make converting a root" `Quick
229+
(Root_test.test_root_make_converting ~random)
198230
] )
199231
] )

0 commit comments

Comments
 (0)