Skip to content

canton: emitter-approval crank + token-bridge example#37

Closed
bengtlofgren wants to merge 4 commits into
integration/cantonfrom
feat/canton-emitter-approval-crank
Closed

canton: emitter-approval crank + token-bridge example#37
bengtlofgren wants to merge 4 commits into
integration/cantonfrom
feat/canton-emitter-approval-crank

Conversation

@bengtlofgren

Copy link
Copy Markdown

Summary

Adds an off-ledger emitter-approval crank for the Canton core, plus the token-bridge example it exercises. Rebased onto the latest integration/canton (the earlier guardianObserver commits on this branch are already upstream via #32 and were dropped during the rebase).

What's here

node/cantoncrank — emitter-approval crank (Canton 3.5.x)

  • Off-ledger service that fulfils the EmitterRequest -> Emitter approval flow. The operator is the sole controller of EmitterRequest.ApproveEmitter and nothing on-ledger advances a pending request, so this backend polls active EmitterRequest contracts and exercises the choice.
  • Follows current Canton 3.5.x guidance (a backend Ledger API v2 client, not Daml Triggers): reads via StateService.GetActiveContracts, writes via CommandService.SubmitAndWaitForTransaction.
  • Exactly-once rests on the consuming ApproveEmitter choice plus Canton conflict detection; deterministic command-id dedup (derived from the request contract id, with pinned act-as and user id) is defense-in-depth. Errors are classified into a benign/transient taxonomy keyed on Canton self-service error-id tokens.
  • Adds the CommandService protos (command_service, commands, reassignment_commands) vendored from canton-open-source 3.5.1 with regenerated gRPC stubs. The crank depends only on narrow Reader/Submitter interfaces; the shared CantonClient interface is unchanged.

canton/examples/token-bridge — lock-and-attest example (CN Token Standard)

Tests

  • Unit (crank_test.go, cantonacs_test.go, cantoncmd_test.go): poll loop, deterministic command-id derivation, policy gating, error classification.
  • Integration (TestCantonCrankIntegration, build tag integration): drives a live dpm sandbox and asserts approve-and-consume, command dedup, the consuming-choice backstop, and restart idempotency over a backlog.
  • Daml: dpm build --all / dpm test --all pass; TestTokenBridge, TestCrank cover the example and approval flow.

Notes / follow-ups

  • The default approval policy (ApproveAll) is sandbox-only. Production requires a real approval-policy authority (package pin + requester allowlist), participant authentication with a provisioned crank user, and supervisor/health/metrics integration before running in-node.

…erver core

Rebases the token-bridge example onto canton-guardian-observer (stacked, since
feat/canton-public-observer is closed). The example predates both #29 (registry-
keyed Emitter/EmitterRegistry, no more EmitterIdentity) and #32 (guardianObserver
replacing the generic public party), so this adapts rather than just replays:

- mkInitialCoreState now takes guardianGovernance + guardianObserver; setup also
  creates an EmitterRegistry via mkInitialEmitterRegistry.
- ApproveEmitter returns ContractId Emitter directly (no coreStateCid arg, no
  separate EmitterIdentity contract).
- WormholeMessage carries registrar/owner/emitterId directly instead of an
  identity contract-id; TestTokenBridge and the Go integration test assert on
  those fields and derive the address via deriveEmitterAddress, matching the
  pattern in watcher_integration_test.go.
- TokenBridge's public field/observer renamed to guardianObserver.
- daml.yaml bumped to sdk-version 3.5.1 / --target=2.3 to match core.

Verified: dpm build --all and dpm test --all pass (all 3 scripts); go vet -tags
integration ./pkg/watchers/canton/... is clean.
Add an off-ledger "crank" service that fulfils the EmitterRequest -> Emitter
approval flow. The operator is the sole controller of
EmitterRequest.ApproveEmitter and nothing on-ledger advances a pending request,
so this backend polls active EmitterRequest contracts and exercises the choice.

Follows current Canton 3.5.x guidance (a backend Ledger API v2 client, not Daml
Triggers): reads via StateService.GetActiveContracts, writes via
CommandService.SubmitAndWaitForTransaction. Exactly-once rests on the consuming
ApproveEmitter choice plus Canton conflict detection; command deduplication
(a deterministic command id derived from the request contract id, with pinned
act-as and user id) is defense-in-depth. Errors are classified into a
benign/transient taxonomy keyed on Canton self-service error-id tokens.

The write path adds the CommandService protos (command_service, commands,
reassignment_commands) vendored from canton-open-source 3.5.1 with regenerated
gRPC stubs. The crank depends only on narrow Reader/Submitter interfaces; the
shared CantonClient interface is left unchanged.

Tests: unit tests cover the poll loop, deterministic command-id derivation,
policy gating, and error classification. An integration test
(TestCantonCrankIntegration, build tag integration) drives a live dpm sandbox
and asserts all four critical paths: approve-and-consume, command dedup, the
consuming-choice backstop, and restart idempotency over a backlog.

The default approval policy (ApproveAll) is sandbox-only. Production requires a
real approval-policy authority (package pin plus requester allowlist),
participant authentication with a provisioned crank user, and supervisor/health/
metrics integration before running in-node.

Signed-off-by: Bengt Lofgren <bengtlofgren8@gmail.com>
@bengtlofgren

Copy link
Copy Markdown
Author

Closing: this PoC is superseded by the core's emitter-registration redesign, not adaptable to it.

The crank existed to service an operator-gated approval step: poll for pending EmitterRequest contracts and exercise the operator-controlled ApproveEmitter choice. Both the EmitterRequest template and ApproveEmitter choice have since been removed from integration/canton. Emitter registration is now the permissionless, single-transaction EmitterRegistry.RegisterEmitter flow (flexible controller: the requester brings its signature, the operator's is inherited from the disclosed registry) — documented in Wormhole/Core/State.daml verbatim as "no operator crank". A sweep of the current core found no operator-gated approval surface left for an off-ledger crank to drain (SubmitGovernanceVAA is the only controller operator choice, and it applies guardian-signed VAAs — not a pending-approvals queue). The permissionless path is already tested on-ledger (testRegistryAllocation, testRegistrationFee).

The token-bridge example bundled in this PR is tracked independently in #28.

Branch retained (not deleted) as an archive for the reusable Ledger-API write-path introduced here (cantonclient CommandService SubmitAndWaitForTransaction wrapper + ACS iterator + command protos), which is generic transport worth salvaging if a future off-ledger writer is needed. The read-side client is already upstream on integration/canton.

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.

1 participant