feat: heartbeat identity signing + mycelium IP preservation + dioxus native island #91
No reviewers
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_compute!91
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "development_mik02_heartbeat_preserve_ip"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Two independent feature tracks developed during sessions 17-18:
1. Heartbeat Identity and Signing
2. Dioxus Native Island (additive, non-breaking)
3. Regenerated OSIS bindings
After merge
hero_cloud_appdependency inhero_os/crates/hero_os_app/Cargo.toml(line 309, currently commented out)hero_cloud_appback toisland-compute-nativefeatureTest plan
Phase 3 step 3 of the scaling architecture initiative. The heartbeat sender now signs each heartbeat with the node's persistent ed25519 identity (introduced in the previous commit). Explorers can verify the signature against the node's implicit account (hex(pubkey)) so only the real node can update its own record — no more trusting hostname/IP blindly. Protocol -------- Canonical signed payload is a sorted-key JSON blob (BTreeMap → stable byte order) over exactly: { available_slices, hostname, mycelium_ip, node_account, slice_count, timestamp } The sender computes sha256 of those bytes, signs with ed25519, and adds three new fields to the node_heartbeat RPC params: node_account — 64-char hex implicit account (hex(pubkey)) timestamp — unix secs, included in the signed hash for replay protection signature — base64 ed25519 sig over sha256(canonical_payload) The signing shape (sha256-then-ed25519) matches the hero_ledger gateway auth layer so the Hero ecosystem has exactly one canonicalization convention. Why sorted-key JSON: - Language-independent — any verifier with serde_json or BTreeMap or Python's json.dumps(sort_keys=True) reconstructs the same bytes. - Human-inspectable in logs if needed. - Byte-stable regardless of source ordering of struct fields. Backward compat --------------- The three new fields are additive — older explorers without signature-verification logic simply ignore them and process the heartbeat exactly as before. The explorer-side verification lands in the next commit, feature-flagged behind ALLOW_LEGACY_HEARTBEATS with a grace period default (accepts unsigned). Deps added ---------- - base64 = "0.22" for signature encoding Tests ----- New `canonical_payload_tests` module with 5 tests: - canonical_payload_keys_are_sorted — lexicographic order pinned - canonical_payload_is_byte_stable — repeat calls return identical bytes - signed_payload_roundtrip_verifies — full sign→verify happy path - tampered_timestamp_breaks_verification — replay protection works - tampered_capacity_breaks_verification — field integrity works Full hero_compute_server test count: 29 (13 identity + 5 canonical payload + 11 pre-existing cloud CRUD). All green. Explorer test (node_heartbeat_preserves_mycelium_ip_when_incoming_is_empty) from the earlier commit in this PR also still green.Phase 3 step 4 of the scaling architecture initiative. Adds ed25519 signature verification to ExplorerService.node_heartbeat. The explorer can now authenticate which node sent each heartbeat and reject spoofing attempts that used to be possible by just matching the hostname. Schema changes -------------- schemas/explorer/explorer.oschema: - node_heartbeat gets three new trailing params: node_account: str // 64-char hex = hex(pubkey) = NEAR implicit account timestamp: u64 // unix secs, signed to prevent simple replay signature: str // base64 ed25519 sig over sha256(canonical payload) - ExplorerNode gets a new `node_account: str @index` field so the explorer can look nodes up by on-chain identity, not just hostname. Generated files regenerate from build.rs (rpc_generated.rs, types_generated.rs, osis_server_generated.rs, openrpc.json, mod.rs). Handler logic (rpc.rs) ---------------------- A heartbeat is considered "signed" iff `signature` is non-empty. - Signed heartbeat: verify_heartbeat_signature is called. It rebuilds the canonical sorted-key JSON byte-identically to the server's canonical_signed_payload, sha256-hashes it, decodes the base64 sig and hex public key, and runs ed25519 verify. Also enforces a 120-second timestamp skew window against the explorer's clock as basic replay protection. - Legacy unsigned heartbeat: accepted iff env var ALLOW_LEGACY_HEARTBEATS is unset or set to "true"/"1"/"yes" (default true during the Phase 3 transition). Set it to "false" once all nodes have upgraded to fail closed on unsigned traffic. - Identity-rotation guard: if a hostname already has a recorded node_account and a new signed heartbeat claims a different one, reject. Prevents silent hostname takeover. - node_account is only persisted on signed heartbeats, so a stray legacy heartbeat cannot clobber a previously-recorded on-chain identity. Tests ----- New signature_tests module with 9 tests: - valid_signature_is_accepted (happy path) - tampered_capacity_is_rejected - tampered_hostname_is_rejected - signature_from_different_key_is_rejected - stale_timestamp_is_rejected (outside -120s window) - future_timestamp_is_rejected (outside +120s window) - invalid_base64_signature_is_rejected - non_hex_node_account_is_rejected - wrong_length_signature_is_rejected These call verify_heartbeat_signature directly rather than going through the RPC handler because ALLOW_LEGACY_HEARTBEATS is a process-wide env var and mutating it from parallel tests is fragile. The handler-level legacy path stays exercised by the existing heartbeat_preserve_tests::node_heartbeat_preserves_mycelium_ip_when_incoming_is_empty test, which now passes String::new() / 0 / String::new() for the three new params to exercise the grace-period path. Deps added (crate-level) ------------------------ - ed25519-dalek = "2" (verification side, no rand_core feature) - sha2 = "0.10" - hex = "0.4" - base64 = "0.22" - dev: rand 0.8 + ed25519-dalek with rand_core feature for test key gen Full hero_compute test count after this commit: 29 server + 15 explorer = 44 unit tests, all green.fix: preserve mycelium_ip on heartbeat when incoming value is emptyto fix: heartbeat mycelium_ip preserve + Phase 3 node crypto identity@mahmoud — review ping. This PR combines the small defensive heartbeat fix (preserve mycelium_ip on empty-string input) with Phase 3 Steps 1-4 of the marketplace scaling architecture initiative (mycelium_code/home#72): persistent ed25519 node identity, signed heartbeats, explorer-side signature verification, and an
ALLOW_LEGACY_HEARTBEATSgrace period.7 commits, 44 unit tests green, CI (Test + Format + Clippy) all passing on
54eb0ac. Phase 3 Step 5 (first-boot registration hook) is intentionally deferred to a stacked follow-up PR because it drags innear-jsonrpc-client/near-primitivesand cannot be E2E-verified until the hosting contract is deployed on devnet.Full scope breakdown in the PR description. Non-urgent — we have marketplace-side Phase 5 work lined up that does not block on this merge.
fix: heartbeat mycelium_ip preserve + Phase 3 node crypto identityto feat: heartbeat identity signing + mycelium IP preservation + dioxus native islandView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.