Skip to content

Tenant API: the add-hosts prepare and onboard endpoints - #381

Open
schronck wants to merge 2 commits into
feat/external-party/add-hosts-topologyfrom
feat/api/tenant-add-hosts-endpoints
Open

Tenant API: the add-hosts prepare and onboard endpoints#381
schronck wants to merge 2 commits into
feat/external-party/add-hosts-topologyfrom
feat/api/tenant-add-hosts-endpoints

Conversation

@schronck

Copy link
Copy Markdown
Collaborator

Stacked on #379. Base is feat/external-party/add-hosts-topology, so review that first — the diff here is only this commit.

Item 3 of the scoping study's §06. #379 built the serial-N+1 topology write and its validator; nothing called them. These are the callers.

Endpoints

Endpoint Does
POST /v0/tenant/add-hosts/prepare Reads head state, builds the serial N+1 replace, returns it with the hashes to sign
POST /v0/tenant/add-hosts/onboard Validates the wallet-signed bundle, co-signs with this node's topology key, submits

Same shape as onboarding: every host prepares independently, the wallet compares the bytes before it signs, and each host submits only its own authorization. Canton accumulates the rest and promotes the mapping when the last one lands. No host relays to another, and a retry converges because re-submitting an identical transaction is a no-op.

Status codes worth arguing about

409 on a stale pin. Prepare reads head state before it builds. If the wallet's base_serial no longer matches, that is not a malfunction — the party moved between the wallet's read and this call — so it returns 409 with both serials and the wallet can re-read and retry. A 500 would read as "this host is broken" and send the wallet to the wrong remedy.

400 when validation refuses the bundle. validate_add_hosts_topology rejecting a submission means the caller's bytes failed against this host's own head-state read. That is the caller's problem, not the host's.

Both endpoints report only this host's view, matching /v0/tenant/onboard. Completed means this host's authorized mapping has already advanced past the base serial; InProgress means the change is still a proposal here.

New in add_hosts.rs

submit_add_hosts — the add-hosts counterpart to allocate_party. It re-reads head state, runs the validator, co-signs with an empty signed_by so the node picks its own key, and submits. Validation happens before this node's key touches the bytes, which is the whole point: the party already exists and already holds contracts, so a forged serial N+1 could evict its current hosts rather than merely create something unwanted.

Tests

Five unit tests: decode_all (including that one bad entry fails the whole batch, since a silently dropped entry would break the index alignment the signatures depend on), and wire-shape round-trips for both request DTOs — decman-wallet mirrors these types, so a renamed field would fail to deserialize on the other end.

The endpoint behavior itself needs a live Canton, so an integration-test phase is the honest coverage. That plus the wallet-side flow (§06 item 4) is the natural next PR.

cargo clippy --workspace --all-targets --all-features -- -D warnings is clean. Per the usual rule I have not run cargo test locally.

The serial-N+1 builder had no caller. These are it:
POST /v0/tenant/add-hosts/prepare returns the unsigned topology plus the
hashes to sign, and POST /v0/tenant/add-hosts/onboard co-signs the
wallet-signed bundle with this node's topology key and submits it.

Prepare reads head state before it builds, so a wallet whose pinned base
serial has gone stale gets a 409 it can act on — re-read and retry — rather
than a 500 that reads as a broken host. Onboard returns 400 rather than 500
when validation refuses the bundle, because that is the caller's bytes failing
against this host's own view, not this host malfunctioning.

Both report only this host's view. The wallet calls every host itself and
aggregates, exactly as onboarding already works.
@schronck schronck self-assigned this Aug 26, 2026
@schronck
schronck requested review from scolear and a lite review from Copilot August 26, 2026 09:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR wires the previously-added “add hosts to an existing external party” topology builder/validator into the Tenant API by adding prepare/onboard HTTP endpoints, associated request/response DTOs, and the workflow submission function that co-signs and submits the wallet-provided topology bundle.

Changes:

  • Added POST /v0/tenant/add-hosts/prepare and POST /v0/tenant/add-hosts/onboard handlers and registered them in the server.
  • Introduced wallet-facing DTOs for add-hosts prepare/onboard requests and responses (and re-exported them via server types).
  • Added submit_add_hosts workflow function to validate, co-sign, and submit the wallet-signed add-hosts topology bundle.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/decman/src/workflow/external_party/add_hosts.rs Adds submit_add_hosts for co-signing + submission of wallet-signed add-hosts topology.
crates/decman/src/server/types.rs Re-exports new Tenant add-hosts DTOs for handler use.
crates/decman/src/server/mod.rs Registers the new tenant add-hosts endpoints on the Actix server.
crates/decman/src/server/handlers/tenant.rs Implements the new /v0/tenant/add-hosts/* endpoints plus shared base64 decode helper/tests.
crates/decman/src/server/handlers/mod.rs Re-exports the new tenant add-hosts handlers.
crates/common/src/api.rs Defines new API DTOs for add-hosts prepare/onboard request/response payloads.
Suppressed comments (1)

crates/decman/src/server/handlers/tenant.rs:443

  • All submit_add_hosts(...) errors are mapped to HTTP 400, but submit_add_hosts can also fail due to internal RPC issues (signing/submission/topology reads) that should be 500 per the endpoint’s documented responses. This makes client retry/diagnostics harder.
        Err(e) => {
            tracing::error!("tenant add-hosts onboard: submission failed: {e:#}");
            return HttpResponse::BadRequest().json(ErrorResponse {
                error: format!("Failed to submit the add-hosts topology on this host: {e}"),
            });

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/decman/src/server/handlers/tenant.rs Outdated
Comment thread crates/decman/src/server/handlers/tenant.rs Outdated
Comment thread crates/decman/src/workflow/external_party/add_hosts.rs
The wire-shape test used a truncated namespace, which CantonId refuses, so
the unit-test job failed. Fixed with a full fingerprint.

The rest is Copilot's review, which was right. submit_add_hosts and
prepare_add_hosts both returned anyhow, so the handlers could not tell a
caller error from a Canton failure and answered 400 to everything — including
the stale-serial race that the docs promised a 409 for, and RPC failures that
are not the caller's fault at all.

AddHostsError names the four cases and the handlers map them: 404 unknown
party, 409 stale pin, 400 refused bundle, 500 Canton failure. Each is a
different remedy for the wallet, so collapsing them sent it to the wrong one.

Dropped the pre-read in the prepare handler: prepare_add_hosts reads head
state itself and now reports why it refused, so the second read only widened
the window in which the serial could move between check and build.

Also documented the 404 and 409 the handlers actually return.
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