Skip to content

feat(control-plane): read/write pallet-resource-market offers - #70

Merged
flo2517 merged 1 commit into
mainfrom
feat/issue-15-resource-market-bridge
Aug 6, 2026
Merged

feat(control-plane): read/write pallet-resource-market offers#70
flo2517 merged 1 commit into
mainfrom
feat/issue-15-resource-market-bridge

Conversation

@flo2517

@flo2517 flo2517 commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

Second slice of #15, on top of the delegated calls from #69.

internal/blockchainbridge/resourcemarket.go

AnnounceOfferFor/RemoveOfferFor submit announce_offer_for/remove_offer_for, mirroring EnsureActive's proven submission shape exactly (serialize nonce use under the same mutex, sudo-wrap, sign, submit). Not live-verified: the locally running dev chain predates this pallet change and I have no way to rebuild/redeploy it in this sandbox (no libclang for openinfra-node), so the encoding is built and unit-tested against the same primitives EnsureActive already uses in production, not against a live extrinsic acceptance. Flagging this explicitly rather than implying more confidence than I have.

FinalizedOffer/decodeResourceOffer read pallet-resource-market's Offers map (single-key Blake2_128Concat, same shape as the reputation/validator reads from #46#49) and decode cpu/ram/storage (fixed u32+u64+u64) plus capabilities (compact-length-prefixed bytes, the same shape decodeAccountIdVec already handles for the validator set). This is live-verifiedOffers storage is unchanged by #69, so I ran it against the running local dev chain during development: correct "no offer yet" for every registered provider, no decode errors.

ResourceOffer documents this bridge's own unit convention explicitly (CPU in millicores, matching workloadapi.CPUCoresToMillicores and the scheduler; RAM in MB, storage in GB, matching ResourceCapability on the wire) since the pallet's u32/u64 fields carry no unit themselves — issue #15 asks for units to be defined, not left implicit.

Tests (5 new)

Fixed-field + capabilities round trip across several shapes including empty capabilities · truncated and trailing-byte inputs both rejected (a decoder that silently ignores trailing bytes would also silently accept a corrupted encoding) · encodeBoundedBytes matches the existing compact decoder · storage key is deterministic and provider-distinguishing.

Verification

$ go build ./...
$ go vet ./...
$ gofmt -l .
$ go test ./...

Full suite green.

Still open in #15

A reconciler that actually calls Announce/RemoveOfferFor as provider capacity/status changes, the scheduler checking finalized on-chain offers before selecting a provider, and integer pricing (needs a proto change + consumer analysis, out of scope for a bridge-layer change).

🤖 Generated with Claude Code

Second slice of #15, on top of the delegated calls from #69.

internal/blockchainbridge/resourcemarket.go:

- AnnounceOfferFor/RemoveOfferFor submit announce_offer_for/
  remove_offer_for, mirroring EnsureActive's proven submission shape
  exactly (serialize nonce use under the same mutex, sudo-wrap, sign,
  submit). Not live-verified: the locally running dev chain predates
  this pallet change and I have no way to rebuild/redeploy it in this
  sandbox (no libclang for openinfra-node), so the encoding is built
  and unit-tested against the same primitives EnsureActive already
  uses in production, not against a live extrinsic acceptance.

- FinalizedOffer/decodeResourceOffer read pallet-resource-market's
  Offers map (single-key Blake2_128Concat, same shape as the
  reputation/validator reads from #46-#49) and decode cpu/ram/storage
  (fixed u32+u64+u64) plus capabilities (compact-length-prefixed
  bytes, the same shape decodeAccountIdVec already handles for the
  validator set). This is live-verified: Offers storage is unchanged
  by #69, so it ran against the running local dev chain during
  development -- correct "no offer yet" for every registered provider,
  no decode errors.

ResourceOffer documents this bridge's own unit convention explicitly
(CPU in millicores, matching workloadapi.CPUCoresToMillicores and the
scheduler; RAM in MB, storage in GB, matching ResourceCapability on
the wire) since the pallet's u32/u64 fields carry no unit themselves --
issue #15 asks for units to be defined, not left implicit.

Adds 5 tests: fixed-field + capabilities round trip across several
shapes including empty capabilities; truncated and trailing-byte
inputs both rejected (a decoder that silently ignores trailing bytes
would also silently accept a corrupted encoding); encodeBoundedBytes
matches the existing compact decoder; storage key is deterministic and
provider-distinguishing.

Verified (control-plane/): go build ./...; go vet ./...; gofmt -l .;
go test ./... (full suite green).

Still open in #15: a reconciler that actually calls Announce/
RemoveOfferFor as provider capacity/status changes, the scheduler
checking finalized on-chain offers before selecting a provider, and
integer pricing (needs a proto change + consumer analysis, out of
scope for a bridge-layer change).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@flo2517
flo2517 merged commit 97963c0 into main Aug 6, 2026
4 checks passed
@flo2517
flo2517 deleted the feat/issue-15-resource-market-bridge branch August 6, 2026 13:50
flo2517 added a commit that referenced this pull request Aug 6, 2026
…e capacity (#71)

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: FlorianJeandenans <florian.jeandenans@skin-soft.org>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants