Skip to content

docs(registry): commit arctic-1 alongside pacific-1 and atlantic-2 - #75

Closed
bdchatham wants to merge 1 commit into
brandon2/fee-cap-from-chainfrom
brandon2/registry-commits-arctic-1
Closed

docs(registry): commit arctic-1 alongside pacific-1 and atlantic-2#75
bdchatham wants to merge 1 commit into
brandon2/fee-cap-from-chainfrom
brandon2/registry-commits-arctic-1

Conversation

@bdchatham

Copy link
Copy Markdown
Contributor

Fifth on the stack. Doc-only; no code changes.

The policy change

arctic-1 was excluded from registry/chains/ because it can be re-genesised. It
can, but we control that and intend not to, and the exclusion costs more than the
risk.

The argument inverts once you look at what the alternative buys. Supplying the
file from a deployment lets anyone who can edit that source point a run at a
contract of their choosing, and the code-hash check cannot catch it — the check
proves the address holds the code the file recorded, not that the code is ours.
Committing removes that exposure and puts every address change through review.

Two corrections to what the section claimed

Both found by reading the code rather than trusting the prose, and both make the
trade look better than it did.

A re-genesis does not break a run. Resolve returns "deploy" when the chain
key does not match, with no error:

chain, ok := r.Chain(chainID, genesisHash)
if !ok {
    return Contract{}, true, nil   // deploy, not fail
}

So a run whose committed entries have gone stale deploys its own contracts, which
is what every run did before this directory held a file. Recovery is a pull
request here at whatever pace suits, not an outage. What does not degrade
quietly is a recorded address whose code has changed: Verify fails and the run
stops at startup rather than binding something else.

The section omitted the requirement that makes committing work. The registry
keys on genesisHash, so a profile that omits it matches nothing. prepare.go
already catches that and says why:

the registry describes chain N but this run names no genesisHash, so it matches
nothing and would deploy again on every restart

Any profile reading a committed entry has to carry it — including the three in
platform#1587, which currently do not. That is a prerequisite for the embedding
work, not an optional extra.

The values, for whoever does the embedding

Computed from sei-config, and cross-checked against a second independent
checkout — both agree:

arctic-1     8ef5b0c01c1cde65be22a0f501d1663c55b3a900b46ecb72a5ccd823040bf035
atlantic-2   d78c35f55c5263108d56f638b26255c714a36e0800f2dcf57136ecb9042e7772
pacific-1    d7ce8349b9e26573951c8c19afe018ee721cddcedb2d5a8df6f712ecbccc3428

Bare SHA-256 over the exact file bytes, which is what the controller records:

data, _ := os.ReadFile(genesisPath)
sum := sha256.Sum256(data)
genesisHash := hex.EncodeToString(sum[:])

I verified arctic-1's against the running chain via /genesis_chunked. The byte
hashes differ, and the reason is benign: the endpoint returns a re-marshalled
document with node defaults filled in (block.max_gas_wanted,
block.min_txs_in_block). Everything that identifies the chain is identical —
chain_id, genesis_time, app_hash, all 30 validators, and 516,993 bytes of
app_state. So the sei-config file is arctic-1's genesis.

One caveat worth recording: arctic-1 has no SeiNetwork, so the controller never
assembled or hashed it and there is no Status.GenesisHash to copy. Using the
sei-config file hash is reproducible and consistent with how the controller
computes one, but it is us establishing the value rather than reading it back.

🤖 Generated with Claude Code

arctic-1 was excluded because it can be re-genesised. It can, but we control
that and intend not to, and the exclusion cost more than the risk.

Supplying the file from a deployment instead lets anyone who can edit that source
point a run at a contract of their choosing, and the code-hash check cannot catch
it: the check proves the address holds the code the file recorded, not that the
code is ours. Committing removes that exposure and puts every address change
through review.

Two corrections to what the section claimed about a re-genesis.

It does not break a run. Resolve returns "deploy" when the chain key does not
match, with no error, so a run whose committed entries have gone stale deploys
its own contracts — which is what every run did before this directory held a
file. Recovery is a pull request here at whatever pace suits. What does not
degrade quietly is a recorded address whose code has changed: Verify fails and
the run stops at startup.

It also omitted the requirement that makes committing work at all. The registry
keys on genesisHash, so a profile that omits it matches nothing, and a run that
omits it on a chain the registry describes already fails at startup with that
explanation. Any profile reading a committed entry has to carry it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 29, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Documentation-only policy and behavior clarification; no runtime or deployment logic changes in this PR.

Overview
Updates registry/chains/README.md policy for which chains get committed chain JSON in-repo—no code changes.

Policy: arctic-1 moves from “always supply via --chain-file” to commit with pacific-1 and atlantic-2. The doc reframes the rule around whether a recorded address stays valid, not chain naming or devnet cadence. It explains why committing arctic-1 is intentional despite possible re-genesis: deployment-supplied files let anyone who can edit that source point runs at arbitrary contracts, which code-hash checks cannot fully mitigate; committing routes address changes through review and the signed image.

Operational behavior the section now states explicitly: after re-genesis, registry keys no longer match so runs deploy their own contracts (not a hard outage); changed code at a recorded address fails Verify and stops startup. Profiles using committed entries must set genesisHash or they match nothing—runs that omit it on a described chain fail at startup instead of redeploying every restart.

--chain-file is narrowed to chains that outlive a run but not a release, plus bootstrap before a file is committed.

