Skip to content

Commit aa625e2

Browse files
committed
Add mina advanced fix-persistent-frontier
1 parent 33d4f58 commit aa625e2

File tree

5 files changed

+390
-1
lines changed

5 files changed

+390
-1
lines changed

src/app/cli/src/init/client.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2558,6 +2558,7 @@ let advanced ~itn_features =
25582558
; ("thread-graph", thread_graph)
25592559
; ("print-signature-kind", signature_kind)
25602560
; ("generate-hardfork-config", generate_hardfork_config)
2561+
; ("fix-persistent-frontier", Fix_persistent_frontier.command)
25612562
; ( "test"
25622563
, Command.group ~summary:"Testing-only commands"
25632564
[ ("create-genesis", test_genesis_creation) ] )
Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
open Core
2+
open Async
3+
open Mina_base
4+
open Frontier_base
5+
6+
(* Build path from frontier root to persistent root by walking backwards *)
7+
let rec build_path_to_root ~(frontier : Transition_frontier.t) ~current_hash
8+
~target_hash acc =
9+
if State_hash.equal current_hash target_hash then
10+
(* Reached the target (frontier root), return accumulated path *)
11+
Ok acc
12+
else
13+
match Transition_frontier.find frontier current_hash with
14+
| None ->
15+
Error
16+
(sprintf "Block %s not found in frontier"
17+
(State_hash.to_base58_check current_hash) )
18+
| Some breadcrumb ->
19+
let parent_hash = Breadcrumb.parent_hash breadcrumb in
20+
build_path_to_root ~frontier ~current_hash:parent_hash ~target_hash
21+
(breadcrumb :: acc)
22+
23+
let check_directories_exist ~logger ~persistent_root_location
24+
~persistent_frontier_location =
25+
let%bind root_exists =
26+
Sys.file_exists persistent_root_location
27+
>>| function `Yes -> true | `No | `Unknown -> false
28+
in
29+
let%bind frontier_exists =
30+
Sys.file_exists persistent_frontier_location
31+
>>| function `Yes -> true | `No | `Unknown -> false
32+
in
33+
if not root_exists then (
34+
[%log' error logger] "Persistent root directory not found at $location"
35+
~metadata:[ ("location", `String persistent_root_location) ] ;
36+
Deferred.return (Error "Persistent root not found - nothing to fix against")
37+
)
38+
else if not frontier_exists then (
39+
[%log' info logger]
40+
"Persistent frontier directory not found - nothing to fix" ;
41+
Deferred.return (Ok `No_frontier) )
42+
else Deferred.return (Ok `Both_exist)
43+
44+
(* Apply a sequence of root transition diffs to the persistent database *)
45+
let apply_root_transitions ~logger ~db diffs =
46+
try
47+
(* Get initial root hash *)
48+
let initial_root_hash =
49+
Transition_frontier.Persistent_frontier.Database.get_root_hash db
50+
|> Result.map_error ~f:(fun err ->
51+
Exn.create_s
52+
(Sexp.of_string
53+
( "Failed to get root hash: "
54+
^ Transition_frontier.Persistent_frontier.Database.Error
55+
.message err ) ) )
56+
|> Result.ok_exn
57+
in
58+
Transition_frontier.Persistent_frontier.Database.with_batch db
59+
~f:(fun batch ->
60+
( List.fold diffs ~init:initial_root_hash ~f:(fun old_root_hash diff ->
61+
match diff with
62+
| Diff.Lite.E.E
63+
(Diff.Root_transitioned
64+
{ new_root; garbage = Lite garbage; _ } ) ->
65+
let parent_hash =
66+
Root_data.Limited.Stable.Latest.transition new_root
67+
|> Mina_block.Validated.Stable.Latest.header
68+
|> Mina_block.Header.protocol_state
69+
|> Mina_state.Protocol_state.previous_state_hash
70+
in
71+
assert (State_hash.equal parent_hash old_root_hash) ;
72+
Transition_frontier.Persistent_frontier.Database.move_root
73+
~old_root_hash ~new_root ~garbage batch ;
74+
(* Return new root hash for next iteration *)
75+
(Root_data.Limited.Stable.Latest.hashes new_root).state_hash
76+
| _ ->
77+
failwith "Expected Root_transitioned diff" )
78+
: State_hash.t )
79+
|> ignore ) ;
80+
[%log' info logger] "Successfully applied $count diffs"
81+
~metadata:[ ("count", `Int (List.length diffs)) ] ;
82+
Ok ()
83+
with exn ->
84+
[%log' error logger] "Failed to apply root transitions: $error"
85+
~metadata:[ ("error", `String (Exn.to_string exn)) ] ;
86+
Error ("Failed to apply root transitions: " ^ Exn.to_string exn)
87+
88+
let fix_persistent_frontier_root_do ~logger ~config_directory
89+
~chain_state_locations ~max_frontier_depth runtime_config =
90+
let signature_kind = Mina_signature_kind.t_DEPRECATED in
91+
(* Get compile-time constants *)
92+
let genesis_constants = Genesis_constants.Compiled.genesis_constants in
93+
let constraint_constants = Genesis_constants.Compiled.constraint_constants in
94+
let proof_level = Genesis_constants.Compiled.proof_level in
95+
let%bind.Deferred.Result precomputed_values, _runtime_config_opt =
96+
Genesis_ledger_helper.init_from_config_file ~genesis_constants
97+
~constraint_constants ~logger ~proof_level ~cli_proof_level:None
98+
~genesis_dir:chain_state_locations.Chain_state_locations.genesis
99+
~genesis_backing_type:Stable_db runtime_config
100+
>>| Result.map_error ~f:Error.to_string_mach
101+
in
102+
(* Initialize Parallel as master before creating verifier *)
103+
Parallel.init_master () ;
104+
(* Create verifier - simplified without blockchain keys for now *)
105+
let%bind ( `Blockchain blockchain_verification_key
106+
, `Transaction transaction_verification_key ) =
107+
Verifier.get_verification_keys_eagerly ~constraint_constants ~proof_level
108+
~signature_kind
109+
in
110+
let%bind verifier =
111+
Verifier.create ~logger ~commit_id:"" ~blockchain_verification_key
112+
~transaction_verification_key ~signature_kind
113+
~proof_level:precomputed_values.proof_level
114+
~pids:(Child_processes.Termination.create_pid_table ())
115+
~conf_dir:(Some config_directory) ()
116+
in
117+
let tmp_root_location = chain_state_locations.root ^ "-tmp" in
118+
let%bind () =
119+
Mina_stdlib_unix.File_system.copy_dir chain_state_locations.root
120+
tmp_root_location
121+
>>| Result.ok_exn
122+
in
123+
(* Set up persistent root and frontier *)
124+
let persistent_root =
125+
Persistent_root.create ~logger ~backing_type:Stable_db
126+
~directory:tmp_root_location
127+
~ledger_depth:precomputed_values.constraint_constants.ledger_depth
128+
in
129+
let persistent_frontier =
130+
Persistent_frontier.create ~logger ~verifier
131+
~directory:chain_state_locations.frontier
132+
~time_controller:(Block_time.Controller.basic ~logger)
133+
~signature_kind
134+
in
135+
let proof_cache_db = Proof_cache_tag.create_identity_db () in
136+
let%bind.Deferred.Result persistent_frontier_root_hash =
137+
Persistent_frontier.with_instance_exn persistent_frontier
138+
~f:Persistent_frontier.Instance.get_root_hash
139+
in
140+
let persistent_root_id =
141+
Persistent_root.load_root_identifier persistent_root
142+
|> Option.value_exn ~message:"couldn't load persistent root hash"
143+
in
144+
let persistent_root_hash = persistent_root_id.state_hash in
145+
Persistent_root.set_root_state_hash persistent_root
146+
persistent_frontier_root_hash ;
147+
(* Set up context module for frontier loading *)
148+
let module Context = struct
149+
let logger = logger
150+
151+
let precomputed_values = precomputed_values
152+
153+
let constraint_constants = precomputed_values.constraint_constants
154+
155+
let consensus_constants = precomputed_values.consensus_constants
156+
157+
let proof_cache_db = proof_cache_db
158+
159+
let signature_kind = signature_kind
160+
end in
161+
let consensus_local_state =
162+
Consensus.Data.Local_state.create
163+
~context:(module Context)
164+
~genesis_ledger:precomputed_values.genesis_ledger
165+
~genesis_epoch_data:precomputed_values.genesis_epoch_data
166+
~epoch_ledger_location:chain_state_locations.epoch_ledger
167+
~genesis_state_hash:
168+
(State_hash.With_state_hashes.state_hash
169+
precomputed_values.protocol_state_with_hashes )
170+
~epoch_ledger_backing_type:Stable_db
171+
Signature_lib.Public_key.Compressed.Set.empty
172+
in
173+
(* TODO loading of frontier is redundant unless fixing is needed *)
174+
(* Load transition frontier using the standard API *)
175+
let%bind frontier =
176+
match%map
177+
Transition_frontier.load
178+
~context:(module Context)
179+
~retry_with_fresh_db:false ~max_frontier_depth ~verifier
180+
~consensus_local_state ~persistent_root ~persistent_frontier
181+
~catchup_mode:`Super ~set_best_tip:false ()
182+
with
183+
| Error err ->
184+
let err_str =
185+
match err with
186+
| `Failure s ->
187+
sprintf "Failure: %s" s
188+
| `Bootstrap_required ->
189+
"Bootstrap required"
190+
| `Persistent_frontier_malformed ->
191+
"Persistent frontier malformed"
192+
| `Snarked_ledger_mismatch ->
193+
"Snarked ledger mismatch"
194+
in
195+
[%log' error logger] "Failed to load transition frontier: $error"
196+
~metadata:[ ("error", `String err_str) ] ;
197+
failwith (sprintf "Failed to load frontier: %s" err_str)
198+
| Ok f ->
199+
f
200+
in
201+
let frontier_root_hash =
202+
Transition_frontier.root frontier |> Breadcrumb.state_hash
203+
in
204+
assert (State_hash.equal frontier_root_hash persistent_frontier_root_hash) ;
205+
let with_persistent_frontier_instance f =
206+
Persistent_frontier.with_instance_exn persistent_frontier ~f
207+
in
208+
let clean_frontier () =
209+
let%bind () = Transition_frontier.close ~loc:__LOC__ frontier in
210+
Mina_stdlib_unix.File_system.remove_dir tmp_root_location
211+
in
212+
(* Check if persistent root is in the frontier *)
213+
match
214+
( State_hash.equal frontier_root_hash persistent_root_hash
215+
, Transition_frontier.find frontier persistent_root_hash )
216+
with
217+
| true, _ ->
218+
[%log info]
219+
"Frontier root already matches persistent root. Nothing to do." ;
220+
let%map () = clean_frontier () in
221+
Ok ()
222+
| _, None ->
223+
[%log error]
224+
"Persistent root $persistent_root not found in frontier. Bootstrap \
225+
required."
226+
~metadata:
227+
[ ("persistent_root", State_hash.to_yojson persistent_root_hash)
228+
; ("frontier_root", State_hash.to_yojson frontier_root_hash)
229+
] ;
230+
let%map () = clean_frontier () in
231+
Error "Persistent root not found in frontier. Bootstrap required."
232+
| _, Some _persistent_root_breadcrumb ->
233+
(* Build path from persistent root back to frontier root *)
234+
let%bind.Deferred.Result path =
235+
build_path_to_root ~frontier ~current_hash:persistent_root_hash
236+
~target_hash:frontier_root_hash []
237+
|> Deferred.return
238+
in
239+
[%log info]
240+
"Built path from persistent root to frontier root: $length blocks"
241+
~metadata:[ ("length", `Int (List.length path)) ] ;
242+
assert (
243+
State_hash.equal
244+
(List.hd_exn path |> Breadcrumb.parent_hash)
245+
frontier_root_hash ) ;
246+
(* Generate root transition diffs for each step *)
247+
let _, diffs =
248+
let successors = Transition_frontier.successors frontier in
249+
let init =
250+
( Transition_frontier.root frontier
251+
, Transition_frontier.protocol_states_for_root_scan_state frontier )
252+
in
253+
List.fold_map path ~init
254+
~f:(fun (parent, protocol_states_for_root_scan_state) breadcrumb ->
255+
let root_transition =
256+
Transition_frontier.Util.calculate_root_transition_diff
257+
~protocol_states_for_root_scan_state ~parent ~successors
258+
breadcrumb
259+
in
260+
let res =
261+
Diff.Full.E.to_lite
262+
(Diff.Full.E.E (Root_transitioned root_transition))
263+
in
264+
( ( breadcrumb
265+
, Transition_frontier.Util.to_protocol_states_map_exn
266+
@@ Root_data.Limited.Stable.Latest.protocol_states
267+
@@ root_transition.new_root )
268+
, res ) )
269+
in
270+
[%log info] "Generated $count transition diffs"
271+
~metadata:[ ("count", `Int (List.length diffs)) ] ;
272+
let%bind () = clean_frontier () in
273+
(* Apply the diffs to persistent frontier database *)
274+
let%map.Deferred.Result () =
275+
with_persistent_frontier_instance (fun instance ->
276+
apply_root_transitions ~logger ~db:instance.db diffs )
277+
in
278+
[%log info] "Successfully moved frontier root to match persistent root"
279+
280+
let fix_persistent_frontier_root ~config_directory ~config_file
281+
~max_frontier_depth =
282+
Logger.Consumer_registry.register ~commit_id:"" ~id:Logger.Logger_id.mina
283+
~processor:Internal_tracing.For_logger.processor
284+
~transport:
285+
(Internal_tracing.For_logger.json_lines_rotate_transport
286+
~directory:(config_directory ^ "/internal-tracing")
287+
() )
288+
() ;
289+
let logger = Logger.create ~id:Logger.Logger_id.mina () in
290+
let log_processor =
291+
Logger.Processor.pretty ~log_level:Logger.Level.Trace
292+
~config:
293+
{ Interpolator_lib.Interpolator.mode = After
294+
; max_interpolation_length = 50
295+
; pretty_print = true
296+
}
297+
in
298+
Logger.Consumer_registry.register ~commit_id:Mina_version.commit_id
299+
~id:Logger.Logger_id.mina ~processor:log_processor
300+
~transport:(Logger.Transport.stdout ())
301+
() ;
302+
let%bind () = Internal_tracing.toggle ~commit_id:"" ~logger `Enabled in
303+
(* Load the persistent root identifier *)
304+
(* Load and initialize precomputed values from config *)
305+
let%bind.Deferred.Result runtime_config_json =
306+
Genesis_ledger_helper.load_config_json config_file
307+
>>| Result.map_error ~f:Error.to_string_mach
308+
in
309+
let%bind.Deferred.Result runtime_config =
310+
Deferred.return @@ Runtime_config.of_yojson runtime_config_json
311+
in
312+
let chain_state_locations =
313+
Chain_state_locations.of_config ~conf_dir:config_directory runtime_config
314+
in
315+
(* Check if directories exist *)
316+
match%bind.Deferred.Result
317+
check_directories_exist ~logger
318+
~persistent_root_location:chain_state_locations.root
319+
~persistent_frontier_location:chain_state_locations.frontier
320+
with
321+
| `No_frontier ->
322+
Deferred.Result.return ()
323+
| `Both_exist ->
324+
fix_persistent_frontier_root_do ~logger ~config_directory
325+
~chain_state_locations ~max_frontier_depth runtime_config
326+
327+
let command =
328+
Command.async
329+
~summary:
330+
"Fix persistent frontier root hash mismatch with persistent root by \
331+
applying proper root transitions"
332+
(let open Command.Let_syntax in
333+
let%map_open config_directory = Cli_lib.Flag.conf_dir
334+
and config_file =
335+
flag "--config-file" ~doc:"PATH path to a configuration file"
336+
(required string)
337+
and max_frontier_depth =
338+
flag "--max-frontier-depth"
339+
~doc:"INT maximum frontier depth (default: 10)" (optional int)
340+
in
341+
Cli_lib.Exceptions.handle_nicely
342+
@@ fun () ->
343+
let open Deferred.Let_syntax in
344+
let%bind conf_dir =
345+
match config_directory with
346+
| Some dir ->
347+
Deferred.return dir
348+
| None ->
349+
let%map home = Sys.home_directory () in
350+
home ^/ Cli_lib.Default.conf_dir_name
351+
in
352+
match%bind
353+
fix_persistent_frontier_root ~config_directory:conf_dir ~config_file
354+
~max_frontier_depth:(Option.value max_frontier_depth ~default:10)
355+
with
356+
| Ok () ->
357+
printf "Persistent frontier root fix completed successfully.\n" ;
358+
Deferred.unit
359+
| Error msg ->
360+
eprintf "Failed to fix persistent frontier: %s\n" msg ;
361+
exit 1)

src/lib/transition_frontier/frontier_base/dune

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
kimchi_pasta
5858
kimchi_pasta.basic
5959
mina_wire_types
60-
internal_tracing)
60+
internal_tracing
61+
proof_cache_tag)
6162
(instrumentation
6263
(backend bisect_ppx))
6364
(preprocess

0 commit comments

Comments
 (0)