canton: emitter-approval crank + token-bridge example#37
Conversation
…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>
|
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 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 ( |
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)EmitterRequest -> Emitterapproval flow. The operator is the sole controller ofEmitterRequest.ApproveEmitterand nothing on-ledger advances a pending request, so this backend polls activeEmitterRequestcontracts and exercises the choice.StateService.GetActiveContracts, writes viaCommandService.SubmitAndWaitForTransaction.ApproveEmitterchoice 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.CommandServiceprotos (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 sharedCantonClientinterface is unchanged.canton/examples/token-bridge— lock-and-attest example (CN Token Standard)mkInitialCoreStatetakesguardianGovernance+guardianObserver;ApproveEmitterreturnsContractId Emitterdirectly;WormholeMessagecarriesregistrar/owner/emitterIddirectly.Tests
crank_test.go,cantonacs_test.go,cantoncmd_test.go): poll loop, deterministic command-id derivation, policy gating, error classification.TestCantonCrankIntegration, build tagintegration): drives a live dpm sandbox and asserts approve-and-consume, command dedup, the consuming-choice backstop, and restart idempotency over a backlog.dpm build --all/dpm test --allpass;TestTokenBridge,TestCrankcover the example and approval flow.Notes / follow-ups
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.