Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,17 +464,17 @@ The table below is a curated subset. A complete, interactive API reference is av
| `/packages/vetted` | GET | Returns packages uploaded on this node |
| `/external-parties` | GET | Lists the external (co-validated) parties this node hosts |
| `/v0/tenant/prepare` | POST | Wallet-facing: builds an external party's onboarding topology and returns the hash to sign |
| `/v0/tenant/onboard` | POST | Wallet-facing: validates the wallet's signed topology, co-signs, and submits it |
| `/v0/tenant/execute` | POST | Wallet-facing: validates the wallet's signed topology, co-signs, and submits it |
| `/v0/tenant/{party}/status` | GET | Wallet-facing: reports whether this host has the party hosted yet |
| `/v0/tenant/{party}/state` | GET | Wallet-facing: the party's current serial, threshold and host count, so a caller can pin `base_serial` on the next write |
| `/v0/tenant/add-hosts/prepare` | POST | Wallet-facing: builds the serial-N+1 topology that adds hosts to an existing external party |
| `/v0/tenant/add-hosts/onboard` | POST | Wallet-facing: validates the wallet-signed add-hosts topology, co-signs, and submits it |
| `/v0/tenant/add-hosts/execute` | POST | Wallet-facing: validates the wallet-signed add-hosts topology, co-signs, and submits it |
| `/v0/tenant/{party}/acs/{target}` | GET | Wallet-facing: serves one block of the party's ACS scoped to a joining host (`?seq=`), for the wallet to relay. The host keeps the Canton export stream open between blocks |
| `/v0/tenant/add-hosts/import` | POST | Wallet-facing: feeds one relayed block straight into this host's open Canton import, clearing the onboarding marker when the final block lands |
| `/v0/tenant/threshold/prepare` | POST | Wallet-facing: builds a confirmation-threshold change |
| `/v0/tenant/threshold/onboard` | POST | Wallet-facing: submits the wallet-signed threshold change |
| `/v0/tenant/threshold/execute` | POST | Wallet-facing: submits the wallet-signed threshold change |
| `/v0/tenant/local-party/adopt-key/prepare` | POST | Wallet-facing: builds the conversion that gives a local party an owner-held signing key |
| `/v0/tenant/local-party/adopt-key/onboard` | POST | Wallet-facing: co-signs and submits the owner-signed conversion |
| `/v0/tenant/local-party/adopt-key/execute` | POST | Wallet-facing: co-signs and submits the owner-signed conversion |