Reviewed by Cursor Bugbot for commit 26c1a64. Bugbot is set up for automated code reviews on this repo. Configure here.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Doc-only change reversing the arctic-1 exclusion policy in registry/chains/README.md. The genesisHash and Verify claims check out against the code, but two security/failure-mode claims in the new prose are stronger than what the code supports.

Findings: 0 blocking | 2 non-blocking | 2 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • None at the file/PR level.
  • 2 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread registry/chains/README.md
That alternative is supplying the file from a deployment. Anyone who can edit
that source then points a run at a contract of their choosing. The code-hash
check cannot catch it: the check proves the address holds the code the *file*
recorded, not that the code is ours. Committing removes that exposure. It also

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] "Committing removes that exposure" overstates what committing achieves. --chain-file is still accepted (main.go:71, config.ChainFiles), and Registry.add replaces an entry under the same chainID+genesisHash key, so a supplied file still wins over the compiled-in one (registry/registry.go:132-144; main.go:71 documents "a later file wins"). Anyone who can edit the deployment source can therefore still redirect an arctic-1 run to a contract of their choosing, and the code-hash check still cannot catch it. Committing removes the need to supply a file and gives a reviewed default, but the override path remains — worth stating as the residual trust boundary rather than claiming the exposure is gone.

Comment thread registry/chains/README.md
A re-genesis changes the chain's genesis hash, and the committed entries stop
matching. A run then deploys its own contracts, which is what every run did
before this directory held a file. Recovery is therefore a pull request here, at
whatever pace suits, rather than an outage.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] The graceful-degradation claim holds only when the profile's genesisHash is updated at the same time as the re-genesis. genesisHash comes from the profile config (config/config.go:25, read at generator/prepare.go:233), not from the chain. If a profile still names the pre-re-genesis hash, Resolve matches the stale committed entry, Verify reads no code at the recorded address, and the run fails at startup with MismatchError — whose own message names re-genesis as "the usual cause" (registry/resolve.go:42-48). That is the outage this paragraph says will not happen; "a run then deploys its own contracts" is only reached once the profile carries the new hash. Since the profiles live outside this repo, recovery is still a coordinated profile change, which weakens the trade-off argument as written.

@bdchatham

Copy link
Copy Markdown
Contributor Author

Collapsed into #73. Reviewing these apart cost more than the separate boundaries were worth: the Disperse fix and the Value field were in #74, the read headroom in #76, and the newest estimator fixes in #73, so no branch was individually correct and each review pass had to reason about where a fix actually lived.

Every commit is preserved in #73's history. Nothing is dropped.

@bdchatham bdchatham closed this Aug 29, 2026
bdchatham added a commit that referenced this pull request Aug 29, 2026
…#73)

Replaces the numbers this tool hard-codes with numbers it asks the chain
for.

Sei charges 72,000 for a storage write that takes a slot from zero,
against the
EVM default of 20,000. Every gas constant in the repo was calibrated
against the
default, so every contract scenario was short on Sei — and a short limit
does not
fail visibly. The transaction reaches a block, burns the whole limit,
and a run
with `trackReceipts` off reports it as sent.

| | was | arctic-1 needs |
|---|---|---|
| `AMM.swapAToB` | 85,000 | 185,711 |
| `ERC20.transfer` | 72,156 | 175,242 |
| `ERC721.mint` | 22,460 | 175,000 |
| fee cap (all paths) | 20 gwei | 55 gwei |

## What it does

A scenario declares `GasEstimateCalls()`; the run prices them once at
startup,
after the contracts are bound, and the send path reads a number.
`ContractScenarioBase` does not implement the method, so a scenario
added without
one does not compile.

The fee cap comes from `eth_gasPrice` at startup. Contract addresses for
arctic-1
ship in `registry/chains/`, so a run binds them instead of redeploying
on every
restart.

Pricing failure stops the run. There is no fallback constant, because a
fallback
constant is what this removes.

## Also here

- `/healthz` and `/readyz`, and readiness now drops on every exit rather
than
  only on a signal
- an `amm` scenario, and ERC721's gas constant fixed before it was
deleted

## Verification

Run against arctic-1: contracts bound from the registry with no
deployment sent,
gas quoted per operation, transactions executed — the AMM reserve moved
off its
floor and ERC721 token 1 has an owner.

Every fix broken on purpose first. Four review rounds found five, three,
two and
one defect; all are fixed here. Twice a fix introduced its own mirror
image: the
value-carrying estimate now funds its caller through a state override,
and the
per-quote timeout runs through `WithinBudget` so a hung endpoint reads
as a
failure rather than a clean shutdown.

`gofmt`, `go vet`, `golangci-lint` clean; 15 packages passing.

## Known

- committing `registry/chains/arctic-1.json` makes `genesisHash`
required for any
arctic-1 profile that drives a contract scenario;
`profiles/arctic-1.json` now
  carries it, but a profile kept outside this repo needs it added
- the gas tip is still a constant while the cap is chain-derived, and
StorageRW
packs its calldata twice per send; both are flagged non-blocking on the
PR
- ERC721's token counter restarts at 1, so reusing a recorded contract
wastes gas
  until it passes the previous high-water mark (PLT-1107)
- gas and fee cap are resolved once and held; a long run outliving
either is
  undetected
- `--duration N` exits 1 at the end of a bounded run

Collapsed from #74, #75 and #76, which are closed. Deploys via
sei-protocol/platform#1587.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant