Skip to content

Commit bf85671

Browse files
authored
Merge pull request #1 from Reiers/feat/fill-missing-node-core
feat: fill missing node core P0 vertical slice
2 parents b1e175a + 1b419c5 commit bf85671

14 files changed

Lines changed: 657 additions & 7 deletions

File tree

cmd/curiocore/main.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"errors"
99
"flag"
1010
"fmt"
11+
"net/http"
1112
"os"
1213
"path/filepath"
1314
"strings"
@@ -18,8 +19,10 @@ import (
1819
importer "github.com/Reiers/curio-core/internal/import"
1920
"github.com/Reiers/curio-core/internal/logging"
2021
"github.com/Reiers/curio-core/internal/node"
22+
"github.com/Reiers/curio-core/internal/rpc"
2123
"github.com/Reiers/curio-core/internal/snapshot"
2224
"github.com/Reiers/curio-core/internal/status"
25+
"github.com/Reiers/curio-core/internal/store"
2326
"github.com/Reiers/curio-core/internal/wallet"
2427
)
2528

@@ -72,6 +75,11 @@ func main() {
7275
logger.Errorf("wallet failed: %v", err)
7376
os.Exit(1)
7477
}
78+
case "rpc":
79+
if err := cmdRPC(os.Args[2:], logger); err != nil {
80+
logger.Errorf("rpc failed: %v", err)
81+
os.Exit(1)
82+
}
7583
default:
7684
usage()
7785
os.Exit(1)
@@ -89,6 +97,7 @@ func usage() {
8997
fmt.Println(" curiocore chain msg --decode <hex|base64> [--explain]")
9098
fmt.Println(" curiocore chain coverage-report")
9199
fmt.Println(" curiocore wallet new|list|show|export|import|resolve|sign|verify")
100+
fmt.Println(" curiocore rpc serve [--listen :1234]")
92101
fmt.Println("")
93102
fmt.Println("Examples:")
94103
fmt.Println(" curiocore doctor --explain")
@@ -334,8 +343,16 @@ func cmdStatus(args []string, log *logging.Logger) error {
334343
if err != nil {
335344
return err
336345
}
346+
cs := store.NewChainStore(cfg.NetworkDataDir())
347+
head, _ := cs.Head()
337348
if *jsonOut {
338-
fmt.Println(s.JSON())
349+
out := map[string]any{"status": s, "head": head}
350+
b, _ := json.Marshal(out)
351+
fmt.Println(string(b))
352+
return nil
353+
}
354+
if head != nil {
355+
fmt.Printf("stage=%s progress=%d%% height=%d tipset=%s updated=%s message=%s\n", s.Stage, s.Progress, head.Height, head.TipSetKey, s.UpdatedAt.Format(time.RFC3339), s.Message)
339356
return nil
340357
}
341358
fmt.Printf("stage=%s progress=%d%% updated=%s message=%s\n", s.Stage, s.Progress, s.UpdatedAt.Format(time.RFC3339), s.Message)
@@ -357,6 +374,7 @@ func cmdDoctor(args []string, log *logging.Logger) error {
357374
dataDir := fs.String("data-dir", defaultHome(), "curiocore home")
358375
explain := fs.Bool("explain", false, "explain each check and remediation")
359376
jsonOut := fs.Bool("json", false, "json output")
377+
postImport := fs.Bool("post-import", false, "run post-import chainstore checks")
360378
if err := fs.Parse(args); err != nil {
361379
return err
362380
}
@@ -365,6 +383,13 @@ func cmdDoctor(args []string, log *logging.Logger) error {
365383
if err != nil {
366384
return err
367385
}
386+
if *postImport {
387+
post, pErr := doctor.RunPostImport(cfg.NetworkDataDir())
388+
if pErr != nil {
389+
return pErr
390+
}
391+
checks = append(checks, post...)
392+
}
368393
if *jsonOut {
369394
b, _ := json.MarshalIndent(checks, "", " ")
370395
fmt.Println(string(b))
@@ -597,6 +622,28 @@ func cmdWallet(args []string, _ *logging.Logger) error {
597622
}
598623
}
599624

625+
func cmdRPC(args []string, log *logging.Logger) error {
626+
if len(args) == 0 || args[0] != "serve" {
627+
return errors.New("usage: curiocore rpc serve [--listen :1234] [--data-dir ~/.curiocore]")
628+
}
629+
fs := flag.NewFlagSet("rpc serve", flag.ContinueOnError)
630+
listen := fs.String("listen", ":1234", "listen address")
631+
dataDir := fs.String("data-dir", defaultHome(), "curiocore home")
632+
if err := fs.Parse(args[1:]); err != nil {
633+
return err
634+
}
635+
cfg, err := config.LoadOrDefault(*dataDir)
636+
if err != nil {
637+
return err
638+
}
639+
if err := config.InitDirs(cfg); err != nil {
640+
return err
641+
}
642+
srv := rpc.New(cfg)
643+
log.Infof("rpc listening on %s", *listen)
644+
return http.ListenAndServe(*listen, srv.Handler())
645+
}
646+
600647
func decodeMessage(v string) ([]byte, string, error) {
601648
trim := strings.TrimSpace(v)
602649
trim = strings.TrimPrefix(trim, "0x")

docs/alpha2_1-checklist.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Alpha2.1 Checklist
2+
3+
## Done
4+
5+
- P0: Persistent blockstore introduced (`data/<network>/blockstore/*.blk`)
6+
- P0: Persistent chainstore with canonical head (`data/<network>/chainstore/head.json`)
7+
- P0: Snapshot import now materializes chain data into blockstore and commits head/state root
8+
- P0: Sync vertical slice now advances head incrementally after startup (post-import continuity path)
9+
- P0: `curiocore status` now reports real chain height from chainstore
10+
- P0: `curiocore doctor --post-import` checks added:
11+
- chain head exists
12+
- blockstore populated
13+
- state root reachable
14+
- P1 usability: Minimal RPC surface added:
15+
- `/rpc/v0/status`
16+
- `/rpc/v0/chain/head`
17+
- `/rpc/v0/chain/message/:cid`
18+
- `/metrics`
19+
- Tests added for store, importer, and doctor post-import checks
20+
21+
## What’s next
22+
23+
- Replace incremental sync placeholder with real peer fetch + tipset/header validation pipeline
24+
- Add bad block cache + fork choice + reorg-safe application path
25+
- Add real message index for RPC message lookup
26+
- Add auth model for RPC endpoints
27+
- Add FVM apply path behind feature flag
28+
29+
## Blocked
30+
31+
- Full consensus/VM parity requires deeper Lotus-compatible validation execution integration
32+
- High-throughput SP message handling depends on real mempool + network exchange path
33+
34+
## Complexity buckets (effort only)
35+
36+
- **S**
37+
- RPC auth middleware (local token mode)
38+
- richer metrics labels and counters
39+
- **M**
40+
- bad block cache and fork-choice module
41+
- header validation pipeline with persisted checkpoints
42+
- **L**
43+
- full state transition validation parity with Lotus/FVM
44+
- production-grade p2p sync and SP traffic handling under reorg stress
45+
46+
## How to verify
47+
48+
1. Initialize and run fast sync/import:
49+
- `curiocore init`
50+
- `curiocore sync --yes --mode fast`
51+
2. Verify status contains height:
52+
- `curiocore status`
53+
3. Verify post-import health:
54+
- `curiocore doctor --post-import`
55+
4. Run RPC server:
56+
- `curiocore rpc serve --listen :1234`
57+
5. Query endpoints:
58+
- `curl localhost:1234/rpc/v0/status`
59+
- `curl localhost:1234/rpc/v0/chain/head`
60+
- `curl localhost:1234/metrics`

docs/gap-matrix.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Curio Core Capability Gap Matrix (vs Lotus / Venus / Forest)
2+
3+
Date: 2026-02-13
4+
5+
Severity rubric:
6+
- **P0** = cannot sync reliably without it
7+
- **P1** = required for real-world SP traffic / stability
8+
- **P2** = can ship later
9+
10+
| Capability | Lotus | Venus | Forest | Curio Core Today | Gap Severity | What we do |
11+
|---|---|---|---|---|---|---|
12+
13+
## A) Chain Sync
14+
15+
| Capability | Lotus | Venus | Forest | Curio Core Today | Gap Severity | What we do |
16+
|---|---|---|---|---|---|---|
17+
| Tipset fetch/validation pipeline | Full staged syncer + manager + validator | Full dispatcher/syncer pipeline | Full task-state-machine sync | Snapshot import + `node.StartSkeleton`; no real fetch/validate loop | **P0** | Implement staged sync pipeline: fetch -> header validate -> persist -> candidate head update |
18+
| Bad block handling | Built-in bad block cache/policy | Bad tipset tracking | Explicit bad-block policy/cache | None | **P0** | Add bad block cache + rejection gate before persistence |
19+
| Fork choice | Heaviest-chain with finality guards | Chain selector interfaces | Weight-based with validation gates | None | **P0** | Add weight comparator + head selection interface |
20+
| Reorg handling | Mature reorg notifications/rollback | Head update with rollback logic | Head update state machine | None | **P0** | Add canonical head update + rollback-safe state transitions |
21+
| Chain store persistence | Persistent chain + metadata stores | Persistent store/checkpoint/head | Persistent chain store | Only `imported.snapshot.meta` text file | **P0** | Add chain store + blockstore persistence layout |
22+
| Header vs full validation | Both (staged fast path then full) | Both | Both | No chain validation | **P1** | Add explicit validation stage flags, with header-only bootstrap mode |
23+
24+
## B) State / VM Execution
25+
26+
| Capability | Lotus | Venus | Forest | Curio Core Today | Gap Severity | What we do |
27+
|---|---|---|---|---|---|---|
28+
| FVM integration / actor execution | Yes | Yes | Yes (multi-FVM shims) | None | **P0** | Add feature-flagged apply stub with deterministic interfaces for future FVM integration |
29+
| Message application | Full | Full | Full | Decode helper only | **P0** | Add message decode registry + execution pipeline stubs + coverage reporting |
30+
| Gas accounting | Full | Full | Full | None | **P1** | Add gas accounting interfaces and test fixtures around message application flow |
31+
| AMT/HAMT data structures | Full | Full | Full | None | **P1** | Add adapters for AMT/HAMT traversal in state module (Go now; Rust sidecar later) |
32+
| State tree/CBOR decoding | Full | Full | Full | None | **P0** | Add state root resolver + basic CBOR decode path |
33+
34+
## C) Networking
35+
36+
| Capability | Lotus | Venus | Forest | Curio Core Today | Gap Severity | What we do |
37+
|---|---|---|---|---|---|---|
38+
| libp2p host | Yes | Yes | Yes | `network.Supported` only | **P0** | Add minimal libp2p host and peer manager |
39+
| gossipsub | Yes | Yes | Yes | None | **P1** | Add gossipsub subscription for blocks/messages (initial read-only) |
40+
| peer scoring | Yes | Yes | Yes | None | **P1** | Add baseline peer scoring config compatible with Lotus defaults |
41+
| hello/identify/exchange | Yes | Yes | Yes | None | **P0** | Add identify + hello + chain exchange fetch entrypoints |
42+
| bitswap/blockstore strategy | Yes | Partial | Yes | None | **P1** | Add block fetch strategy integrated with blockstore and backpressure |
43+
44+
## D) Storage
45+
46+
| Capability | Lotus | Venus | Forest | Curio Core Today | Gap Severity | What we do |
47+
|---|---|---|---|---|---|---|
48+
| blockstore | Yes | Yes | Yes | No real blockstore | **P0** | Implement persistent blockstore interface + default local backend |
49+
| chain datastore | Yes | Yes | Yes | No chain datastore | **P0** | Implement chain metadata store (head, tipset index, bad blocks) |
50+
| snapshot import compatibility (Forest assumptions) | Yes | N/A | Native | Imports bytes only; no chain graph/state wiring | **P0** | Parse imported snapshot into blockstore + set canonical head and state roots |
51+
| pruning strategy | Mature options | Some options | Modes include stateless patterns | None | **P2** | Defer; design after stable sync correctness |
52+
53+
## E) RPC / API
54+
55+
| Capability | Lotus | Venus | Forest | Curio Core Today | Gap Severity | What we do |
56+
|---|---|---|---|---|---|---|
57+
| compatible endpoints (minimal tooling set) | Broad | Broad | Growing | CLI only; no RPC server | **P0** | Add minimal RPC server: status, chain head, message lookup |
58+
| auth model | Token/JWT style controls | Similar | Present | None | **P1** | Add token auth middleware and local admin-only mode |
59+
| admin commands | Yes | Yes | Yes | CLI basics only | **P1** | Add RPC admin endpoints for sync controls and diagnostics |
60+
| metrics endpoint | Yes | Yes | Yes | None | **P1** | Add `/metrics` Prometheus endpoint |
61+
62+
## F) Observability
63+
64+
| Capability | Lotus | Venus | Forest | Curio Core Today | Gap Severity | What we do |
65+
|---|---|---|---|---|---|---|
66+
| structured logs | Yes | Yes | Yes | Basic logger | **P1** | Add sync-context fields (peer, tipset, CID, stage) |
67+
| metrics (prometheus) | Yes | Yes | Yes | None | **P1** | Add sync lag, fetch failures, head height, queue depth metrics |
68+
| tracing hooks | Partial/varies | Partial/varies | Strong async tracing | None | **P2** | Add OpenTelemetry hooks after core path stabilizes |
69+
70+
## G) Wallet / Signing
71+
72+
| Capability | Lotus | Venus | Forest | Curio Core Today | Gap Severity | What we do |
73+
|---|---|---|---|---|---|---|
74+
| f1/f3/f4 complete | Yes | Yes | Yes | Placeholder address/key flows | **P1** | Replace placeholders with real key derivation + validation |
75+
| f2 resolve | Yes | Yes | Yes | TODO stub | **P1** | Implement on-chain actor resolve via state lookup |
76+
| keystore security model | Mature | Mature | Mature | Local encrypted JSON (alpha) | **P1** | Harden keystore format, locking, and key material policy |
77+
| message signing + send flow | Full | Full | Full | Alpha sign/verify placeholders | **P1** | Add real signing + mpool send path once RPC/network path exists |

docs/rust-adoption.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Curio Core Rust Adoption Decision
2+
3+
Date: 2026-02-13
4+
5+
## Decision
6+
7+
**Choose Option A: Rust sidecar service (recommended), but only for data-plane heavy paths first.**
8+
9+
This is the lowest-risk path that gives real performance upside without destabilizing consensus-critical behavior.
10+
11+
## Where Rust goes first
12+
13+
1. **Snapshot import + CAR parsing**
14+
- High CPU/IO pressure
15+
- Easy to validate with deterministic outputs
16+
2. **AMT/HAMT traversal and state decode acceleration**
17+
- Hot loops suitable for Rust memory/perf profile
18+
3. **Block validation acceleration helpers (non-authoritative initially)**
19+
- Run as assistive path with parity checks against Go path
20+
21+
## What stays Go now (Lotus parity critical)
22+
23+
- Fork choice and canonical head decision
24+
- Reorg application semantics
25+
- Network-version/upgrade policy wiring
26+
- RPC compatibility surface and operational controls
27+
- Any authoritative consensus decision until conformance harness is mature
28+
29+
## Safest boundary choice
30+
31+
**Boundary: sidecar over gRPC/IPC**
32+
33+
Why:
34+
- Clear process isolation and crash containment
35+
- Versioned contracts and easier rollout/rollback
36+
- Better observability than raw FFI
37+
- Avoids GC/ownership complexity and CGO brittleness in consensus hot path
38+
39+
## What we explicitly avoid right now
40+
41+
- No fragile CGO/FFI in the core sync decision loop
42+
- No language-percentage cargo-cult migration
43+
- No consensus-authoritative Rust execution cutover before differential parity proof
44+
45+
## Rollout policy
46+
47+
- Start Rust sidecar in shadow/assist mode
48+
- Compare outputs against Go path
49+
- Promote functionality only after stable no-diff windows and replay tests

internal/doctor/doctor.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package doctor
22

33
import (
4+
"encoding/json"
5+
"errors"
46
"fmt"
57
"os"
68
"os/exec"
@@ -20,6 +22,11 @@ func Run(homeDir, dataDir string) ([]Check, error) {
2022
return checks, nil
2123
}
2224

25+
func RunPostImport(dataDir string) ([]Check, error) {
26+
checks := []Check{checkChainHead(dataDir), checkBlockstorePopulated(dataDir), checkStateRootReachable(dataDir)}
27+
return checks, nil
28+
}
29+
2330
func checkAria2c() Check {
2431
if _, err := exec.LookPath("aria2c"); err != nil {
2532
return Check{
@@ -59,3 +66,51 @@ func checkWritable(dataDir string) Check {
5966
_ = os.Remove(testPath)
6067
return Check{Name: "data-dir-writable", OK: true, Message: fmt.Sprintf("data dir is writable: %s", dataDir)}
6168
}
69+
70+
func checkChainHead(dataDir string) Check {
71+
headFile := filepath.Join(dataDir, "chainstore", "head.json")
72+
b, err := os.ReadFile(headFile)
73+
if err != nil {
74+
if errors.Is(err, os.ErrNotExist) {
75+
return Check{Name: "chain-head-exists", OK: false, Message: "chain head missing", Fix: "Re-run snapshot import and ensure head is committed"}
76+
}
77+
return Check{Name: "chain-head-exists", OK: false, Message: err.Error(), Fix: "Inspect chainstore/head.json permissions"}
78+
}
79+
if len(b) == 0 {
80+
return Check{Name: "chain-head-exists", OK: false, Message: "chain head file empty", Fix: "Re-import snapshot"}
81+
}
82+
return Check{Name: "chain-head-exists", OK: true, Message: "chain head exists"}
83+
}
84+
85+
func checkBlockstorePopulated(dataDir string) Check {
86+
dir := filepath.Join(dataDir, "blockstore")
87+
ents, err := os.ReadDir(dir)
88+
if err != nil {
89+
return Check{Name: "blockstore-populated", OK: false, Message: "blockstore missing", Fix: "Re-run snapshot import"}
90+
}
91+
if len(ents) == 0 {
92+
return Check{Name: "blockstore-populated", OK: false, Message: "blockstore empty", Fix: "Re-run snapshot import"}
93+
}
94+
return Check{Name: "blockstore-populated", OK: true, Message: fmt.Sprintf("blockstore contains %d blocks", len(ents))}
95+
}
96+
97+
func checkStateRootReachable(dataDir string) Check {
98+
headFile := filepath.Join(dataDir, "chainstore", "head.json")
99+
b, err := os.ReadFile(headFile)
100+
if err != nil {
101+
return Check{Name: "state-root-reachable", OK: false, Message: "chain head unavailable", Fix: "Re-run snapshot import"}
102+
}
103+
var h struct {
104+
StateRoot string `json:"stateRoot"`
105+
}
106+
if err := json.Unmarshal(b, &h); err != nil {
107+
return Check{Name: "state-root-reachable", OK: false, Message: "invalid head format", Fix: "Re-run snapshot import with latest binary"}
108+
}
109+
if h.StateRoot == "" {
110+
return Check{Name: "state-root-reachable", OK: false, Message: "state root missing", Fix: "Import a snapshot containing state root metadata"}
111+
}
112+
if _, err := os.Stat(filepath.Join(dataDir, "blockstore", h.StateRoot+".blk")); err != nil {
113+
return Check{Name: "state-root-reachable", OK: false, Message: "state root block not found in blockstore", Fix: "Re-run import to rebuild blockstore"}
114+
}
115+
return Check{Name: "state-root-reachable", OK: true, Message: "state root reachable"}
116+
}

0 commit comments

Comments
 (0)