Skip to content

qkc/slave, cmd/slave: boot a slave's shards and run until interrupted - #38

Open
syntrust wants to merge 35 commits into
goshard/basefrom
slave-m3
Open

qkc/slave, cmd/slave: boot a slave's shards and run until interrupted#38
syntrust wants to merge 35 commits into
goshard/basefrom
slave-m3

Conversation

@syntrust

@syntrust syntrust commented Jul 14, 2026

Copy link
Copy Markdown

Milestone M3 of #17, on top of #26 (now merged, so this targets goshard/base).

Boots a slave identity end to end: slave --cluster_config <file> --node_id S0 starts every shard the identity owns, runs until SIGINT/SIGTERM, and shuts down cleanly. No network I/O.

What's included

  • qkc/slave: SlaveBackend, a branch-keyed shard registry mirroring pyquarkchain's SlaveServer. The slave process itself owns no database — all persistent state lives in the per-shard chaindbs, and the registry is rebuilt from config on every boot. A shard that fails to boot rolls back the ones already started (chains stopped, chaindbs closed) before the error returns, so the datadir stays reopenable; Stop() is idempotent and blocks until every shard is down.
  • cmd/slave: the default run action — a drop-in for how pyquarkchain's cluster.py launches a slave. The SIGINT/SIGTERM handler is installed before any resource opens, so a signal landing mid-boot still funnels into the blocking shutdown instead of the OS default; a second signal force-quits.
  • cmd/slave/README.md: a "Running a slave" section written against the current output.

Test plan

go build ./... && go test -race ./qkc/... ./cmd/slave/ — 423 tests / 13 packages green; gofmt and go vet clean.

