Skip to content

fix(token): vet test-token DARs on every LocalNet participant - #319

Merged
zheli merged 6 commits into
mainfrom
fix/token-create-dar-all-participants
Aug 19, 2026
Merged

fix(token): vet test-token DARs on every LocalNet participant#319
zheli merged 6 commits into
mainfrom
fix/token-create-dar-all-participants

Conversation

@zheli

@zheli zheli commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

token create only vetted the bundled test-token DARs on the create participant, so mint/transfer to a party on app-user failed. Create now fetches each DAR once, caches it by Splice commit, and uploads plus vets on sv, app-provider, and app-user. Fixes #318.

Changes

  • Fan ensureTokenDARs out to all three LocalNet participants; missing port or upload/vet failure on any role fails create
  • Cache fetched DARs under ~/.canton-devkit/localnet/.dar-cache/<commit>/ (shared across instances)
  • Print which roles were vetted; update token create help, docs/tokens.md, and docs/troubleshooting.md
  • Unit tests for all-three fan-out, missing-port failure, disk cache hit, and secondary-role failure

Test plan

  • make test (./internal/localnet/token/, ./internal/cli/localnet/token/, ./internal/cli/)
  • make lint
  • scripts/e2e/run-all.sh
  • On a running multi-participant LocalNet: token create, then token mint to an app-user party

