slave (M1): add slave node with config and genesis - #21
Conversation
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>
|
Looks like both this PR and #20 are adding duplicate content to |
All types required to achieve the current milestone. We can use a refactor later before merging into the base. |
| app.Flags = slices.Concat( | ||
| []cli.Flag{}, | ||
| debug.Flags, | ||
| ) |
There was a problem hiding this comment.
The expression slices.Concat([]cli.Flag{}, debug.Flags) is unnecessarily complex. Concatenating with an empty slice produces the same result as debug.Flags alone. Simplifying to debug.Flags removes one import ("slices") and makes the intent clearer.
| len(shard.Genesis.Alloc), | ||
| ) | ||
| } | ||
| tw.Flush() |
There was a problem hiding this comment.
The error returned by tw.Flush() is silently ignored.
There was a problem hiding this comment.
Ignoring Flush() errors is consistent with surrounding Fprintf calls, both of which write targeting os.Stdout. This is common in geth, so it's acceptable and not a defect.
| if v.Sign() == 0 { | ||
| continue | ||
| } | ||
| t.balances[k.Uint64()] = v |
There was a problem hiding this comment.
Validate bigint(k) range before converting to uint64 to prevent truncation collisions.
| num, err := bb.GetUInt32() | ||
| if err != nil { | ||
| return err | ||
| } |
There was a problem hiding this comment.
Should we add an entry count validation here to prevent potential OOM/DoS attacks from malicious data?
There was a problem hiding this comment.
Not really. Firstly, make does not pre-allocate according to num; secondly, every time an entry is read, it goes through the getBytes boundary check; plus, forcing an upper limit that pyquarkchain does not have will only create consensus disagreements.
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.
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).
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>
…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 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>
| // overwrites any configured value with that derivation (config.py:534), so a | ||
| // present value is only accepted when consistent (checked in Validate). Zero | ||
| // means absent: real chain ids start at BASE_ETH_CHAIN_ID + 1 > 0. | ||
| EthChainID uint32 `json:"ETH_CHAIN_ID,omitempty"` |
There was a problem hiding this comment.
ETH_CHAIN_ID is validated but never derived. When omitted (as in both singularity configs), chain and shard configs retain 0, while pyquarkchain sets BASE_ETH_CHAIN_ID + CHAIN_ID + 1. Normalize it before copying ChainConfig into shard configs, and distinguish omission from an explicit zero.
| return nil, fmt.Errorf("genesis: ROOT.GENESIS.HASH_MERKLE_ROOT: %w", err) | ||
| } | ||
|
|
||
| difficulty := new(big.Int).SetUint64(g.Difficulty) |
There was a problem hiding this comment.
RootGenesis.Difficulty is uint64 and Nonce is uint32, but the QKC wire types are biguint and uint64. Valid pyquarkchain configs such as difficulty 2^64 or nonce 2^32 are rejected before hashing. Please preserve the wire ranges and add a Python golden case.
| if shard.Genesis == nil { | ||
| return fmt.Errorf("full shard id 0x%08x has no GENESIS", id) | ||
| } | ||
| if shard.Genesis.RootHeight != root.Height { |
There was a problem hiding this comment.
Validate does not check shard-genesis hex fields. For example, EXTRA_DATA: "zz" is silently decoded as empty and slave config reports config OK; the failure is deferred until shard genesis creation. Strictly validate HASH_PREV_MINOR_BLOCK, HASH_MERKLE_ROOT, EXTRA_DATA, and ALLOC code/storage at load time.
| if slave == nil { | ||
| continue | ||
| } | ||
| if len(slave.ChainMaskListForBackward) > 0 { |
There was a problem hiding this comment.
len(...) > 0 misses a present but empty CHAIN_MASK_LIST. With CHAIN_MASK_LIST: [] and no FULL_SHARD_ID_LIST, this config passes and reports zero shards. Track field presence and reject the legacy key whenever it is present.
| // TokenBalances maps a token id to a balance. It implements serialize.Serializable | ||
| // so it can be embedded in RootBlockHeader and hashed byte-identically to | ||
| // pyquarkchain's TokenBalanceMap. | ||
| type TokenBalances struct { |
There was a problem hiding this comment.
TokenBalances depends on the pending PR from Alan. Please sync/rebase this implementation onto the latest TokenBalances code after that PR lands, to avoid maintaining a duplicate or divergent type.
| } | ||
| nodeIDFlag = &cli.StringFlag{ | ||
| Name: "node_id", | ||
| Usage: "Slave identity (e.g. S0) selecting which shards this process owns", |
There was a problem hiding this comment.
Should the "Usage" section mention that this node_id needs to match the name of the xxx field in the configuration?
|
|
||
| Both: 8 chains (one shard each), 4 slaves `S0..S3` each owning 2 shards, | ||
| `FULL_SHARD_ID_LIST` form (S0's first id is written `0x1`), genesis `ALLOC` present. | ||
|
|
There was a problem hiding this comment.
Should we add some notes regarding the configuration? For instance:
To expose the service to public, you need to change, "JSON_RPC_HOST" to 0.0.0.0,
as well as the following settings:
PRIVATE_JSON_RPC_HOST
ENABLE_TRANSACTION_HISTORY
Or add configuration files for specific purposes, such as a node with an index.
There was a problem hiding this comment.
Not related to this PR. We may expand/update this doc later.
| "github.com/ethereum/go-ethereum/common" | ||
| "github.com/ethereum/go-ethereum/qkc/account" | ||
| ) | ||
|
|
There was a problem hiding this comment.
This file is included in this minimal version rootblock to ensure the PR is self-contained; since more content will need to be added later, it would be best to include a TODO comment explaining this.
| // mixhash, and signature are all empty/zero, exactly as the Python default header. | ||
| // The resulting header.Hash() is byte-identical to pyquarkchain's | ||
| // create_root_block().header.get_hash(). | ||
| func RootBlock(qkc *config.QuarkChainConfig) (*types.RootBlockHeader, error) { |
| @@ -0,0 +1,79 @@ | |||
| // Copyright 2026-2027, QuarkChain. | |||
There was a problem hiding this comment.
Its previous path is core/genesis.go, should we put it qkc/core/genesis.go?
| @@ -0,0 +1,79 @@ | |||
| // Copyright 2026-2027, QuarkChain. | |||
There was a problem hiding this comment.
Its previous path is core/genesis.go, should we put it qkc/core/genesis.go?
Boot a slave from a pyquarkchain-compatible cluster_config.json. The slave hosts the shards assigned to one slave identity (tracking issue #17).
config(validate + print a normalized per-slave summary) andgenesis(derive the root genesis block, hash byte-identical to pyquarkchain's GenesisManager.create_root_block())ETH_CHAIN_IDconsistency check) and checked-in singularity mainnet/devnet configsmake slavetargetTests passed: