Skip to content

Commit 9845659

Browse files
authored
Merge pull request #17819 from MinaProtocol/cjjdespres/make-sync-check-more-defensive
Make converting ledger sync check more defensive
2 parents d45f4a2 + 4ea1acc commit 9845659

File tree

8 files changed

+107
-12
lines changed

8 files changed

+107
-12
lines changed

changes/17819.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash in ledger sync check that could occur when trying to load ledger
2+
databases that were only partially synced to a network ledger

src/lib/merkle_ledger/any_ledger.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ module Make_base (Inputs : Intf.Inputs.Intf) :
8181

8282
let set_at_index_exn (T ((module Base), t)) = Base.set_at_index_exn t
8383

84+
let get_at_index (T ((module Base), t)) = Base.get_at_index t
85+
8486
let get_at_index_exn (T ((module Base), t)) = Base.get_at_index_exn t
8587

8688
let set_batch ?hash_cache (T ((module Base), t)) =
@@ -118,6 +120,8 @@ module Make_base (Inputs : Intf.Inputs.Intf) :
118120

119121
let token_owners (T ((module Base), t)) = Base.token_owners t
120122

123+
let iteri_untrusted (T ((module Base), t)) = Base.iteri_untrusted t
124+
121125
let iteri (T ((module Base), t)) = Base.iteri t
122126

123127
(* ignored_keys must be Base.Keys.Set.t, but that isn't necessarily the same as Keys.Set.t for the

src/lib/merkle_ledger/converting_merkle_tree.ml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ end)
109109

110110
let to_list_sequential t = Primary_ledger.to_list_sequential t.primary_ledger
111111

112+
let iteri_untrusted t ~f = Primary_ledger.iteri_untrusted t.primary_ledger ~f
113+
112114
let iteri t ~f = Primary_ledger.iteri t.primary_ledger ~f
113115

114116
let foldi t ~init ~f = Primary_ledger.foldi t.primary_ledger ~init ~f
@@ -180,6 +182,8 @@ end)
180182
~f:(fun (loc, account) -> (loc, convert account))
181183
located_accounts )
182184

185+
let get_at_index t idx = Primary_ledger.get_at_index t.primary_ledger idx
186+
183187
let get_at_index_exn t idx =
184188
Primary_ledger.get_at_index_exn t.primary_ledger idx
185189