Checklist

  • Tests pass locally
  • No coverage regression on touched code
  • Docs updated (and website/ synced if any mirrored docs/*.md changed)
  • CLI ↔ Web UI parity maintained (or follow-up issue + TODO(#issue) comment)

zheli added 5 commits August 18, 2026 23:32
token create only uploaded the bundle to the create participant, so mint
to a party on app-user failed. Fetch once, cache by Splice commit, and
upload plus vet on sv, app-provider, and app-user.
Comments added for cross-participant vetting restated mechanics and
ticket context; rewrite them as single causal sentences per house style.
Token subcommands migrated from positional args + --name to named
flags + --instance. All test cases updated:
- token create: --token-name -> --name, add --non-interactive, --issuer
- token mint: positional symbol/amount -> --instrument/--amount, add --to, --instance
- token transfer: positional -> --instrument/--amount/--from/--to/--instance, add --auto-accept
- token burn: positional -> --instrument/--amount/--from/--instance, add --yes
- token balance: positional symbol -> --instrument, --to -> --party, --name -> --instance
- env setup: remove stale WALLET_A/B capture (replaced by per-test party aliases)
- M3-TOK-010 regression script updated to match new flag shapes
When the receiver party lives on a different participant than the sender
(e.g. app-user vs app-provider), Canton rejects the accept submission with
NO_SYNCHRONIZER_ON_WHICH_ALL_SUBMITTERS_CAN_SUBMIT because the sender's
node has no CanActAs right for the receiver's party.

Add resolveAcceptConn to pick the correct LedgerConn for the accept step,
routing to the receiver's participant when the roles differ. Wire it into
runMintLive, runTransferLiveOnLedger auto-accept, and
runAcceptOnLedgerIfTestToken standalone accept.

Add receiverRole helper (alias.go) and test seams (dialSenderFn,
dialLedgerConcreteFn, findTokenRulesDisclosedFn, mintViaOfferMintFn) so
unit tests can assert on accept-side routing without a live gRPC server.
@zheli
zheli marked this pull request as ready for review August 19, 2026 09:12
…st step

PR #319 changes user-facing behaviour (token create now vets the bundled
test-token DARs on all three participants, prints the vetting line, caches
DARs, and fails when a role's participant port is missing), so the proposal
deviation log must record it per the tracking rule in AGENTS.md.

The M3 cross-platform meta-test tore down the LocalNet instance as part of
its own cleanup while sorting before M3-TOK-011, which both hid the ordering
constraint and tied cleanup to a test that may never run on failure. Renumber
it M3-TOK-999 so it always sorts last, and lift teardown into a standalone
section that an automated run must execute unconditionally.

Anchor the artifacts ignore rule to the repository root so nested directories
named artifacts are not ignored by accident.
@zheli
zheli merged commit b114693 into main Aug 19, 2026
25 checks passed
@zheli
zheli deleted the fix/token-create-dar-all-participants branch August 19, 2026 10:02
zheli added a commit that referenced this pull request Aug 19, 2026
* fix(token): vet test-token DARs on every LocalNet participant

token create only uploaded the bundle to the create participant, so mint
to a party on app-user failed. Fetch once, cache by Splice commit, and
upload plus vet on sv, app-provider, and app-user.

* Update .gitignore

* docs(token): tighten DAR fan-out comments to state why

Comments added for cross-participant vetting restated mechanics and
ticket context; rewrite them as single causal sentences per house style.

* docs(tests): update all M3-TOK command syntax to current flag shapes

Token subcommands migrated from positional args + --name to named
flags + --instance. All test cases updated:
- token create: --token-name -> --name, add --non-interactive, --issuer
- token mint: positional symbol/amount -> --instrument/--amount, add --to, --instance
- token transfer: positional -> --instrument/--amount/--from/--to/--instance, add --auto-accept
- token burn: positional -> --instrument/--amount/--from/--instance, add --yes
- token balance: positional symbol -> --instrument, --to -> --party, --name -> --instance
- env setup: remove stale WALLET_A/B capture (replaced by per-test party aliases)
- M3-TOK-010 regression script updated to match new flag shapes

* fix(token): dial receiver's participant for mint/transfer accept

When the receiver party lives on a different participant than the sender
(e.g. app-user vs app-provider), Canton rejects the accept submission with
NO_SYNCHRONIZER_ON_WHICH_ALL_SUBMITTERS_CAN_SUBMIT because the sender's
node has no CanActAs right for the receiver's party.

Add resolveAcceptConn to pick the correct LedgerConn for the accept step,
routing to the receiver's participant when the roles differ. Wire it into
runMintLive, runTransferLiveOnLedger auto-accept, and
runAcceptOnLedgerIfTestToken standalone accept.

Add receiverRole helper (alias.go) and test seams (dialSenderFn,
dialLedgerConcreteFn, findTokenRulesDisclosedFn, mintViaOfferMintFn) so
unit tests can assert on accept-side routing without a live gRPC server.

* docs: track token DAR fan-out deviation and make M3 teardown a non-test step

PR #319 changes user-facing behaviour (token create now vets the bundled
test-token DARs on all three participants, prints the vetting line, caches
DARs, and fails when a role's participant port is missing), so the proposal
deviation log must record it per the tracking rule in AGENTS.md.

The M3 cross-platform meta-test tore down the LocalNet instance as part of
its own cleanup while sorting before M3-TOK-011, which both hid the ordering
constraint and tied cleanup to a test that may never run on failure. Renumber
it M3-TOK-999 so it always sorts last, and lift teardown into a standalone
section that an automated run must execute unconditionally.

Anchor the artifacts ignore rule to the repository root so nested directories
named artifacts are not ignored by accident.
zheli added a commit that referenced this pull request Aug 19, 2026
…g auth, JSON parity) (#321)

* refactor(splice): add AllRoleNames as the single fan-out role source

`dar upload --all-participants` re-spelled the literal
{"sv","app-provider","app-user"} while the token DAR path derived the
same list from splice.AllRoles(). A comment asserted the two must match,
but nothing enforced it, so the two fan-out paths could silently drift in
which participants they target or in what order.

Both now derive from splice.AllRoleNames(), making the claim structural.

* refactor(token): probe DAR vetting via the admin API through shared darops

The token DAR fan-out carried its own dialling and vetting logic parallel
to internal/localnet/darops, and its probe was wrong in ways that could
report success while the participant could not host the package:

- It called the ledger API's ListKnownPackages, which reports upload, not
  vetting, so "Vetted ..." overstated what had been checked. It now uses
  the admin API's ListDars, the same probe darops already relies on.
- It matched packages by name alone, so the pinned
  splice-util-token-standard-wallet 1.1.0 was satisfied by an installed
  1.0.0 that lacks BatchingUtilityV2. Matching is now name and version.
- It issued one list call per package per role (21 per create). It now
  issues one per role before uploads and one after.
- A failing list RPC was indistinguishable from "package absent", causing
  redundant uploads and a misleading "still not vetted after upload".
  List errors now propagate with the failing role and host.
- An interrupted cache write could leave a truncated DAR that later runs
  would trust. Cache entries are now written to a temp file in the same
  directory and renamed into place.

Extracting EnsureVetted into darops lets the token path and `dar upload`
share one implementation instead of drifting.

* fix(token): keep the auth token on the accept-leg connection

Both auto-accept paths rebuilt a LedgerConn from the options to target
the receiver's participant, but the rebuilt value omitted Token. The
accept leg therefore dialled unauthenticated whenever the caller had
supplied a JWT, failing against an auth-on LocalNet.

Both blocks now pass the in-scope conn, which already carries Token.

* feat(token): expose vetted_roles on both token create JSON surfaces

Which participants a create vetted was only ever printed as prose, so
neither `token create --format json` nor POST /api/tokens could report
it. Scripts and the Web UI had no way to tell a fully vetted create from
a registry-only one.

Both surfaces now encode a shared types.TokenCreateResponse carrying
schema_version and vetted_roles, so they cannot drift. TokenRef stays
embedded rather than nested, keeping the instrument's fields where they
have always been on the wire. vetted_roles is omitted for a
registry-only create, which vets nothing.

* docs: record the corrected vetting claim and the vetted_roles field

AGENTS.md requires the proposal-deviation entry to track user-facing
behaviour. The existing `token create` entry described a ledger-port
failure mode that is now an admin-port one, and predates both the
vetted_roles JSON field and the atomic DAR cache write.

* fix(token): claim "known" where only ListKnownPackages was checked

resolvePackageID and discoverTokenSurfaces both call the ledger API's
ListKnownPackages, which proves a package was uploaded to the
participant. It does not prove the package is vetted -- vetting is a
topology fact, and darops.EnsureVetted reads it through the admin API.

Both functions and the four user-facing errors that inherit from them
nonetheless told the operator a package was or was not "vetted". That
sends someone debugging a routing failure to look at vetting state that
was never actually checked, which is the opposite of what the message
should do.

Reword the claims to match what the call proves, and record on
discoverTokenSurfaces which API to reach for when the vetting state
itself is what matters. No behaviour change: the same packages are
found by the same call.
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.

[Bug]: token create vets test-token DARs on one participant only

1 participant