The `/v0/tenant/*` endpoints are the tenant API. They authenticate with a
separate tenant API key rather than the operator JWT, and are driven by
Expand Down
29 changes: 22 additions & 7 deletions crates/common/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ pub struct TenantPrepareResponse {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantOnboardRequest {
pub struct TenantExecuteRequest {
pub party_hint: String,
/// The party's raw Ed25519 public key, base64-encoded.
pub public_key: String,
Expand All @@ -486,13 +486,13 @@ pub struct TenantOnboardRequest {
}

/// Response to a wallet onboarding request. Reports this host's view only: the
/// wallet calls `/onboard` on every host and aggregates. `Completed` means this
/// wallet calls `/execute` on every host and aggregates. `Completed` means this
/// host's authorized `PartyToParticipant` names it; `InProgress` means the
/// topology is still a proposal here (more hosts must sign).
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantOnboardResponse {
pub struct TenantExecuteResponse {
pub status: WorkflowProgress,
pub party_id: String,
}
Expand Down Expand Up @@ -567,7 +567,7 @@ pub struct TenantAddHostsPrepareResponse {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantAddHostsOnboardRequest {
pub struct TenantAddHostsExecuteRequest {
/// Full party id of the party gaining hosts.
pub party_id: String,
/// The serial the wallet pinned when it prepared. Re-checked against this
Expand All @@ -588,7 +588,7 @@ pub struct TenantAddHostsOnboardRequest {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantAddHostsOnboardResponse {
pub struct TenantAddHostsExecuteResponse {
pub status: WorkflowProgress,
pub party_id: String,
/// The serial this host now has for the party. Advances once the change is
Expand Down Expand Up @@ -718,7 +718,7 @@ pub struct TenantThresholdRequest {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantThresholdOnboardRequest {
pub struct TenantThresholdExecuteRequest {
pub party_id: String,
pub base_serial: u32,
pub topology_transactions: Vec<String>,
Expand Down Expand Up @@ -746,7 +746,7 @@ pub struct LocalPartyAdoptRequest {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct LocalPartyAdoptOnboardRequest {
pub struct LocalPartyAdoptExecuteRequest {
pub party_id: String,
pub base_serial: u32,
/// The adopted key, so the host can rebuild what the mapping must carry
Expand Down Expand Up @@ -931,11 +931,26 @@ pub struct ContractsInvitePayload {
/// Human-readable contract/package names (from `ContractDefinition.name`).
#[serde(default)]
pub package_names: Vec<String>,
/// Ordered creates committed to before acceptance, independent of ledger preparation.
#[serde(default)]
pub contract_intents: Vec<ContractDeploymentIntent>,
/// The coordinator's run instance name (see `OnboardingInvitePayload`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub workflow_instance: Option<String>,
}

/// The exact create a contracts invitation authorizes. The argument hash uses
/// DecMan's normalized Daml value encoding (record labels and type annotations omitted).
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct ContractDeploymentIntent {
pub package_id: String,
pub module_name: String,
pub entity_name: String,
pub argument_hash: String,
}

/// Response for pending invitations endpoint
#[derive(Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
Expand Down
2 changes: 2 additions & 0 deletions crates/common/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,8 @@ pub struct PendingInvitation {
/// so the peer card shows the same "Packages" row the coordinator shows.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub package_names: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub contract_intents: Vec<crate::api::ContractDeploymentIntent>,
/// The coordinator's run instance name from the invite payload. Echoed
/// back on decline so the coordinator only fails the matching run.
#[serde(default, skip_serializing_if = "Option::is_none")]
Expand Down
1 change: 1 addition & 0 deletions crates/decman-cli/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2855,6 +2855,7 @@ mod tests {
dec_party_id: None,
new_participant: None,
package_names: Vec::new(),
contract_intents: Vec::new(),
workflow_instance: None,
}),
];
Expand Down
6 changes: 3 additions & 3 deletions crates/decman-wallet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ code cannot generate a party key at all.

```rust
use common::canton_id::CantonId;
use decman_wallet::{ExternalKeyPair, TenantClient, WalletHost, onboard_co_validated, statuses};
use decman_wallet::{ExternalKeyPair, TenantClient, WalletHost, execute_co_validated, statuses};

// The hosting set: one DecMan endpoint + participant id per host.
let hosts = vec![
Expand All @@ -30,7 +30,7 @@ let hosts = vec![
let key = ExternalKeyPair::generate();

// Every host prepares, the wallet signs locally, and onboards on every host.
let party = onboard_co_validated(&hosts, &key, "alice", Some(2)).await?;
let party = execute_co_validated(&hosts, &key, "alice", Some(2)).await?;

// Authorization is not instant — poll until every host reports the party hosted.
let reports = statuses(&hosts, &party.party_id).await;
Expand All @@ -43,7 +43,7 @@ another. The wallet signs a hash it cannot itself recompute, so agreement betwee
hosts is what stands between it and a host that returns the hash of a mapping it
never showed — one honest host defeats a lying one. Canton keeps the topology a
proposal until the last host signs, so the party is only live once all of them
report it. `onboard_co_validated` records a failed host in its report rather than
report it. `execute_co_validated` records a failed host in its report rather than
aborting, and onboarding is idempotent, so stragglers can simply be retried.

The wallet signs one hash per topology transaction, each hash computed by Canton
Expand Down
32 changes: 16 additions & 16 deletions crates/decman-wallet/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ use base64::{Engine, engine::general_purpose::STANDARD};
use common::{
api::{
TenantAcsBlockResponse, TenantAcsImportRequest, TenantAcsImportResponse,
TenantAddHostsOnboardRequest, TenantAddHostsOnboardResponse, TenantAddHostsPrepareResponse,
TenantAddHostsRequest, TenantOnboardRequest, TenantOnboardResponse,
TenantAddHostsExecuteRequest, TenantAddHostsExecuteResponse, TenantAddHostsPrepareResponse,
TenantAddHostsRequest, TenantExecuteRequest, TenantExecuteResponse,
TenantPartyStateResponse, TenantPrepareRequest, TenantPrepareResponse,
TenantThresholdOnboardRequest, TenantThresholdRequest,
TenantThresholdExecuteRequest, TenantThresholdRequest,
},
canton_id::CantonId,
types::WorkflowProgress,
Expand Down Expand Up @@ -88,10 +88,10 @@ impl TenantClient {
self.post("/v0/tenant/prepare", req).await
}

/// `POST /v0/tenant/onboard` — submit the wallet-signed bundle to THIS host.
/// `POST /v0/tenant/execute` — submit the wallet-signed bundle to THIS host.
/// Idempotent, so a retry after a network blip is safe.
pub async fn onboard(&self, req: &TenantOnboardRequest) -> Result<TenantOnboardResponse> {
self.post("/v0/tenant/onboard", req).await
pub async fn execute(&self, req: &TenantExecuteRequest) -> Result<TenantExecuteResponse> {
self.post("/v0/tenant/execute", req).await
}

/// `GET /v0/tenant/{party}/status` — this host's view of the party.
Expand Down Expand Up @@ -129,13 +129,13 @@ impl TenantClient {
self.post("/v0/tenant/add-hosts/prepare", req).await
}

/// `POST /v0/tenant/add-hosts/onboard` — submit the signed add-hosts bundle
/// `POST /v0/tenant/add-hosts/execute` — submit the signed add-hosts bundle
/// to THIS host.
pub async fn add_hosts_onboard(
pub async fn add_hosts_execute(
&self,
req: &TenantAddHostsOnboardRequest,
) -> Result<TenantAddHostsOnboardResponse> {
self.post("/v0/tenant/add-hosts/onboard", req).await
req: &TenantAddHostsExecuteRequest,
) -> Result<TenantAddHostsExecuteResponse> {
self.post("/v0/tenant/add-hosts/execute", req).await
}

/// `GET /v0/tenant/{party}/acs/{target}?seq=` — one block of the party's ACS
Expand Down Expand Up @@ -176,12 +176,12 @@ impl TenantClient {
self.post("/v0/tenant/threshold/prepare", req).await
}

/// `POST /v0/tenant/threshold/onboard` — submit the signed threshold change.
pub async fn threshold_onboard(
/// `POST /v0/tenant/threshold/execute` — submit the signed threshold change.
pub async fn threshold_execute(
&self,
req: &TenantThresholdOnboardRequest,
) -> Result<TenantAddHostsOnboardResponse> {
self.post("/v0/tenant/threshold/onboard", req).await
req: &TenantThresholdExecuteRequest,
) -> Result<TenantAddHostsExecuteResponse> {
self.post("/v0/tenant/threshold/execute", req).await
}

/// Base64-decode a field the host sent us, tagging which field it was.
Expand Down
22 changes: 11 additions & 11 deletions crates/decman-wallet/src/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

use common::{
api::{
HostPermission, TenantAcsImportRequest, TenantAddHostsOnboardRequest,
TenantAddHostsRequest, TenantOnboardRequest, TenantPrepareRequest,
TenantThresholdOnboardRequest, TenantThresholdRequest,
HostPermission, TenantAcsImportRequest, TenantAddHostsExecuteRequest,
TenantAddHostsRequest, TenantExecuteRequest, TenantPrepareRequest,
TenantThresholdExecuteRequest, TenantThresholdRequest,
},
canton_id::CantonId,
types::WorkflowProgress,
Expand Down Expand Up @@ -131,7 +131,7 @@ fn progress_to_status(progress: WorkflowProgress) -> HostStatus {
/// prepared party id is not the one `key` derives — a mismatch there means the node
/// did not read the public key we sent, and signing would bind the wallet to the
/// wrong party.
pub async fn onboard_co_validated(
pub async fn execute_co_validated(
hosts: &[WalletHost],
key: &dyn Signer,
party_hint: &str,
Expand Down Expand Up @@ -235,7 +235,7 @@ pub async fn onboard_co_validated(
signatures.push(key.sign_b64_async(&hash).await?);
}

let onboard_request = TenantOnboardRequest {
let execute_request = TenantExecuteRequest {
party_hint: party_hint.to_string(),
public_key: public_key.clone(),
topology_transactions: prepared.topology_transactions.clone(),
Expand All @@ -245,7 +245,7 @@ pub async fn onboard_co_validated(

let mut reports = Vec::with_capacity(hosts.len());
for host in hosts {
let report = match host.client.onboard(&onboard_request).await {
let report = match host.client.execute(&execute_request).await {
Ok(resp) => {
tracing::info!(
host = host.client.base_url(),
Expand All @@ -256,7 +256,7 @@ pub async fn onboard_co_validated(
HostReport::ok(host, progress_to_status(resp.status))
}
Err(e) => {
tracing::warn!(host = host.client.base_url(), "onboard failed: {e}");
tracing::warn!(host = host.client.base_url(), "execute failed: {e}");
HostReport::failed(host, &e)
}
};
Expand Down Expand Up @@ -453,7 +453,7 @@ pub async fn add_hosts(
signatures.push(key.sign_b64_async(&hash).await?);
}

let onboard_request = TenantAddHostsOnboardRequest {
let execute_request = TenantAddHostsExecuteRequest {
party_id: party_id.to_string(),
base_serial,
topology_transactions: prepared.topology_transactions.clone(),
Expand All @@ -465,7 +465,7 @@ pub async fn add_hosts(
// participant, and the existing hosts are neither.
let mut reports = Vec::with_capacity(new_hosts.len());
for host in new_hosts {
let report = match host.client.add_hosts_onboard(&onboard_request).await {
let report = match host.client.add_hosts_execute(&execute_request).await {
Ok(resp) => {
tracing::info!(
host = host.client.base_url(),
Expand Down Expand Up @@ -692,7 +692,7 @@ pub async fn raise_threshold(
signatures.push(key.sign_b64_async(&hash).await?);
}

let onboard = TenantThresholdOnboardRequest {
let execute_request = TenantThresholdExecuteRequest {
party_id: party_id.to_string(),
base_serial,
topology_transactions: prepared.topology_transactions.clone(),
Expand All @@ -702,7 +702,7 @@ pub async fn raise_threshold(

let mut reports = Vec::with_capacity(hosts.len());
for host in hosts {
let report = match host.client.threshold_onboard(&onboard).await {
let report = match host.client.threshold_execute(&execute_request).await {
Ok(resp) => HostReport::ok(host, progress_to_status(resp.status)),
Err(e) => {
tracing::warn!(
Expand Down
6 changes: 3 additions & 3 deletions crates/decman-wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//! anywhere, and the node-side code cannot generate a party key at all.
//!
//! ```no_run
//! use decman_wallet::{ExternalKeyPair, TenantClient, WalletHost, onboard_co_validated};
//! use decman_wallet::{ExternalKeyPair, TenantClient, WalletHost, execute_co_validated};
//! use common::canton_id::CantonId;
//!
//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
Expand All @@ -33,7 +33,7 @@
//! let key = ExternalKeyPair::generate();
//!
//! // Prepare on one host, sign locally, onboard on every host.
//! let party = onboard_co_validated(&hosts, &key, "alice", None).await?;
//! let party = execute_co_validated(&hosts, &key, "alice", None).await?;
//! println!("party {} across {} hosts", party.party_id, party.hosts.len());
//! # Ok(())
//! # }
Expand All @@ -54,7 +54,7 @@ pub mod signer;
pub use client::{HostStatus, TenantClient};
pub use error::{Error, Result};
pub use flow::{
AddedHosts, HostReport, OnboardedParty, WalletHost, add_hosts, onboard_co_validated,
AddedHosts, HostReport, OnboardedParty, WalletHost, add_hosts, execute_co_validated,
raise_threshold, statuses,
};
pub use key::ExternalKeyPair;
Expand Down
Loading
Loading