Repository navigation
fix(pool): make pool leave work and keep the Blox config in step with the chain - #251
Merged
Merged
Conversation
HandlePoolLeave always answered 500 and never cleaned up: it gated the cleanup on an eth_call of removeMemberPeerId with a placeholder selector (0x12345678) that reverts on both chains, using the caller's peer id. The device can't remove itself on-chain anyway — the app sends removeMemberPeerId from the member wallet. - HandlePoolLeave now checks the chain (strictly: RPC errors are errors) that the device's cluster peer is neither a member of nor waiting to join the pool, re-checking briefly for RPC lag, then clears pool and chain from the config and answers 202. 409 if still listed or the device is configured for another pool; 503 if the chain can't be read. Config is never cleared on a guess. - ReconcilePoolConfig (run at startup and on the 6 h loop) clears a configured pool the chain says the Blox has left — e.g. it left from the app while offline — except within 24 h of a join from this device (join-time marker), since joins write the config before the chain lists the Blox. - After the config is cleared, signal the existing fula restart marker so ipfs-cluster re-runs its init and stops following the old pool. - Real selectors for removeMemberPeerId and joinRequests; tests for the handler, reconcile and ABI helpers, plus build-tagged (livechain) read-only tests against the real SKALE and Base contracts. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QaKz9HeXaNBjrchbbGimFx
…gured - A leave for pool N on chain B must not clear a config of pool N on chain A (pool ids repeat across chains; some Bloxes are in pool 1 on both SKALE and Base): answer 409 "Chain mismatch". - With no pool configured (e.g. a reconcile already cleared it), answer 202 straight away, without reading the chain or restarting services. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QaKz9HeXaNBjrchbbGimFx
- Run the startup reconcile in the background 2 min after start (off the startup path, after the network settles), and never on pool hosts (--poolHost), whose own pool is not a chain-listed membership. - Only signal the fula services restart after the pool is confirmed gone from the config: if the config write failed, a restart would find the pool again and loop. Leave answers 500 in that case. - Don't write Blox fields from the background reconcile; the restart reloads them. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QaKz9HeXaNBjrchbbGimFx
- At most one automatic (reconcile) clear — and services restart — per 24 h, recorded in /internal/.tmp/pool_reconcile_cleared_at.tmp before clearing; if that record can't be written, nothing is cleared. - Act only on two definitive "not listed" answers 30 s apart, so a single lagging RPC node can't clear a member's pool. - clearPoolConfig checks the config write errors itself instead of trusting a re-read that falls back to "0" when config.yaml can't be read. - Don't write in-memory topic/chain names from the leave / reconcile goroutines (other request goroutines read them; the restart reloads them). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QaKz9HeXaNBjrchbbGimFx
So that the update changes nothing for devices as they run today: - ReconcilePoolConfig only clears a configured pool this firmware has itself seen the Blox be a member of (recorded in /internal/.tmp/pool_member_confirmed.tmp as "<chain>:<pool>") and the chain now says it isn't. A configured pool never seen as a membership — e.g. a join whose on-chain part never landed — is left alone, as before. Replaces the 24 h join-time grace marker. - Re-read the config just before clearing and abort if it changed (a join or rejoin landing during the 30 s check is not wiped). - Start the reconcile goroutine only after startup pool discovery, and serialise the config.yaml accessors in cmd/blox with a mutex, so concurrent read-modify-writes can't mix fields. - Kill switch: while /internal/.disable_pool_reconcile exists, reconcile does nothing. - Pool hosts (--poolHost) refuse pool leave requests (409) and never reconcile. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QaKz9HeXaNBjrchbbGimFx
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QaKz9HeXaNBjrchbbGimFx
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Why
Leaving a pool never worked on the Blox:
HandlePoolLeavegated its cleanup on aneth_callofremoveMemberPeerIdwith a placeholder selector (0x12345678) that reverts on both SKALE and Base, so every leave answered 500 andpoolName/chainNamewere never cleared. The device can't remove itself on-chain anyway — the app sendsremoveMemberPeerIdfrom the member wallet (fxblox-web 0.0.3).What changes
Leave handler (
fula-pool-leave): clears pool + chain fromconfig.yamlonly after a strict on-chain check (RPC errors are errors) that the device's cluster peer is neither a member of nor waiting to join the pool, then restarts the fula services (existing.command_restart_fula→docker compose restart) so ipfs-cluster stops following the old pool.--poolHost)Reconcile (background, after startup discovery, then every 6 h; never on pool hosts): records the pool the Blox is a member of, and later clears it only if the Blox was seen as a member by this firmware and the chain no longer lists it (e.g. it left from the app while offline). A configured pool never seen as a membership (e.g. a join whose on-chain part never landed) is left alone — so the update changes nothing for devices as they run today.
Safety rails: two definitive reads 30 s apart; config re-read before clearing; at most one reconcile clear (and restart) per 24 h, recorded before clearing (no record → no clear); restart only after the clear is verified; config.yaml accessors in
cmd/bloxserialised with a mutex; kill switch: while/internal/.disable_pool_reconcileexists (host:/home/pi/.internal/.disable_pool_reconcile) reconcile does nothing. Real selectors forremoveMemberPeerId(0x3d71233a) andjoinRequests(0xccc9fc03).Checks done
0so neither path fires again. Nothing here reboots the host (onlydocker compose restart), so the 5-boots factory reset can't be reached;go-fula.sh's OTA md5 marker logic is untouched.chainName, unconfirmed pools, pool hosts and offline/slow networks are all left alone (errors keep the config).blox,wap,initipfs,initipfsclusterbuild for linux/arm64 with Go 1.25 (the image's toolchain). Rollout is the existing path: fula-ota release buildsgo-fula:releasefrom go-fulamain→ Watchtower →go-fula.shone OTA reboot. No fula-ota change needed (.command_restart_fulaexists since fula-ota v3.1.6, OTA marker since v3.0.0).Tests: new unit tests for the handler, reconcile, loop guards and ABI helpers pass with
-race -shuffle=on; build-taggedlivechaintests (go test -tags livechain -run TestLive ./blockchain/) pass read-only against the real SKALE and Base contracts.CI note:
unitandAllare already red onmainbefore this PR —TestRealContractIntegrationandTestPoolDiscoveryWithMockServeralways fail,TestBlockchainHealthCheck/TestFxBlockchainCreationflake (port collisions), and 6 files already fail gofmt. This PR adds no new failures (same results onmainand the branch over 5 shuffled race runs; gofmt list unchanged).Known limits: a Blox whose owner leaves on-chain before the Blox has ever run this firmware is never confirmed as a member, so it isn't auto-cleared (as today). The mobile app sends the Blox leave before its on-chain tx, so mobile users get a 409 (previously 500) until the app reorders.
🤖 Generated with Claude Code
https://claude.ai/code/session_01QaKz9HeXaNBjrchbbGimFx