Skip to content

Conversation

cjjdespres
Copy link
Member

@cjjdespres cjjdespres commented Sep 17, 2025

The client RPC command mina advanced test generate-hard-fork-config --hardfork-config-dir <DIR> can be used to request that a running daemon generate and save a complete hard fork config in the specified directory <DIR>. This will currently contain:

A genesis/ directory containing the tar.gz archives of the hard fork genesis ledgers

  • A daemon.json containing the updated genesis constants for the hard fork
  • A genesis_legacy/ directory containing the tar.gz archives of the hard fork genesis ledgers without any account migration applied
  • A daemon.legacy.json containing the updated genesis constants based on the genesis_legacy/ ledgers
  • An empty activated file to indicate that the hard fork config was generated fully without errors

This method is not quite complete - it needs:

  1. A parameter to specify a specific slot or chain height to use for the hard fork block: it will only dump the last block produced before the configured slot_tx_end (or the best tip if that's not set) at the moment
  2. To calculate the genesis timestamp properly: it should actually be the timestamp of the chain end slot plus a configured offset
  3. To save the hard fork block itself as a precomputed block json file

@cjjdespres cjjdespres requested a review from a team as a code owner September 17, 2025 15:36
@cjjdespres
Copy link
Member Author

Related: #17752

@cjjdespres cjjdespres force-pushed the cjjdespres/full-fork-config branch from 96a8de1 to 6a7b23f Compare September 17, 2025 16:16
@glyh
Copy link
Member

glyh commented Sep 18, 2025

I suggest changing the folder structure to something like

$ tree genesis/
genesis/
├── berkeley
│   ├── activated
│   ├── daemon.json
│   └── whatever.tar.gz
└── mesa
    ├── activated
    ├── daemon.json
    └── whatever.tar.gz

3 directories, 6 files

So to be forward compatible. Also this allow us to mark completeness of fork config separately.

Maybe berkeley and mesa are not proper names, then use hard fork mode to name these folders?

@cjjdespres
Copy link
Member Author

I think the directory structure is something that @dkijania might have opinions on as well, since he thought having this command output the precomputed fork block and the legacy format config would be useful for testing.

I was also wondering if it would be useful to save the fork_config.json that the forkConfig endpoint would return (reusing that code and not reimplementing it, of course) so we could test that the runtime genesis executables give the same output as the automatic config dump.

@cjjdespres
Copy link
Member Author

I hadn't intended this to match the output of the automatic config dump exactly, but maybe that's wise. In that case, the structure for the migrated (mesa) format would look like

<DIR>/daemon.json
<DIR>/chain-state/mesa-{network}/activated
<DIR>/chain-state/mesa-{network}/genesis/{ledger}.tar.gz

if we wanted the output in <DIR> to be a valid config directory structure.

This is slightly complicated by the fact that the current compatible daemon wants the config directory to look like this:

<DIR>/daemon.json
<DIR>/genesis/{ledger}.tar.gz

Maybe it would be simpler if the command accepted a few parameters to control where the config directories and precomputed block json file should go, separately.

@cjjdespres
Copy link
Member Author

cjjdespres commented Sep 19, 2025

Or it could be this by default:

/daemon.json
/chain-state/mesa-{network}/activated
/chain-state/mesa-{network}/genesis/{ledger}.tar.gz
/chain-state/mesa-{network}/fork-validation/precomputed_fork_block.json
/chain-state/mesa-{network}/fork-validation/fork_config.json
/chain-state/mesa-{network}/fork-validation/legacy/daemon.json
/chain-state/mesa-{network}/fork-validation/legacy/genesis/{ledger}.tar.gz

and in the automatic config dump we'd omit the fork-validation/ directory. (Or still output it if a testing option was set).

Cli_lib.Exceptions.handle_nicely
Test_genesis_creation.time_genesis_creation )

let test_generate_hardfork_config =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am i understand correctly that we are putting this command under test since its mocking some parameters, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I don't actually think it needs to be under test. It could just be an advanced command. It's mocking some parameters only because I haven't added them yet. The genesis timestamp delta in particular could be an optional parameter to the command, and the daemon could use its configured timestamp delta if that parameter isn't set. But, the runtime config for that delta hasn't been added yet either.

