forked from ethereum/go-ethereum
-
Notifications
You must be signed in to change notification settings - Fork 0
slave (M1): add slave node with config and genesis #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
f70d2c7
cmd/slave: add slave node (M1) with config and genesis subcommands
syntrust f92ba2f
fix comment
syntrust 53d3f97
fix comment
syntrust 72238f2
Merge branch 'goshard/base' of https://github.com/QuarkChain/goshard …
syntrust 9b92428
qkc/config: parse and validate per-chain ETH_CHAIN_ID
syntrust 81c1654
qkc/config: compute the ETH_CHAIN_ID derivation in uint64
syntrust bbc7c68
cmd/slave: register shared flags globally and allowlist debug flags
syntrust 4048490
cmd/slave, qkc/config: finalize README structure in the creating mile…
syntrust 152b366
cmd/slave: reference the pinned root-genesis regeneration command
syntrust 67ede3a
Merge branch 'goshard/base' of https://github.com/QuarkChain/goshard …
syntrust c0b4751
merge
syntrust fbfca80
fix comments
syntrust a31e269
fix comments
syntrust b6db6a7
Merge branch 'goshard/base' of https://github.com/QuarkChain/goshard …
syntrust 4a04462
fix build
syntrust 38d5c80
fix comments
syntrust File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # slave | ||
|
|
||
| The goshard slave node. It boots from a pyquarkchain-compatible | ||
| `cluster_config.json` and hosts the shards assigned to one slave identity. It | ||
| performs no network I/O — it is the node, not the protocol (tracking issue | ||
| [#17](https://github.com/QuarkChain/goshard/issues/17)). | ||
|
|
||
| ## Build | ||
|
|
||
| ``` | ||
| make slave | ||
| ``` | ||
|
|
||
| This installs the binary to `./build/bin/slave` (the same convention as `geth`). | ||
| The commands below are run from the repo root so the relative config paths resolve. | ||
|
|
||
| ## Subcommands | ||
|
|
||
| ### `slave config` | ||
|
|
||
| Parse, validate, and print a normalized summary of a cluster config for one slave. | ||
| Exits non-zero on an invalid config or an unknown node id. | ||
|
|
||
| ``` | ||
| ./build/bin/slave config --cluster_config ./qkc/config/singularity/mainnet.json --node_id S0 | ||
| ``` | ||
|
|
||
| ``` | ||
| slave S0 @ 127.0.0.1:38000 | ||
| db path root: ./qkc-data/mainnet | ||
| network id: 1 | ||
| owns 2 shard(s): | ||
| FULL_SHARD_ID CHAIN SHARD CONSENSUS BLOCK_TIME GENESIS_TIME DIFFICULTY GAS_LIMIT ALLOC | ||
| 0x00000001 0 0/1 POW_ETHASH 10s 1556639999 5000000000 12000000 3 | ||
| 0x00040001 4 0/1 POW_ETHASH 10s 1556639999 5000000000 12000000 3 | ||
| config OK | ||
| ``` | ||
|
|
||
| `--node_id S9` (an id the config does not define) prints | ||
| `unknown node id "S9" (config defines: S0)` and exits non-zero. | ||
|
|
||
| ### `slave genesis` | ||
|
|
||
| Derive and print the cluster's root genesis block. The printed hash is | ||
| byte-identical to pyquarkchain's | ||
| `GenesisManager.create_root_block().header.get_hash()`. | ||
|
|
||
| ``` | ||
| ./build/bin/slave genesis --cluster_config ./qkc/config/singularity/mainnet.json | ||
| ``` | ||
|
|
||
| ``` | ||
| root genesis block: | ||
| version: 0 | ||
| height: 0 | ||
| timestamp: 1556639999 | ||
| difficulty: 10000000000000 | ||
| total_difficulty: 10000000000000 | ||
| nonce: 0 | ||
| hash_prev_block: 0x0000000000000000000000000000000000000000000000000000000000000000 | ||
| hash_merkle_root: 0x0000000000000000000000000000000000000000000000000000000000000000 | ||
| seal_hash: 0xe7dcdecc09e724ad81e493d70dedcd6d9ea0ee830d7ab2528a5648f2a0cf8178 | ||
| hash: 0x4036783e441eb5057bf2be96bf1fd4585ac49824de15c0d92a4c14a97886ca51 | ||
| ``` | ||
|
|
||
| The running devnet config works the same way: | ||
|
|
||
| ``` | ||
| ./build/bin/slave genesis --cluster_config ./qkc/config/singularity/devnet.json | ||
| ``` | ||
|
|
||
| prints `hash: 0x5ad443efb7cf5246a3d1bbc1734bd02bf3a5d83bedeccfcfe707d0ebee03780d`. | ||
|
|
||
| ## Fixtures and pyquarkchain cross-validation | ||
|
|
||
| Both real (singularity) cluster configs are checked in under | ||
| [`qkc/config/singularity/`](../../qkc/config/singularity/) — `mainnet.json` and | ||
| `devnet.json`. They are copied verbatim from pyquarkchain; provenance and the | ||
| regeneration steps live in [that directory's README](../../qkc/config/singularity/README.md). | ||
|
|
||
| To cross-validate a `slave genesis` run against pyquarkchain, derive the same | ||
| header there with the command in that README's | ||
| [Pinned root-genesis values](../../qkc/config/singularity/README.md#pinned-root-genesis-values) | ||
| section and compare its `hash` output with the `hash:` line printed here. | ||
|
|
||
| The shard-level `chain genesis` printed by `slave inspect` is the config | ||
| descriptor's fingerprint, not a pyquarkchain minor-block hash; it becomes the | ||
| real shard genesis block hash when the QKC block format (#1) lands. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| // Copyright 2026-2027, QuarkChain. | ||
|
|
||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "io" | ||
| "os" | ||
| "text/tabwriter" | ||
|
|
||
| "github.com/ethereum/go-ethereum/qkc/config" | ||
| "github.com/urfave/cli/v2" | ||
| ) | ||
|
|
||
| var configCommand = &cli.Command{ | ||
| Name: "config", | ||
| Usage: "Parse, validate, and print a normalized cluster config summary for a slave", | ||
| ArgsUsage: " ", | ||
| Flags: []cli.Flag{ | ||
| clusterConfigFlag, | ||
| nodeIDFlag, | ||
| }, | ||
| Description: `Loads and validates a pyquarkchain cluster_config.json, resolves the shards owned | ||
| by --node_id, prints a normalized summary, and reports "config OK". Exits non-zero | ||
| on an invalid config or an unknown node id.`, | ||
| Action: runConfig, | ||
| } | ||
|
|
||
| func runConfig(ctx *cli.Context) error { | ||
| cfg, err := loadClusterConfig(ctx) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| nodeID := ctx.String(nodeIDFlag.Name) | ||
| if nodeID == "" { | ||
| return fmt.Errorf("--%s is required (e.g. S0)", nodeIDFlag.Name) | ||
| } | ||
| slaveCtx, err := cfg.ResolveSlave(nodeID) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| printConfigSummary(os.Stdout, slaveCtx) | ||
| fmt.Fprintln(os.Stdout, "config OK") | ||
| return nil | ||
| } | ||
|
|
||
| func printConfigSummary(out io.Writer, c *config.SlaveContext) { | ||
| fmt.Fprintf(out, "slave %s @ %s:%d\n", c.ID, c.Slave.IP, c.Slave.Port) | ||
| fmt.Fprintf(out, "db path root: %s\n", c.DBPathRoot) | ||
| fmt.Fprintf(out, "network id: %d\n", c.Quarkchain.NetworkID) | ||
| fmt.Fprintf(out, "owns %d shard(s):\n", len(c.Slave.FullShardList)) | ||
|
|
||
| tw := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0) | ||
| fmt.Fprintln(tw, " FULL_SHARD_ID\tCHAIN\tSHARD\tCONSENSUS\tBLOCK_TIME\tGENESIS_TIME\tDIFFICULTY\tGAS_LIMIT\tALLOC") | ||
| for _, shard := range c.ShardConfigs() { | ||
| blockTime := "-" | ||
| if shard.ConsensusConfig != nil { | ||
| blockTime = fmt.Sprintf("%ds", shard.ConsensusConfig.TargetBlockTime) | ||
| } | ||
| fmt.Fprintf(tw, " 0x%08x\t%d\t%d/%d\t%s\t%s\t%d\t%d\t%d\t%d\n", | ||
| shard.GetFullShardId(), | ||
| shard.ChainID, | ||
| shard.ShardID, shard.ShardSize, | ||
| shard.ConsensusType, | ||
| blockTime, | ||
| shard.Genesis.Timestamp, | ||
| shard.Genesis.Difficulty, | ||
| shard.Genesis.GasLimit, | ||
| len(shard.Genesis.Alloc), | ||
| ) | ||
| } | ||
| tw.Flush() | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| // Copyright 2026-2027, QuarkChain. | ||
|
|
||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "io" | ||
| "os" | ||
|
|
||
| "github.com/ethereum/go-ethereum/qkc" | ||
| "github.com/ethereum/go-ethereum/qkc/types" | ||
| "github.com/urfave/cli/v2" | ||
| ) | ||
|
|
||
| var genesisCommand = &cli.Command{ | ||
| Name: "genesis", | ||
| Usage: "Derive and print the root genesis block and its hash", | ||
| ArgsUsage: " ", | ||
| Flags: []cli.Flag{ | ||
| clusterConfigFlag, | ||
| }, | ||
| Description: `Derives the cluster's root genesis block purely from QUARKCHAIN.ROOT.GENESIS and | ||
| prints its fields and hash. The hash is byte-identical to pyquarkchain's | ||
| GenesisManager.create_root_block().header.get_hash().`, | ||
| Action: runGenesis, | ||
| } | ||
|
|
||
| func runGenesis(ctx *cli.Context) error { | ||
| cfg, err := loadClusterConfig(ctx) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| header, err := qkc.CreateRootBlock(cfg.Quarkchain) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| printRootGenesis(os.Stdout, header) | ||
| return nil | ||
| } | ||
|
|
||
| func printRootGenesis(out io.Writer, h *types.RootBlockHeader) { | ||
| fmt.Fprintln(out, "root genesis block:") | ||
| fmt.Fprintf(out, " version: %d\n", h.Version) | ||
| fmt.Fprintf(out, " height: %d\n", h.Number) | ||
| fmt.Fprintf(out, " timestamp: %d\n", h.Time) | ||
| fmt.Fprintf(out, " difficulty: %d\n", h.Difficulty) | ||
| fmt.Fprintf(out, " total_difficulty: %d\n", h.TotalDifficulty) | ||
| fmt.Fprintf(out, " nonce: %d\n", h.Nonce) | ||
| fmt.Fprintf(out, " hash_prev_block: %s\n", h.ParentHash.Hex()) | ||
| fmt.Fprintf(out, " hash_merkle_root: %s\n", h.MinorHeaderHash.Hex()) | ||
| fmt.Fprintf(out, " seal_hash: %s\n", h.SealHash().Hex()) | ||
| fmt.Fprintf(out, " hash: %s\n", h.Hash().Hex()) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| // Copyright 2026-2027, QuarkChain. | ||
|
|
||
| // Command slave is the goshard slave node. It boots from a pyquarkchain-compatible | ||
| // cluster_config.json and hosts the shards assigned to one slave identity. | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "slices" | ||
|
|
||
| "github.com/ethereum/go-ethereum/internal/debug" | ||
| "github.com/ethereum/go-ethereum/internal/flags" | ||
| "github.com/ethereum/go-ethereum/qkc/config" | ||
| "github.com/urfave/cli/v2" | ||
| ) | ||
|
|
||
| // Flags shared by the default run action and the subcommands. The names match how | ||
| // pyquarkchain's cluster.py launches a slave: | ||
| // `slave --cluster_config=<file> --node_id=<id>`. | ||
| // | ||
| // Neither flag is marked Required even though the actions insist on it: they are | ||
| // registered both globally (for the default action) and on the subcommands, and | ||
| // cli enforces a required global flag even when the value is passed after the | ||
| // subcommand name. The actions validate presence instead. | ||
| var ( | ||
| clusterConfigFlag = &cli.StringFlag{ | ||
| Name: "cluster_config", | ||
| Usage: "Path to the pyquarkchain cluster_config.json", | ||
| } | ||
| nodeIDFlag = &cli.StringFlag{ | ||
| Name: "node_id", | ||
| Usage: "Slave identity (e.g. S0), matched against SLAVE_LIST[].ID in cluster_config to select which shards this process owns", | ||
| } | ||
| ) | ||
|
|
||
| // debugFlagAllowlist selects which of geth's debug.Flags the slave registers: | ||
| // logging and file-based profiling only. The slave performs no network I/O of | ||
| // any kind, so the debug flags that open sockets — the pprof HTTP server and | ||
| // pyroscope push — are deliberately absent; debug.Setup treats an unregistered | ||
| // flag as unset and never starts a listener. An allowlist (rather than a | ||
| // blocklist) keeps that boundary fail-closed when a geth merge grows debug.Flags. | ||
| var debugFlagAllowlist = map[string]bool{ | ||
| "verbosity": true, | ||
| "log.vmodule": true, | ||
| "vmodule": true, | ||
| "log.json": true, | ||
| "log.format": true, | ||
| "log.file": true, | ||
| "log.rotate": true, | ||
| "log.maxsize": true, | ||
| "log.maxbackups": true, | ||
| "log.maxage": true, | ||
| "log.compress": true, | ||
| "pprof.memprofilerate": true, | ||
| "pprof.blockprofilerate": true, | ||
| "pprof.cpuprofile": true, | ||
| "go-execution-trace": true, | ||
| } | ||
|
|
||
| func slaveDebugFlags() []cli.Flag { | ||
| var kept []cli.Flag | ||
| for _, f := range debug.Flags { | ||
| if debugFlagAllowlist[f.Names()[0]] { | ||
| kept = append(kept, f) | ||
| } | ||
| } | ||
| return kept | ||
| } | ||
|
|
||
| // newApp assembles the slave CLI app; tests construct it directly. | ||
| func newApp() *cli.App { | ||
| app := flags.NewApp("the goshard slave node") | ||
| app.Name = "slave" | ||
| app.Flags = slices.Concat([]cli.Flag{clusterConfigFlag, nodeIDFlag}, slaveDebugFlags()) | ||
| app.Before = func(ctx *cli.Context) error { | ||
| flags.MigrateGlobalFlags(ctx) | ||
| return debug.Setup(ctx) | ||
| } | ||
| app.After = func(ctx *cli.Context) error { | ||
| debug.Exit() | ||
| return nil | ||
| } | ||
| app.Commands = []*cli.Command{ | ||
| configCommand, | ||
| genesisCommand, | ||
| } | ||
| return app | ||
| } | ||
|
|
||
| func main() { | ||
| if err := newApp().Run(os.Args); err != nil { | ||
| fmt.Fprintln(os.Stderr, err) | ||
| os.Exit(1) | ||
| } | ||
| } | ||
|
|
||
| // loadClusterConfig loads and validates the config named by --cluster_config, | ||
| // insisting the flag is set. | ||
| func loadClusterConfig(ctx *cli.Context) (*config.ClusterConfig, error) { | ||
| path := ctx.String(clusterConfigFlag.Name) | ||
| if path == "" { | ||
| return nil, fmt.Errorf("--%s is required", clusterConfigFlag.Name) | ||
| } | ||
| return config.LoadClusterConfig(path) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error returned by
tw.Flush()is silently ignored.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.