New tests:

  • qkc/slave: boot + reopen over the {mainnet, devnet} fixtures, checking the registry against config order; boot rollback through a failing ChainService injected via Options.Chain (pebble's directory lock proves the databases were closed, since the reboot that follows would otherwise fail); idempotent Stop.
  • cmd/slave: the bootSlave pipeline with DB_PATH_ROOT redirected to a temp dir; TestRunHonorsSignalDuringStartup reexecs the binary, sends SIGTERM at the slave booting line and asserts exit 0 plus a datadir that reopens.
  • goleak over both packages with a deliberately empty ignore list, so the real chain's background goroutines are heard when they land.

Manual run (devnet, S0): first boot logs genesis committed and shard started genesis=… head=0 per shard, ^C exits 0; rerunning against the same datadir logs existing genesis validated; rerunning it with the mainnet config exits 1 with stored genesis … does not match config genesis … — cluster config changed since initialization; an empty DB_PATH_ROOT runs both shards on in-memory databases and writes nothing to disk.

🤖 Generated with Claude Code

syntrust and others added 9 commits June 23, 2026 18:14
Boot a slave from a pyquarkchain-compatible cluster_config.json. The slave
hosts the shards assigned to one slave identity and performs no network I/O
yet (tracking issue #17).

- cmd/slave: `config` (validate + print a normalized per-slave summary) and
  `genesis` (derive the root genesis block, hash byte-identical to
  pyquarkchain's GenesisManager.create_root_block())
- qkc/config: cluster config loader (load.go) and checked-in singularity
  mainnet/devnet configs
- qkc/genesis: root-block derivation
- qkc/types: root block, token balances, helpers
- Makefile: `make slave` target

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…etadata

Add the per-shard host the slave boots each of its shards through:

- genesis.go: Genesis descriptor built from ShardGenesis (fields + parsed
  ALLOC) with a Petersburg-only ChainConfig, ChainID = BASE_ETH_CHAIN_ID +
  chain_id + 1 (pyquarkchain config.py:363); Fingerprint() gives the
  descriptor a deterministic identity the stub reports as its genesis hash.
- services.go: the ShardChain seam (genesis hash, head, Stop) between the
  slave skeleton and the geth-core shard-chain task, plus the Options
  injection points for #4 (engine/miner), #5 (master conn), and sync; the
  stub chain reports head height 0 at the descriptor's identity.
- rawdb.go: GenesisMeta record (root-genesis linkage + xshard cursor at
  (root_height, 0, 0), genesis.py:92) under a single QKC-prefixed key,
  encoded with qkc/serialize; ReconcileGenesisMeta passes reopen only on an
  exact match and hard-errors naming both genesis hashes and the db path.
  Marked temporary: re-implemented, not patched, once QKC block format (#1)
  lands.
- shard.go: Shard{Branch, cfg, db, chain}; New opens an isolated pebble
  chaindb under {datadir}/shard-0x{full_shard_id}/, reconciles the metadata,
  and constructs the chain through the seam; Stop is idempotent and closes
  chain then db.

qkc/config gains ETH_CHAIN_ID parsing on ChainConfig: pyquarkchain forces
ETH_CHAIN_ID = BASE_ETH_CHAIN_ID + CHAIN_ID + 1 on load (config.py:534), so
Validate() accepts a configured value only when consistent.

Tests cover the descriptor (table-driven over the mainnet/devnet fixtures),
fingerprint determinism/sensitivity, metadata round-trip, reconcile, the
milestone demo (boot one shard into a temp dir, reopen idempotently), and
the loud mismatch errors for shard- and root-genesis config changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pyquarkchain always derives ETH_CHAIN_ID as BASE_ETH_CHAIN_ID + CHAIN_ID + 1
and overwrites any configured value with that derivation on load
(config.py:534); accept a configured value only when consistent with it.
@syntrust syntrust changed the title qkc/slave: add slave boot and lifecycle (M3) slave (M3): add slave boot and lifecycle Jul 14, 2026
@syntrust
syntrust marked this pull request as ready for review July 14, 2026 11:11
syntrust and others added 11 commits July 15, 2026 10:10
Shard.New logs "existing genesis validated" on reopen and "genesis
metadata written" on first boot, using the new existed return value.
An empty DB_PATH_ROOT means an ephemeral in-memory database in pyquarkchain
(use_mem_db, cluster_config.py:247) and in goquarkchain (geth's ephemeral-node
convention). Previously it silently created a persistent pebble db in the
process working directory.
pyquarkchain's arithmetic is unbounded, so BASE_ETH_CHAIN_ID + CHAIN_ID + 1
may exceed uint32; the uint32 sum wrapped silently (worst case to 0, which
reads as "absent" and passes any configured value).
… rule set

- The ETH_CHAIN_ID derivation is computed in uint64, matching pyquarkchain's
  unbounded arithmetic; the uint32 sum wrapped silently (worst case to 0,
  which reads as "absent" and puts a wrong replay-protection chain id into
  the EVM rule set).
- Fingerprint() now encodes the whole ChainConfig instead of just the chain
  id, so a change to the compiled-in fork schedule changes every fingerprint
  and forces a loud re-bootstrap instead of executing new semantics on an
  old db.
ReconcileGenesisMeta no longer writes on a fresh db: Shard.New commits the
record once the chain stands at that genesis, so a boot that fails at chain
construction leaves no record behind and the retry re-runs the fresh path
instead of reporting a never-validated record as existing.
…boot

The slave performs no network I/O of any kind, but registering all of
geth's debug.Flags made --pprof start an HTTP listener and --pyroscope
push to a remote server. Register an allowlisted subset instead: logging
and file-based profiling only, fail-closed against upstream additions,
with a test pinning the boundary in both directions.

Install the SIGINT/SIGTERM handler before any resource opens, so a
signal that lands during config load or shard boot still funnels into
the blocking Stop() path instead of the OS default termination. A
watcher restores default signal handling the moment the first signal
lands, keeping the second-signal force-quit available even mid-boot.
Covered by a reexec subprocess test that signals at the 'slave booting'
line and asserts a clean exit plus a reopenable datadir.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pattern was added with a leading space, which is significant in
gitignore syntax, so it never matched anything. Remove it rather than
fix it and restore the trailing newline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopt the final CLI shape in the milestone that creates these files: newApp
assembles the app for tests, the cluster_config/node_id flags register globally
without Required (actions validate presence via loadClusterConfig), and only
non-networked debug flags are exposed, pinned by TestNoNetworkedDebugFlags.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
syntrust and others added 12 commits July 16, 2026 16:15
Backport the final shape of the shard package into the milestone that creates
it: DBDirName/ParseDBDirName canonicalize the shard chaindb directory names,
and the stub-genesis surrogate paths carry their follow-up TODO markers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Take the finalized cmd/slave CLI shape from slave-m2 and relocate the boot
tests it superseded: the fixture helpers, TestBootSlave, and
TestBootSlaveRejectsBadNodeID move from slave_test.go (owned by slave-m1) into
run_test.go, which also adopts its final goleak TestMain form; run.go and
qkc/slave pick up their follow-up TODO markers. main.go keeps only the
one-line app.Action wiring on top of slave-m2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stone

Drop the milestone markers and the not-yet-implemented tracker, adopt the final
fixtures/pyquarkchain cross-validation section (including the virtualenv note in
the singularity regeneration command), so later milestones only add sections.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The README baseline now carries the final section structure; keep only the
'Running a slave' section on top (its milestone marker dropped with the rest).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pyquarkchain derivation snippet already lives (with seal/serialize output)
in qkc/config/singularity/README.md; link to that section instead of carrying
a trimmed copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The record is a bootstrap stamp standing in for the genesis block, not a
block "meta" in the pyquarkchain sense — half its fields live in the
header there — so free the word before the QKC block format (#1) ports
MinorBlockMeta. The chaindb key moves with it (QKC-genesis-meta ->
QKC-genesis-record); no migration by design: the record is delete-not-
migrate scaffolding, and a stub-era datadir simply re-stamps on its next
boot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@syntrust
syntrust force-pushed the slave-m2 branch 2 times, most recently from 679e101 to b6dd7e9 Compare July 31, 2026 07:36
#44 and #26 rewrote the shard package around the real QKC minor genesis
block, so this merge takes the new base wholesale and re-applies only what
this milestone owns, ported to it:

- The root genesis comes from qkc.CreateRootBlock; the qkc/genesis package
  is gone.
- The injected ChainService follows the current seam,
  New(db, *types.MinorBlock, *params.ChainConfig).
- The README's "Running a slave" section is rewritten against the current
  output: genesis committed / existing genesis validated per shard, and the
  refusal that names both hashes when the config changed.

Four commits on this branch were superseded upstream and are dropped rather
than merged: the GenesisMeta -> GenesisRecord rename and its doc follow-up
(no record exists any more), fingerprinting the whole rule set (replaced by
storing the chain config under its own key and reconciling it with
CheckCompatible), and committing the metadata only after the chain
constructs (now carried by the timing of WriteGenesisBlock).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@syntrust syntrust changed the title slave (M3): add slave boot and lifecycle qkc/slave, cmd/slave: boot a slave's shards and run until interrupted Aug 6, 2026
@syntrust
syntrust changed the base branch from slave-m2 to goshard/base August 6, 2026 10:26
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