Copy link
Member

@dkijania dkijania left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any unit, component tests for this feature?

Copy link
Member

@glyh glyh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does seems this PR has some rough edges to be completed before called functional

Mina_lib.Hardfork_config.dump_reference_config
~breadcrumb_spec:`Stop_slot ~directory_name mina
in
match result with
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

  • convert error to json with helpers in Error_json and attach them to metadata in logging.
  • this log level should be error, I think
  • I think it's also useful to log a info line on succeed

Daemon_rpcs.Generate_hardfork_config.rpc directory_name port
with
| Error e ->
eprintf "Failed to request hardfork config generation: %s\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

  • use logger
  • use Error_json to log error in json to logging metadata

(Error.to_string_hum e) ;
exit 17
| Ok () ->
printf "Hardfork configuration successfully requested in %s\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: use logger. and log dir name in metadata.

()
in
don't_wait_for (dump ()) ;
return (Ok ()) )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the fire-and-forget pattern here. Why can't this be a blocking operation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like we block for a good reason. Client side block indicates that server is working.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'll change this. I wrote it like this because initially I was migrating the ledgers all at once (before the ledger sync was fully merged) and that was causing the daemon to become unresponsive, which made the command fail due to the timeout.

In reality we should be making sure that the daemon remains responsive throughout the hard fork config generation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should hopefully be fixed in the latest update.

module Generate_hardfork_config = struct
type query = string [@@deriving bin_io_unversioned]

type response = unit Or_error.t [@@deriving bin_io_unversioned]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is not the final RPC version since more param to be added.

, Account.Hardfork.of_stable acct ) )
in
(* should this be a parameter? *)
let chunk_size = 1 lsl 6 in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, but for now could we lift it to top level so it's easier to spot? When we want them actually to be params we can always do a refactor.

Ledger.Hardfork_db.create ~directory_name ~depth ()
in
`Inputs
( accounts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to pass accounts along with length of accounts, I think. the later is just length of the previous.

let genesis_next_epoch_ledger_opt =
match source_ledgers.next_epoch_ledger with
| `Genesis l ->
get_genesis_migration_inputs ~name:"staking_ledger" l
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not something that's very important, but it seems here we can also clash if current epoch staking ledger = next epoch staking ledger

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I'm confused by the naming scheme of these ledgers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not something that's very important, but it seems here we can also clash if current epoch staking ledger = next epoch staking ledger

True. This is actually likely to be encountered in my proposed component test for this command (single node with simple genesis ledger) so I think I'll end up wanting to fix that issue in the runtime_genesis_ledger.exe and here.

Also I'm confused by the naming scheme of these ledgers.

The ~name? I just wanted a temporary name for the ledger databases that were being generated. I do see that I accidentally give ~name:"staking_ledger" to the genesis next epoch ledger. I'll fix that.

~target_dir ~ledger_name_prefix root =
let open Deferred.Or_error.Let_syntax in
let root_hash = get_root_hash root in
let ledger_dirname = get_directory root |> Option.value_exn in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: add an error message here.

| `Root l ->
Ledger.Root.create_stable_checkpoint l ~directory_name
| `Uncommitted _l ->
Ledger.Root.create_stable_checkpoint
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the necessity of breaking apply diffs and creating checkpoint into 2 steps. It make here fishy at a first glance.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hoping the new code is a little easier to follow. I still do it in stages, but hopefully a little more cleanly:

  1. Collect the diffs and copy the roots
  2. Migrate the stable database if the migrated version isn't available
  3. Apply the diffs to the copied roots

I'm doing it this way because I wanted the code to work with or without ledger sync enabled, so (2) might take a while. So, I'd like to gather all the inputs necessary for generating the hard fork config in one go, including copying the roots. Then I can do whatever slow, gradual things need to be done on the copies of everything.

I'm still slightly concerned about what might happen if, say, we try generating a hard fork config at the precise moment the daemon decides to commit to a new snarked root. We might have to move the code to gather the hard fork inputs into the transition controller, or at least add some kind of "fork input copying in progress" async flag(s) somewhere, so the controller knows not to mess with the underlying databases until we're done.

@glyh
Copy link
Member

glyh commented Sep 22, 2025

Do you mind left some instructions here using these configs to boot a new post-HF node instance? That'd be helpful

@glyh
Copy link
Member

glyh commented Sep 23, 2025

I hadn't intended this to match the output of the automatic config dump exactly, but maybe that's wise. In that case, the structure for the migrated (mesa) format would look like

<DIR>/daemon.json
<DIR>/chain-state/mesa-{network}/activated
<DIR>/chain-state/mesa-{network}/genesis/{ledger}.tar.gz

if we wanted the output in <DIR> to be a valid config directory structure.

This is slightly complicated by the fact that the current compatible daemon wants the config directory to look like this:

<DIR>/daemon.json
<DIR>/genesis/{ledger}.tar.gz

Maybe it would be simpler if the command accepted a few parameters to control where the config directories and precomputed block json file should go, separately.

I think you can use symlink? Although I personally think it's not critical. I'm fine keeping it as it currently is, just a nit idea of improvement.

@cjjdespres cjjdespres force-pushed the cjjdespres/full-fork-config branch from 6a7b23f to 371f953 Compare October 1, 2025 18:40
@cjjdespres
Copy link
Member Author

I've force-pushed to bring in the recent compatible fixes. I'll be getting to the review comments next.

@cjjdespres
Copy link
Member Author

Any unit, component tests for this feature?

That is something I want to add next. I was hoping for feedback on the general design/initial implementation before I kept going. (Thanks for that, by the way).

The basic component test I was thinking of was starting a single daemon with a known genesis ledger, using this command to generate a compatible hard fork config, then restarting the daemon with the output config.

It does seems this PR has some rough edges to be completed before called functional

I agree.

@cjjdespres cjjdespres force-pushed the cjjdespres/full-fork-config branch from 371f953 to 2c885cc Compare October 3, 2025 18:30
@cjjdespres
Copy link
Member Author

cjjdespres commented Oct 3, 2025

I have updated/rewritten the PR. It's hopefully a bit cleaner. I'll update the PR description on Monday, but just to have it down:

  • The command is now mina advanced generate-hardfork-config instead of mina advanced test
  • The fork config is generated according to how the daemon's current config directory is laid out. So it's currently this:
/activated
/daemon.json
/genesis/<ledger tar files>
/fork-validation/legacy/daemon.json
/fork-validation/legacy/genesis/<ledger tar files>

I think it will be easier for testing purposes to create the config directories according to what the compatible and develop daemons currently expect. When we eventually add #17762 and subsequently modify the develop daemon to expect its chain state to be stored in chain-state/mesa-{network}/, we can adjust how the fork config is generated with this code.

Still to do for this code (in subsequent PRs, unless you feel strongly about doing this now)

  • save the precomputed block json in /fork-validation
  • save the same fork_config.json that would have been generated by the graphql query and put it in /fork-validation
  • add an option to choose whether or not to save any of the /fork-validation items at all
  • handle the corner case where we try generating a hard fork config and the daemon is still at genesis

Incidentally, if --hardfork-mode auto is enabled then this config generation is practically instantaneous, except that it takes 40s to generate the tar files themselves. So we'll probably want an option to not generate the tar files and just save the unpacked genesis databases.

@cjjdespres
Copy link
Member Author

cjjdespres commented Oct 3, 2025

Do you mind left some instructions here using these configs to boot a new post-HF node instance? That'd be helpful

All you should need to do is something like:

  1. Boot up a compatible daemon incorporating this PR and sync it to a network
  2. Run mina advanced generate-hardfork-config --hardfork-config-dir /some/path
  3. Stop the daemon
  4. Start up a daemon that knows about the zkapp state size increase, and use --config-dir /some/path and not --config-file. You should probably start it in demo mode as well, unless you have other hard fork daemons lying around. Alternatively, start up a compatible daemon and give it --config-dir /some/path/fork-validation/legacy/.

I haven't tested the migrated hard fork config again since I wrote the previous version of this PR two weeks ago, but I have tested the /fork-validation/legacy/ config with a compatible daemon and it does still seem to boot up. Of course, we're going to want to start adding tests to expose all the horrifying bugs that surely still remain, make sure this agrees with the manual procedure, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants