Problem
The [withdrawal.config] section recently added to cartesi.toml requires the developer to supply five values, all of them mandatory:
[withdrawal.config]
guardian = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
log2_leaves_per_account = 0
log2_max_num_of_accounts = 7
accounts_drive_start_index = 9895604649984
withdrawal_output_builder = "0x0745787835A019cd4dae8EDB541Fbc0647793d63"
parseWithdrawalConfig (apps/cli/src/config.ts:593) rejects the section unless all five are present, and the result is forwarded verbatim to cartesi-rollups-cli via --withdrawal-config (apps/cli/src/exec/rollups.ts:531).
The semantics of these fields come from the emergency withdrawal specification. None of them is a decision a developer makes while writing an application, and in a local development environment — the CLI's primary target — every one of them has a single correct answer that the CLI is already in a position to know:
withdrawal_output_builder — the devnet already deploys TestUsdWithdrawalOutputBuilder, and the CLI already publishes its address in the address book (apps/cli/src/base.ts:121). The user is being asked to paste back a value the CLI just printed.
guardian — on a local devnet this is one of the well-known Anvil accounts.
log2_leaves_per_account — is 0 for applications that do not store more than one leaf per account, which is the common case.
log2_max_num_of_accounts — is a function of the accounts drive size, derived from the spec (unless the drive is also used for other data). The developer has to do that arithmetic by hand and redo it whenever the drive size changes.
accounts_drive_start_index — is not a choice at all. It is the address at which the emulator placed the accounts drive.
accounts_drive_start_index is the sharpest edge
The CLI deliberately does not pass start or length when it builds flash drive arguments (apps/cli/src/machine.ts:20, // don't specify start and length), so placement is decided entirely by the emulator: the first drive is placed past the RAM, and each remaining drive past the previous one (machine-emulator docs).
The value in the example above, 9895604649984, is 0x90000000000 — that is 0x80000000000 + 1 × 0x10000000000, i.e. "the second flash drive". To produce it, a developer has to know the emulator's base address, its stride, and the ordinal position of the accounts drive among the drives declared in cartesi.toml.
Worse, the value is silently coupled to unrelated edits. Declaring a new drive ahead of the accounts drive, or reordering the drives, shifts the address and invalidates the configuration. Nothing in build or run detects the mismatch — the machine still builds and the application still deploys, just with a withdrawal configuration pointing at the wrong drive.
Consequence
Enabling emergency withdrawal locally requires reading the specification, understanding the emulator's memory layout, and hand-computing values that the CLI could resolve itself. This is disproportionate for what should be a local development workflow, and the resulting configuration is fragile in a way that fails quietly.
What a good outcome looks like
Enabling emergency withdrawal on the local devnet should require little or no configuration, with explicit values still available as overrides for cases the defaults do not cover (notably fork/live chains, where guardian and withdrawal_output_builder have no sane default). The all-or-nothing parsing should become per-field optionality, and existing cartesi.toml files that spell out all five values must keep working unchanged.
For the values that depend on the machine layout, the resolved configuration is the source of truth, not the emulator's placement rule reimplemented in the CLI. cartesi build already stores a machine snapshot (--store=image, apps/cli/src/commands/build.ts:157), and that snapshot already carries a config.json with each flash drive's resolved start and length. Making that resolved configuration reachable — to the CLI itself and to the developer — is likely a prerequisite for anything else here, and is useful for debugging drive layout regardless of emergency withdrawal.
Open questions
- How is the accounts drive identified? By naming convention, by a flag on the drive configuration, or by an explicit reference under
[withdrawal]? Should enabling withdrawal create the drive when none is declared, and at what size?
- What exactly is the formula for
log2_max_num_of_accounts per the specification — leaf size, and whether it rounds down to fit the drive or must match it exactly?
- Where does
log2_leaves_per_account belong — in [withdrawal.config], on the accounts drive configuration, or both?
- What is the opt-in signal for enabling emergency withdrawal at all, once the section can be empty?
- Should
build/run reject (or warn about) an explicitly configured accounts_drive_start_index that disagrees with the resolved machine layout? That is what would catch the "I added a drive and it silently broke" case described above.
Problem
The
[withdrawal.config]section recently added tocartesi.tomlrequires the developer to supply five values, all of them mandatory:parseWithdrawalConfig(apps/cli/src/config.ts:593) rejects the section unless all five are present, and the result is forwarded verbatim tocartesi-rollups-clivia--withdrawal-config(apps/cli/src/exec/rollups.ts:531).The semantics of these fields come from the emergency withdrawal specification. None of them is a decision a developer makes while writing an application, and in a local development environment — the CLI's primary target — every one of them has a single correct answer that the CLI is already in a position to know:
withdrawal_output_builder— the devnet already deploysTestUsdWithdrawalOutputBuilder, and the CLI already publishes its address in the address book (apps/cli/src/base.ts:121). The user is being asked to paste back a value the CLI just printed.guardian— on a local devnet this is one of the well-known Anvil accounts.log2_leaves_per_account— is0for applications that do not store more than one leaf per account, which is the common case.log2_max_num_of_accounts— is a function of the accounts drive size, derived from the spec (unless the drive is also used for other data). The developer has to do that arithmetic by hand and redo it whenever the drive size changes.accounts_drive_start_index— is not a choice at all. It is the address at which the emulator placed the accounts drive.accounts_drive_start_indexis the sharpest edgeThe CLI deliberately does not pass
startorlengthwhen it builds flash drive arguments (apps/cli/src/machine.ts:20,// don't specify start and length), so placement is decided entirely by the emulator: the first drive is placed past the RAM, and each remaining drive past the previous one (machine-emulator docs).The value in the example above,
9895604649984, is0x90000000000— that is0x80000000000 + 1 × 0x10000000000, i.e. "the second flash drive". To produce it, a developer has to know the emulator's base address, its stride, and the ordinal position of the accounts drive among the drives declared incartesi.toml.Worse, the value is silently coupled to unrelated edits. Declaring a new drive ahead of the accounts drive, or reordering the drives, shifts the address and invalidates the configuration. Nothing in
buildorrundetects the mismatch — the machine still builds and the application still deploys, just with a withdrawal configuration pointing at the wrong drive.Consequence
Enabling emergency withdrawal locally requires reading the specification, understanding the emulator's memory layout, and hand-computing values that the CLI could resolve itself. This is disproportionate for what should be a local development workflow, and the resulting configuration is fragile in a way that fails quietly.
What a good outcome looks like
Enabling emergency withdrawal on the local devnet should require little or no configuration, with explicit values still available as overrides for cases the defaults do not cover (notably fork/live chains, where
guardianandwithdrawal_output_builderhave no sane default). The all-or-nothing parsing should become per-field optionality, and existingcartesi.tomlfiles that spell out all five values must keep working unchanged.For the values that depend on the machine layout, the resolved configuration is the source of truth, not the emulator's placement rule reimplemented in the CLI.
cartesi buildalready stores a machine snapshot (--store=image,apps/cli/src/commands/build.ts:157), and that snapshot already carries aconfig.jsonwith each flash drive's resolved start and length. Making that resolved configuration reachable — to the CLI itself and to the developer — is likely a prerequisite for anything else here, and is useful for debugging drive layout regardless of emergency withdrawal.Open questions
[withdrawal]? Should enabling withdrawal create the drive when none is declared, and at what size?log2_max_num_of_accountsper the specification — leaf size, and whether it rounds down to fit the drive or must match it exactly?log2_leaves_per_accountbelong — in[withdrawal.config], on the accounts drive configuration, or both?build/runreject (or warn about) an explicitly configuredaccounts_drive_start_indexthat disagrees with the resolved machine layout? That is what would catch the "I added a drive and it silently broke" case described above.