feat(control-plane): reconcile pallet-resource-market offers with live capacity - #71
Merged
Merged
Conversation
…e capacity Third and final slice of #15, on top of the delegated calls (#69) and the bridge read/write layer (#70). internal/resourcemarket/reconciler.go: - ReconcileOnce lists the same live schedulable-provider view the scheduler ranks against, computes each provider's desired ResourceOffer from its declared *total* capacity (the ceiling the scheduler's atomic Postgres capacity check already uses -- not the fast-changing "available" figure, which stays off-chain in Redis by design), compares it to the chain's FinalizedOffer, and calls AnnounceOfferFor only when it actually differs. Providers that drop out of the schedulable set (deregistered, or heartbeat went stale) have their offer withdrawn via RemoveOfferFor. - Withdrawal tracking (the offering map) is in-memory, not persisted: after a Control Plane restart, a provider that vanished during the outage keeps a stale on-chain offer for up to one reconcile interval before it's noticed and removed. This is a deliberately bounded, self-healing gap, not a security control -- no scheduling decision consults on-chain offers yet, so a briefly-stale offer has no live consequence today. - clampToUint32 protects the pallet's u32 CPU field from a total large enough to overflow it, instead of silently wrapping. Wired into cmd/controlplane/main.go via a marketBridge adapter that combines *blockchainbridge.Registrar's write methods with *blockchainbridge.RPCClient's read methods into the resourcemarket.Market interface the reconciler depends on, run alongside the existing provider-join outbox reconciler. Adds 8 tests: announces a new provider, skips an already-correct offer, updates a changed offer, withdraws a provider that drops out of the schedulable set (and forgets it afterward), skips providers without a usable 32-byte key or capabilities, tolerates a directory read failure without panicking, keeps retrying a failed announce on the next pass, and clampToUint32's boundary cases. Verified (control-plane/): gofmt -l .; go build ./...; go vet ./...; go test ./... -- full suite green against merged main, including this package (0.027s). This closes out #15's three planned slices. Still explicitly out of scope, left for follow-up: the scheduler consulting finalized on-chain offers before selecting a provider, and integer pricing (needs a proto change + consumer analysis). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
Closes #15.
Third and final slice, on top of the delegated calls (#69, merged) and the bridge read/write layer (#70, merged).
What this adds
internal/resourcemarket.Reconciler:ResourceOfferfrom its declared total capacity (the ceiling the scheduler's atomic Postgres capacity check already uses — not the fast-changing available figure, which stays off-chain in Redis by design).FinalizedOfferand callsAnnounceOfferForonly when it actually differs.RemoveOfferFor) any provider that drops out of the schedulable set (deregistered, or heartbeat went stale).Withdrawal tracking is in-memory, not persisted: after a restart, a vanished provider's stale offer survives up to one reconcile interval before being noticed and removed. Documented as a deliberately bounded, self-healing gap, not a security control — no scheduling decision consults on-chain offers yet, so a briefly-stale offer has no live consequence today.
Wired into
cmd/controlplane/main.govia amarketBridgeadapter combining*blockchainbridge.Registrar's write methods with*blockchainbridge.RPCClient's read methods into theresourcemarket.Marketinterface, run alongside the existing provider-join outbox reconciler.Tests
8 new tests: announces a new provider, skips an already-correct offer, updates a changed offer, withdraws + forgets a vanished provider, skips providers without a usable key/capabilities, tolerates a directory failure without panicking, keeps retrying a failed announce,
clampToUint32boundaries.Verified
gofmt -l .;go build ./...;go vet ./...;go test ./...— full suite green against merged main.Still open (deliberately out of scope here)
🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com