@@ -264,12 +268,12 @@ struct
264268
Primary_db.num_accounts db1 = Converting_db.num_accounts db2
265269
&&
266270
let is_synced = ref true in
267-
Primary_db.iteri db1 ~f:(fun idx stable_account ->
268-
let expected_unstable_account = convert stable_account in
269-
let actual_unstable_account = Converting_db.get_at_index_exn db2 idx in
271+
Primary_db.iteri_untrusted db1 ~f:(fun idx stable_account ->
272+
let expected_unstable_account = Option.map ~f:convert stable_account in
273+
let actual_unstable_account = Converting_db.get_at_index db2 idx in
270274
if
271275
not
272-
(Inputs.converted_equal expected_unstable_account
276+
(Option.equal Inputs.converted_equal expected_unstable_account
273277
actual_unstable_account )
274278
then is_synced := false ) ;
275279
!is_synced

src/lib/merkle_ledger/database.ml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,13 @@ module Make (Inputs : Intf.Inputs.DATABASE) = struct
278278
Option.map (last_location mdb) ~f:Location.to_path_exn
279279
end
280280

281-
let get_at_index_exn mdb index =
281+
let get_at_index mdb index =
282282
let addr = Addr.of_int_exn ~ledger_depth:mdb.depth index in
283-
get mdb (Location.Account addr) |> Option.value_exn
283+
get mdb (Location.Account addr)
284+
285+
let get_at_index_exn mdb index =
286+
get_at_index mdb index
287+
|> Option.value_exn ~message:"Expected account at index" ~here:[%here]
284288

285289
let all_accounts (t : t) =
286290
match Account_location.last_location_address t with
@@ -572,13 +576,19 @@ module Make (Inputs : Intf.Inputs.DATABASE) = struct
572576
| Ok location ->
573577
Ok (`Existed, location)
574578

575-
let iteri t ~f =
579+
let iteri_untrusted t ~f =
576580
match Account_location.last_location_address t with
577581
| None ->
578582
()
579583
| Some last_addr ->
580584
Sequence.range ~stop:`inclusive 0 (Addr.to_int last_addr)
581-
|> Sequence.iter ~f:(fun i -> f i (get_at_index_exn t i))
585+
|> Sequence.iter ~f:(fun i -> f i (get_at_index t i))
586+
587+
let iteri t ~f =
588+
iteri_untrusted t ~f:(fun index account_opt ->
589+
f index
590+
(Option.value_exn ~message:"Expected account at index" ~here:[%here]
591+
account_opt ) )
582592

583593
(* TODO : if key-value store supports iteration mechanism, like RocksDB,
584594
maybe use that here, instead of loading all accounts into memory See Issue

src/lib/merkle_ledger/intf.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,9 @@ module Ledger = struct
314314
(** list of accounts via slower sequential mechanism *)
315315
val to_list_sequential : t -> account list
316316

317+
(** iterate over all indexes and accounts, if the ledger is not known to be sound *)
318+
val iteri_untrusted : t -> f:(index -> account option -> unit) -> unit
319+
317320
(** iterate over all indexes and accounts *)
318321
val iteri : t -> f:(index -> account -> unit) -> unit
319322

@@ -385,6 +388,8 @@ module Ledger = struct
385388
val set_batch :
386389
?hash_cache:hash Addr.Map.t -> t -> (Location.t * account) list -> unit
387390

391+
val get_at_index : t -> int -> account option
392+
388393
val get_at_index_exn : t -> int -> account
389394

390395
val set_at_index_exn : t -> int -> account -> unit
@@ -542,6 +547,8 @@ module Ledger = struct
542547

543548
module Config : Config
544549

550+
val dbs_synced : primary_ledger -> converting_ledger -> bool
551+
545552
(** Create a new converting merkle tree with the given configuration. If
546553
[In_directories] is given, existing databases will be opened and used to
547554
back the converting merkle tree. If the converting database does not exist

src/lib/merkle_ledger/null_ledger.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ end = struct
9595
let set_at_index_exn _t =
9696
failwith "set_at_index_exn: null ledgers cannot be mutated"
9797

98+
let get_at_index _t _index = None
99+
98100
let get_at_index_exn _t = failwith "get_at_index_exn: null ledgers are empty"
99101

100102
let set_batch ?hash_cache:_ _t =
@@ -130,6 +132,8 @@ end = struct
130132

131133
let tokens _t _pk = Token_id.Set.empty
132134

135+
let iteri_untrusted _t ~f:_ = ()
136+
133137
let iteri _t ~f:_ = ()
134138

135139
let fold_until _t ~init ~f:_ ~finish = Async.Deferred.return @@ finish init

src/lib/merkle_ledger/test/test_converting.ml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,59 @@ struct
207207
[%test_eq: Migrated.Account.t] stored_migrated_account
208208
(Db_converting.convert primary_account) ) ) ) )
209209

210+
let () =
211+
add_test
212+
"sync detection fails without crashing after accounts are added at high \
213+
addresses" (fun () ->
214+
with_primary ~f:(fun primary ->
215+
let depth = Db.depth primary in
216+
let max_height = Int.min 5 depth - 1 in
217+
populate_primary_db primary max_height ;
218+
with_migrated ~f:(fun migrated ->
219+
let _converting =
220+
Db_converting.of_ledgers_with_migration primary migrated
221+
in
222+
let additional_account = Quickcheck.random_value Account.gen in
223+
let high_index = (1 lsl Int.min 5 depth) - 1 in
224+
let additional_account_addr =
225+
Db.Addr.of_int_exn ~ledger_depth:depth high_index
226+
in
227+
(* Using set_batch_accounts with a high address like this leaves
228+
the databases in an inconsistent state, because it updates
229+
the last added account in the databases but doesn't fill in
230+
the accounts at lower addresses. This state is similar to
231+
what you might get after an incomplete ledger sync. *)
232+
Db.set_batch_accounts primary
233+
[ (additional_account_addr, additional_account) ] ;
234+
Db_migrated.set_batch_accounts migrated
235+
[ ( additional_account_addr
236+
, Db_converting.convert additional_account )
237+
] ;
238+
assert (Db_converting.dbs_synced primary migrated) ) ) )
239+
240+
let () =
241+
add_test "sync detection fails after converting ledger account is mutated"
242+
(fun () ->
243+
with_primary ~f:(fun primary ->
244+
let depth = Db.depth primary in
245+
let max_height = Int.min 5 depth in
246+
populate_primary_db primary max_height ;
247+
let account_to_mutate = Db.get_at_index_exn primary 0 in
248+
let new_balance, _overflow_flag =
249+
Balance.add_amount_flagged account_to_mutate.balance
250+
Currency.Amount.one
251+
in
252+
let mutated_account =
253+
Db_converting.convert
254+
{ account_to_mutate with balance = new_balance }
255+
in
256+
with_migrated ~f:(fun migrated ->
257+
let _converting =
258+
Db_converting.of_ledgers_with_migration primary migrated
259+
in
260+
Db_migrated.set_at_index_exn migrated 0 mutated_account ;
261+
assert (not (Db_converting.dbs_synced primary migrated)) ) ) )
262+
210263
let () =
211264
add_test "create converting ledger, populate randomly, test iteration order"
212265
(fun () ->

src/lib/merkle_mask/masking_merkle_tree.ml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -879,10 +879,15 @@ module Make (Inputs : Inputs_intf.S) = struct
879879
let addr = Location.to_path_exn location in
880880
Addr.to_int addr
881881

882-
let get_at_index_exn t index =
882+
let get_at_index t index =
883883
assert_is_attached t ;
884884
let addr = Addr.of_int_exn ~ledger_depth:t.depth index in
885-
get t (Location.Account addr) |> Option.value_exn
885+
get t (Location.Account addr)
886+
887+
let get_at_index_exn t index =
888+
assert_is_attached t ;
889+
get_at_index t index
890+
|> Option.value_exn ~message:"Expected account at index" ~here:[%here]
886891

887892
let set_at_index_exn t index account =
888893
assert_is_attached t ;
@@ -906,11 +911,17 @@ module Make (Inputs : Inputs_intf.S) = struct
906911
let%map.Async.Deferred accts = to_list t in
907912
List.map accts ~f:Account.identifier |> Account_id.Set.of_list
908913

909-
let iteri t ~f =
914+
let iteri_untrusted t ~f =
910915
assert_is_attached t ;
911916
let num_accounts = num_accounts t in
912917
Sequence.range ~stop:`exclusive 0 num_accounts
913-
|> Sequence.iter ~f:(fun i -> f i (get_at_index_exn t i))
918+
|> Sequence.iter ~f:(fun i -> f i (get_at_index t i))
919+
920+
let iteri t ~f =
921+
iteri_untrusted t ~f:(fun index account_opt ->
922+
f index
923+
(Option.value_exn ~message:"Expected account at index" ~here:[%here]
924+
account_opt ) )
914925

915926
let foldi_with_ignored_accounts t ignored_accounts ~init ~f =
916927
assert_is_attached t ;

0 commit comments

Comments
 (0)