From 2c7ef9a0fcfcdb5b212bf201e5d2a0ef8597489e Mon Sep 17 00:00:00 2001 From: nicosampler Date: Mon, 8 Jun 2026 17:40:37 -0300 Subject: [PATCH 01/98] fix(wallet-service): require explicit backend token --- canton-barebones/wallet-service/src/config.ts | 23 +++---- .../wallet-service/test/smoke.test.ts | 62 +++++++------------ 2 files changed, 33 insertions(+), 52 deletions(-) diff --git a/canton-barebones/wallet-service/src/config.ts b/canton-barebones/wallet-service/src/config.ts index 7439f7f7..9879e0b3 100644 --- a/canton-barebones/wallet-service/src/config.ts +++ b/canton-barebones/wallet-service/src/config.ts @@ -1,7 +1,6 @@ -import { createCantonToken } from './canton-token.ts' import { isMockEnabled } from './mock.ts' -export type TokenSource = 'env' | 'mint' | 'none' +export type TokenSource = 'env' | 'none' export interface WalletServiceConfig { port: number @@ -40,7 +39,8 @@ const optionalNumber = (name: string, fallback: number): number => { return parsed } -const resolveToken = (backendUserId: string): { token?: string; source: TokenSource } => { +// Resolves the explicit runtime bearer token without exposing the local signing recipe to services. +const resolveToken = (): { token?: string; source: TokenSource } => { if (isMockEnabled()) { return { source: 'none' } } @@ -48,20 +48,15 @@ const resolveToken = (backendUserId: string): { token?: string; source: TokenSou if (explicit !== undefined) { return { token: explicit, source: 'env' } } - const audience = optional('CANTON_AUTH_AUDIENCE') - const secret = optional('CANTON_AUTH_SECRET') - if (audience !== undefined && secret !== undefined) { - return { - token: createCantonToken({ subject: backendUserId, audience, secret }), - source: 'mint', - } - } - return { source: 'none' } + throw new Error( + 'CANTON_BACKEND_TOKEN is required. Generate one with: npm run canton:token -- ledger-api-user', + ) } export const loadConfig = (): WalletServiceConfig => { - const backendUserId = optional('CANTON_ADMIN_USER_ID') ?? 'wallet-service' - const resolved = resolveToken(backendUserId) + const backendUserId = + optional('CANTON_AUTH_USER_ID') ?? optional('CANTON_ADMIN_USER_ID') ?? 'ledger-api-user' + const resolved = resolveToken() return { port: optionalNumber('WALLET_SERVICE_PORT', 3010), corsOrigins: ( diff --git a/canton-barebones/wallet-service/test/smoke.test.ts b/canton-barebones/wallet-service/test/smoke.test.ts index 35342a15..86f6cc2d 100644 --- a/canton-barebones/wallet-service/test/smoke.test.ts +++ b/canton-barebones/wallet-service/test/smoke.test.ts @@ -6,7 +6,7 @@ const CANTON_VARS = [ 'CANTON_BACKEND_TOKEN', 'CANTON_AUTH_AUDIENCE', 'CANTON_AUTH_SECRET', - 'CANTON_ADMIN_USER_ID', + 'CANTON_AUTH_USER_ID', 'WALLET_SERVICE_MOCK', ] as const @@ -37,21 +37,32 @@ describe('config loader', () => { restore(saved) }) - it('returns defaults when no env vars are set', () => { - const config = loadConfig() - assert.equal(config.port, 3010) - assert.deepEqual(config.corsOrigins, ['http://localhost:3011']) - assert.equal(config.network, 'canton:local') - assert.equal(config.provider.id, 'wallet-service') + it('fails clearly when real mode starts without CANTON_BACKEND_TOKEN', () => { + // Scenario: real wallet-service mode must not mint a bearer token from the + // local auth recipe. The operator should generate a token explicitly and + // paste it into CANTON_BACKEND_TOKEN so every runtime token is visible. + assert.throws( + () => loadConfig(), + /CANTON_BACKEND_TOKEN is required\. Generate one with: npm run canton:token -- ledger-api-user/, + ) }) - it('tokenSource is "none" when nothing is configured (non-mock)', () => { - const config = loadConfig() - assert.equal(config.canton.tokenSource, 'none') - assert.equal(config.canton.backendToken, undefined) + it('does not use CANTON_AUTH_* to mint a wallet-service token', () => { + // Scenario: CANTON_AUTH_* is only a token-generation recipe for scripts. + // The runtime service must still fail until CANTON_BACKEND_TOKEN is set. + process.env.CANTON_AUTH_AUDIENCE = 'https://canton.network.global' + process.env.CANTON_AUTH_SECRET = 'unsafe' + process.env.CANTON_AUTH_USER_ID = 'ledger-api-user' + + assert.throws( + () => loadConfig(), + /CANTON_BACKEND_TOKEN is required\. Generate one with: npm run canton:token -- ledger-api-user/, + ) }) it('tokenSource is "env" when CANTON_BACKEND_TOKEN is set', () => { + // Scenario: the explicit backend token is the only accepted real-mode + // credential source, and it is passed through unchanged to SDK calls. process.env.CANTON_BACKEND_TOKEN = 'explicit.jwt.value' process.env.CANTON_AUTH_AUDIENCE = 'https://canton-barebones.local' process.env.CANTON_AUTH_SECRET = 'unsafe' @@ -60,34 +71,9 @@ describe('config loader', () => { assert.equal(config.canton.backendToken, 'explicit.jwt.value') }) - it('tokenSource is "mint" when audience + secret are set but no explicit token', () => { - process.env.CANTON_AUTH_AUDIENCE = 'https://canton-barebones.local' - process.env.CANTON_AUTH_SECRET = 'unsafe' - const config = loadConfig() - assert.equal(config.canton.tokenSource, 'mint') - assert.equal( - config.canton.backendToken, - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3YWxsZXQtc2VydmljZSIsImF1ZCI6Imh0dHBzOi8vY2FudG9uLWJhcmVib25lcy5sb2NhbCJ9.-3Xq4rrhJliXWkrqPNXid5_YuuTk3E6EDtQYux-ULiI', - ) - }) - - it('honours CANTON_ADMIN_USER_ID as the JWT subject when minting', () => { - process.env.CANTON_AUTH_AUDIENCE = 'https://canton-barebones.local' - process.env.CANTON_AUTH_SECRET = 'unsafe' - process.env.CANTON_ADMIN_USER_ID = 'custom-subject' - const config = loadConfig() - assert.equal(config.canton.tokenSource, 'mint') - const seg = (config.canton.backendToken ?? '').split('.')[1] - const payload = JSON.parse( - Buffer.from( - `${seg}${'='.repeat((4 - (seg.length % 4)) % 4)}`.replace(/-/g, '+').replace(/_/g, '/'), - 'base64', - ).toString('utf8'), - ) - assert.equal(payload.sub, 'custom-subject') - }) - it('mock mode skips minting and leaves backendToken undefined', () => { + // Scenario: mock mode is used for wallet-only UI iteration and must not + // require any LocalNet token because all Canton calls are short-circuited. process.env.WALLET_SERVICE_MOCK = '1' process.env.CANTON_AUTH_AUDIENCE = 'https://canton-barebones.local' process.env.CANTON_AUTH_SECRET = 'unsafe' From 690f7ae2b92b087d9230a038699eb63a2cb56725 Mon Sep 17 00:00:00 2001 From: nicosampler Date: Tue, 9 Jun 2026 14:51:30 -0300 Subject: [PATCH 02/98] chore(canton): require explicit localnet tokens --- canton-barebones/scripts/mint-token.mjs | 21 +++++++--- canton-barebones/test/mint-token.test.mjs | 40 ++++++++++++++++--- canton-barebones/wallet-service/src/config.ts | 4 -- canton-barebones/wallet-service/src/rpc.ts | 1 - .../wallet-service/test/canton-token.test.ts | 19 +++++---- .../wallet-service/test/mock.test.ts | 1 - .../wallet-service/test/party.test.ts | 2 +- .../wallet-service/test/rpc.test.ts | 1 - .../wallet-service/test/smoke.test.ts | 10 ++--- 9 files changed, 66 insertions(+), 33 deletions(-) diff --git a/canton-barebones/scripts/mint-token.mjs b/canton-barebones/scripts/mint-token.mjs index aaf83109..deab5dfb 100755 --- a/canton-barebones/scripts/mint-token.mjs +++ b/canton-barebones/scripts/mint-token.mjs @@ -7,9 +7,11 @@ import { fileURLToPath } from 'node:url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const root = path.resolve(__dirname, '..') +// Encodes JWT segments in the URL-safe base64 variant required by bearer tokens. const b64url = (input) => Buffer.from(input).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_') +// Reads local .env values so token generation follows the same config as the stack. const parseEnvFile = (filePath) => { if (!fs.existsSync(filePath)) { return {} @@ -55,18 +57,25 @@ export const createCantonToken = ({ subject, audience, secret }) => { return `${encoded}.${b64url(signature)}` } +// Prints a LocalNet token and the two places it can be copied for development. const main = () => { const env = { ...parseEnvFile(path.join(root, '.env')), ...process.env, } - const subject = process.argv[2] ?? env.CANTON_ADMIN_USER_ID + const subject = process.argv[2] ?? 'ledger-api-user' + const token = createCantonToken({ + subject, + audience: env.CANTON_AUTH_AUDIENCE, + secret: env.CANTON_AUTH_SECRET, + }) + process.stdout.write(`${token}\n\n`) + process.stdout.write('For wallet-service:\n') + process.stdout.write(` CANTON_BACKEND_TOKEN=${token}\n\n`) + process.stdout.write('For Carpincho LocalNet settings:\n') + process.stdout.write(' Use this token as the LocalNet bearer token.\n') process.stdout.write( - `${createCantonToken({ - subject, - audience: env.CANTON_AUTH_AUDIENCE, - secret: env.CANTON_AUTH_SECRET, - })}\n`, + ' You may reuse it for local dev or generate another token with this script.\n', ) } diff --git a/canton-barebones/test/mint-token.test.mjs b/canton-barebones/test/mint-token.test.mjs index ad3ff111..d3d0a65b 100644 --- a/canton-barebones/test/mint-token.test.mjs +++ b/canton-barebones/test/mint-token.test.mjs @@ -1,33 +1,61 @@ import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import path from 'node:path' import { describe, it } from 'node:test' +import { fileURLToPath } from 'node:url' import { createCantonToken } from '../scripts/mint-token.mjs' +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const scriptPath = path.resolve(__dirname, '../scripts/mint-token.mjs') + const b64urlDecode = (value) => { const padded = `${value}${'='.repeat((4 - (value.length % 4)) % 4)}` return Buffer.from(padded.replace(/-/g, '+').replace(/_/g, '/'), 'base64') } describe('Canton token generation', () => { - it('creates the HS256 JWT Canton accepts for local auth', () => { + it('creates the HS256 JWT Splice LocalNet accepts for local auth', () => { + // Scenario: Splice LocalNet services share the unsafe local JWT recipe. + // The generated token is what operators paste into wallet-service or + // Carpincho dev settings, while the signing secret stays in .env. const token = createCantonToken({ - subject: 'wallet-service', - audience: 'https://canton-barebones.local', + subject: 'ledger-api-user', + audience: 'https://canton.network.global', secret: 'unsafe', }) const [header, payload, signature] = token.split('.') assert.equal( token, - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3YWxsZXQtc2VydmljZSIsImF1ZCI6Imh0dHBzOi8vY2FudG9uLWJhcmVib25lcy5sb2NhbCJ9.-3Xq4rrhJliXWkrqPNXid5_YuuTk3E6EDtQYux-ULiI', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZWRnZXItYXBpLXVzZXIiLCJhdWQiOiJodHRwczovL2NhbnRvbi5uZXR3b3JrLmdsb2JhbCJ9.G9aLv-IF5X0WmIkbR10f48i-7it5LlgwpJEZ4Ce2Y-E', ) assert.deepEqual(JSON.parse(b64urlDecode(header).toString('utf8')), { alg: 'HS256', typ: 'JWT', }) assert.deepEqual(JSON.parse(b64urlDecode(payload).toString('utf8')), { - sub: 'wallet-service', - aud: 'https://canton-barebones.local', + sub: 'ledger-api-user', + aud: 'https://canton.network.global', }) assert.equal(signature.length, 43) }) + + it('prints the default-subject token and the LocalNet copy-paste instructions', () => { + // Scenario: the script is the single supported way to derive dev JWTs from + // the local signing recipe; it must tell operators where to use the + // generated token without mutating .env or leaking the signing secret. + const output = execFileSync(process.execPath, [scriptPath], { + cwd: path.resolve(__dirname, '..'), + env: { + ...process.env, + CANTON_AUTH_AUDIENCE: 'https://canton.network.global', + CANTON_AUTH_SECRET: 'unsafe', + }, + encoding: 'utf8', + }) + + assert.match(output, /eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/) + assert.match(output, /CANTON_BACKEND_TOKEN=/) + assert.match(output, /Carpincho LocalNet settings/) + }) }) diff --git a/canton-barebones/wallet-service/src/config.ts b/canton-barebones/wallet-service/src/config.ts index 9879e0b3..4df4fcfe 100644 --- a/canton-barebones/wallet-service/src/config.ts +++ b/canton-barebones/wallet-service/src/config.ts @@ -16,7 +16,6 @@ export interface WalletServiceConfig { jsonApiUrl: string ledgerApiUrl: string adminApiUrl: string - backendUserId: string backendToken?: string tokenSource: TokenSource } @@ -54,8 +53,6 @@ const resolveToken = (): { token?: string; source: TokenSource } => { } export const loadConfig = (): WalletServiceConfig => { - const backendUserId = - optional('CANTON_AUTH_USER_ID') ?? optional('CANTON_ADMIN_USER_ID') ?? 'ledger-api-user' const resolved = resolveToken() return { port: optionalNumber('WALLET_SERVICE_PORT', 3010), @@ -78,7 +75,6 @@ export const loadConfig = (): WalletServiceConfig => { jsonApiUrl: optional('CANTON_JSON_API_URL') ?? 'http://localhost:3013', ledgerApiUrl: optional('CANTON_LEDGER_API_URL') ?? 'grpc://localhost:3014', adminApiUrl: optional('CANTON_ADMIN_API_URL') ?? 'grpc://localhost:3015', - backendUserId, backendToken: resolved.token, tokenSource: resolved.source, }, diff --git a/canton-barebones/wallet-service/src/rpc.ts b/canton-barebones/wallet-service/src/rpc.ts index 5aaa2159..fab47452 100644 --- a/canton-barebones/wallet-service/src/rpc.ts +++ b/canton-barebones/wallet-service/src/rpc.ts @@ -428,7 +428,6 @@ export const createRpc = (config: WalletServiceConfig): Rpc => { jsonApiUrl: config.canton.jsonApiUrl, ledgerApiUrl: config.canton.ledgerApiUrl, adminApiUrl: config.canton.adminApiUrl, - backendUserId: config.canton.backendUserId, hasBackendToken: config.canton.backendToken !== undefined, }, }) diff --git a/canton-barebones/wallet-service/test/canton-token.test.ts b/canton-barebones/wallet-service/test/canton-token.test.ts index 2c4067b2..85fc5950 100644 --- a/canton-barebones/wallet-service/test/canton-token.test.ts +++ b/canton-barebones/wallet-service/test/canton-token.test.ts @@ -9,22 +9,27 @@ const b64urlDecode = (value: string): Buffer => { describe('createCantonToken', () => { it('produces the same HS256 token as canton-barebones/scripts/mint-token.mjs for the pinned inputs', () => { + // Scenario: wallet-service and the top-level token script must agree on + // the exact LocalNet JWT shape so generated tokens work across app-user + // Ledger, Validator, Scan, and Registry endpoints. const token = createCantonToken({ - subject: 'wallet-service', - audience: 'https://canton-barebones.local', + subject: 'ledger-api-user', + audience: 'https://canton.network.global', secret: 'unsafe', }) assert.equal( token, - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3YWxsZXQtc2VydmljZSIsImF1ZCI6Imh0dHBzOi8vY2FudG9uLWJhcmVib25lcy5sb2NhbCJ9.-3Xq4rrhJliXWkrqPNXid5_YuuTk3E6EDtQYux-ULiI', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZWRnZXItYXBpLXVzZXIiLCJhdWQiOiJodHRwczovL2NhbnRvbi5uZXR3b3JrLmdsb2JhbCJ9.G9aLv-IF5X0WmIkbR10f48i-7it5LlgwpJEZ4Ce2Y-E', ) }) it('decodes to the canonical header and payload', () => { + // Scenario: the generated bearer token must remain a simple HS256 JWT + // with only the subject and audience claims expected by LocalNet dev auth. const token = createCantonToken({ - subject: 'wallet-service', - audience: 'https://canton-barebones.local', + subject: 'ledger-api-user', + audience: 'https://canton.network.global', secret: 'unsafe', }) const [header, payload, signature] = token.split('.') @@ -34,8 +39,8 @@ describe('createCantonToken', () => { typ: 'JWT', }) assert.deepEqual(JSON.parse(b64urlDecode(payload).toString('utf8')), { - sub: 'wallet-service', - aud: 'https://canton-barebones.local', + sub: 'ledger-api-user', + aud: 'https://canton.network.global', }) assert.equal(signature.length, 43) }) diff --git a/canton-barebones/wallet-service/test/mock.test.ts b/canton-barebones/wallet-service/test/mock.test.ts index c68d37a3..16dbdb80 100644 --- a/canton-barebones/wallet-service/test/mock.test.ts +++ b/canton-barebones/wallet-service/test/mock.test.ts @@ -17,7 +17,6 @@ const baseConfig = () => ({ jsonApiUrl: 'http://localhost:3013', ledgerApiUrl: 'grpc://localhost:3014', adminApiUrl: 'grpc://localhost:3015', - backendUserId: 'wallet-service', backendToken: undefined as string | undefined, tokenSource: 'none' as const, }, diff --git a/canton-barebones/wallet-service/test/party.test.ts b/canton-barebones/wallet-service/test/party.test.ts index b2fa4068..1f66f45f 100644 --- a/canton-barebones/wallet-service/test/party.test.ts +++ b/canton-barebones/wallet-service/test/party.test.ts @@ -12,8 +12,8 @@ const stubConfig = { jsonApiUrl: '', ledgerApiUrl: '', adminApiUrl: '', - backendUserId: '', backendToken: undefined as string | undefined, + tokenSource: 'none' as const, }, } diff --git a/canton-barebones/wallet-service/test/rpc.test.ts b/canton-barebones/wallet-service/test/rpc.test.ts index 1096fb11..10230878 100644 --- a/canton-barebones/wallet-service/test/rpc.test.ts +++ b/canton-barebones/wallet-service/test/rpc.test.ts @@ -17,7 +17,6 @@ const baseConfig = () => ({ jsonApiUrl: 'http://localhost:3013', ledgerApiUrl: 'grpc://localhost:3014', adminApiUrl: 'grpc://localhost:3015', - backendUserId: 'wallet-service', backendToken: undefined as string | undefined, tokenSource: 'none' as const, }, diff --git a/canton-barebones/wallet-service/test/smoke.test.ts b/canton-barebones/wallet-service/test/smoke.test.ts index 86f6cc2d..48fda786 100644 --- a/canton-barebones/wallet-service/test/smoke.test.ts +++ b/canton-barebones/wallet-service/test/smoke.test.ts @@ -6,7 +6,6 @@ const CANTON_VARS = [ 'CANTON_BACKEND_TOKEN', 'CANTON_AUTH_AUDIENCE', 'CANTON_AUTH_SECRET', - 'CANTON_AUTH_USER_ID', 'WALLET_SERVICE_MOCK', ] as const @@ -47,12 +46,11 @@ describe('config loader', () => { ) }) - it('does not use CANTON_AUTH_* to mint a wallet-service token', () => { - // Scenario: CANTON_AUTH_* is only a token-generation recipe for scripts. + it('does not use the local signing recipe to mint a wallet-service token', () => { + // Scenario: the local signing recipe is only for scripts. // The runtime service must still fail until CANTON_BACKEND_TOKEN is set. process.env.CANTON_AUTH_AUDIENCE = 'https://canton.network.global' process.env.CANTON_AUTH_SECRET = 'unsafe' - process.env.CANTON_AUTH_USER_ID = 'ledger-api-user' assert.throws( () => loadConfig(), @@ -64,7 +62,7 @@ describe('config loader', () => { // Scenario: the explicit backend token is the only accepted real-mode // credential source, and it is passed through unchanged to SDK calls. process.env.CANTON_BACKEND_TOKEN = 'explicit.jwt.value' - process.env.CANTON_AUTH_AUDIENCE = 'https://canton-barebones.local' + process.env.CANTON_AUTH_AUDIENCE = 'https://canton.network.global' process.env.CANTON_AUTH_SECRET = 'unsafe' const config = loadConfig() assert.equal(config.canton.tokenSource, 'env') @@ -75,7 +73,7 @@ describe('config loader', () => { // Scenario: mock mode is used for wallet-only UI iteration and must not // require any LocalNet token because all Canton calls are short-circuited. process.env.WALLET_SERVICE_MOCK = '1' - process.env.CANTON_AUTH_AUDIENCE = 'https://canton-barebones.local' + process.env.CANTON_AUTH_AUDIENCE = 'https://canton.network.global' process.env.CANTON_AUTH_SECRET = 'unsafe' const config = loadConfig() assert.equal(config.canton.tokenSource, 'none') From 24ea3a732c131fac5fa7870a1102c16dc63333ec Mon Sep 17 00:00:00 2001 From: nicosampler Date: Tue, 9 Jun 2026 14:51:46 -0300 Subject: [PATCH 03/98] feat(canton): run barebones on splice localnet --- canton-barebones/.env.example | 46 +++--- canton-barebones/config/canton/app.conf | 96 ------------ .../config/canton/bootstrap.canton | 15 -- canton-barebones/config/postgres/init.sql | 3 - .../config/splice/localnet-overrides.yaml | 4 + .../nginx-app-provider-disabled.conf.template | 1 + canton-barebones/docker-compose.yaml | 97 ++---------- canton-barebones/package.json | 6 +- canton-barebones/scripts/deploy-dar.sh | 54 +++++-- canton-barebones/scripts/down.sh | 18 +++ canton-barebones/scripts/health-check.sh | 91 +++++------ canton-barebones/scripts/splice-common.sh | 141 ++++++++++++++++++ canton-barebones/scripts/up.sh | 42 ++++++ canton-barebones/test/splice-common.test.mjs | 110 ++++++++++++++ 14 files changed, 435 insertions(+), 289 deletions(-) delete mode 100644 canton-barebones/config/canton/app.conf delete mode 100644 canton-barebones/config/canton/bootstrap.canton delete mode 100644 canton-barebones/config/postgres/init.sql create mode 100644 canton-barebones/config/splice/localnet-overrides.yaml create mode 100644 canton-barebones/config/splice/nginx-app-provider-disabled.conf.template create mode 100755 canton-barebones/scripts/down.sh create mode 100755 canton-barebones/scripts/splice-common.sh create mode 100755 canton-barebones/scripts/up.sh create mode 100644 canton-barebones/test/splice-common.test.mjs diff --git a/canton-barebones/.env.example b/canton-barebones/.env.example index c39a799a..6d364944 100644 --- a/canton-barebones/.env.example +++ b/canton-barebones/.env.example @@ -1,39 +1,31 @@ -# Canton barebones dev values. Copy to .env and edit if ports conflict. +# Canton dApp Booster local Splice stack. Copy to .env before starting. COMPOSE_PROJECT_NAME=canton-barebones -# Images -CANTON_IMAGE=ghcr.io/digital-asset/decentralized-canton-sync/docker/canton:0.5.3 -POSTGRES_IMAGE=postgres:14 +# Official Splice LocalNet bundle. This stack activates sv + app-user UI +# profiles and disables app-provider nginx routes. The shared backend +# containers still expose app-provider ports because the bundle bakes them in. +SPLICE_IMAGE_TAG=0.5.18 +SPLICE_BUNDLE_DIR=${HOME}/.canton-dappbooster/splice-localnet +# Defaults to COMPOSE_PROJECT_NAME so Docker groups Splice and wallet-service together. +# Override only when intentionally running Splice as a separate compose project. +SPLICE_COMPOSE_PROJECT_NAME=canton-barebones -# Postgres -POSTGRES_HOST_PORT=3018 -POSTGRES_USER=canton -POSTGRES_PASSWORD=canton -POSTGRES_DB=postgres - -# Canton public ports on the host -CANTON_LEDGER_API_PORT=3014 -CANTON_ADMIN_API_PORT=3015 -CANTON_JSON_API_PORT=3013 -CANTON_SEQUENCER_PUBLIC_PORT=3017 -CANTON_HTTP_HEALTH_PORT=3016 - -# Canton internal auth for local/dev. -# The participant accepts HS256 JWTs with this audience/secret. -CANTON_AUTH_AUDIENCE=https://canton-barebones.local +# LocalNet dev JWT recipe. These values are used only by scripts/mint-token.mjs. +# Do not copy CANTON_AUTH_SECRET into Carpincho. +CANTON_AUTH_AUDIENCE=https://canton.network.global CANTON_AUTH_SECRET=unsafe -CANTON_ADMIN_USER_ID=wallet-service -# Convenience token generated from the values above: -# sub = CANTON_ADMIN_USER_ID, aud = CANTON_AUTH_AUDIENCE, alg = HS256, secret = CANTON_AUTH_SECRET. -# Regenerate with: scripts/mint-token.sh wallet-service -CANTON_ADMIN_TOKEN=mock-token-replace-with-scripts-mint-token-output +# Required for wallet-service real mode. +# Generate with: +# npm run canton:token -- ledger-api-user +# Then paste the generated CANTON_BACKEND_TOKEN line here. +CANTON_BACKEND_TOKEN= -# Wallet-service (started by docker compose alongside Canton) +# Wallet-service. It is started by npm run canton:up after Splice LocalNet. WALLET_SERVICE_PORT=3010 WALLET_SERVICE_CORS_ORIGINS=http://localhost:3011 -NETWORK=canton:local +NETWORK=canton:localnet WALLET_PROVIDER_ID=wallet-service WALLET_PROVIDER_VERSION=0.1.0 WALLET_PROVIDER_URL=http://localhost:3010 diff --git a/canton-barebones/config/canton/app.conf b/canton-barebones/config/canton/app.conf deleted file mode 100644 index c157a516..00000000 --- a/canton-barebones/config/canton/app.conf +++ /dev/null @@ -1,96 +0,0 @@ -_storage { - type = postgres - config { - dataSourceClass = "org.postgresql.ds.PGSimpleDataSource" - properties = { - serverName = ${DB_SERVER} - portNumber = 5432 - user = ${DB_USER} - password = ${DB_PASSWORD} - tcpKeepAlive = true - } - } - parameters { - max-connections = 16 - migrate-and-start = true - } -} - -canton { - features { - enable-preview-commands = yes - enable-testing-commands = yes - } - - parameters { - non-standard-config = yes - } - - sequencers { - sequencer1 { - storage = ${_storage} { - config.properties.databaseName = canton_sequencer - } - public-api { - address = "0.0.0.0" - port = 5001 - } - admin-api { - address = "0.0.0.0" - port = 5002 - } - sequencer.type = BFT - } - } - - mediators { - mediator1 { - storage = ${_storage} { - config.properties.databaseName = canton_mediator - } - admin-api { - address = "0.0.0.0" - port = 5202 - } - } - } - - participants { - participant1 { - storage = ${_storage} { - config.properties.databaseName = canton_participant - } - - admin-api { - address = "0.0.0.0" - port = 5012 - } - - ledger-api { - address = "0.0.0.0" - port = 5011 - max-token-lifetime = Inf - admin-token-config.admin-claim = true - auth-services = [{ - type = unsafe-jwt-hmac-256 - target-audience = ${CANTON_AUTH_AUDIENCE} - secret = ${CANTON_AUTH_SECRET} - }] - user-management-service.additional-admin-user-id = ${CANTON_ADMIN_USER_ID} - interactive-submission-service { - enable-verbose-hashing = true - } - } - - http-ledger-api { - address = "0.0.0.0" - port = 7575 - } - - monitoring.http-health-server { - address = "0.0.0.0" - port = 7000 - } - } - } -} diff --git a/canton-barebones/config/canton/bootstrap.canton b/canton-barebones/config/canton/bootstrap.canton deleted file mode 100644 index ec778fa8..00000000 --- a/canton-barebones/config/canton/bootstrap.canton +++ /dev/null @@ -1,15 +0,0 @@ -try { - bootstrap.synchronizer_local() -} catch { - case _: Throwable => println("local synchronizer already initialized; skipping bootstrap") -} - -try { - participant1.synchronizers.connect_local(sequencer1, alias = "local") -} catch { - case _: Throwable => println("participant already connected to local synchronizer; skipping connect") -} - -utils.retry_until_true { - participant1.synchronizers.active("local") -} diff --git a/canton-barebones/config/postgres/init.sql b/canton-barebones/config/postgres/init.sql deleted file mode 100644 index 56cc4b1a..00000000 --- a/canton-barebones/config/postgres/init.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE DATABASE canton_participant; -CREATE DATABASE canton_sequencer; -CREATE DATABASE canton_mediator; diff --git a/canton-barebones/config/splice/localnet-overrides.yaml b/canton-barebones/config/splice/localnet-overrides.yaml new file mode 100644 index 00000000..0909026c --- /dev/null +++ b/canton-barebones/config/splice/localnet-overrides.yaml @@ -0,0 +1,4 @@ +services: + nginx: + volumes: + - ${CANTON_BAREBONES_DIR}/config/splice/nginx-app-provider-disabled.conf.template:/etc/nginx/templates/app-provider.conf.template:ro diff --git a/canton-barebones/config/splice/nginx-app-provider-disabled.conf.template b/canton-barebones/config/splice/nginx-app-provider-disabled.conf.template new file mode 100644 index 00000000..4d437aa3 --- /dev/null +++ b/canton-barebones/config/splice/nginx-app-provider-disabled.conf.template @@ -0,0 +1 @@ +# app-provider UI containers are not part of this local stack. diff --git a/canton-barebones/docker-compose.yaml b/canton-barebones/docker-compose.yaml index a233c3ae..8db6f042 100644 --- a/canton-barebones/docker-compose.yaml +++ b/canton-barebones/docker-compose.yaml @@ -1,98 +1,23 @@ services: - postgres: - image: "${POSTGRES_IMAGE}" - container_name: canton-barebones-postgres - environment: - POSTGRES_USER: "${POSTGRES_USER}" - POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" - POSTGRES_DB: "${POSTGRES_DB}" - ports: - - "${POSTGRES_HOST_PORT}:5432" - volumes: - - postgres-data:/var/lib/postgresql/data - - ./config/postgres/init.sql:/docker-entrypoint-initdb.d/001-init.sql:ro - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] - interval: 5s - timeout: 3s - retries: 20 - - canton: - image: "${CANTON_IMAGE}" - container_name: canton-barebones-canton - environment: - DB_SERVER: postgres - DB_USER: "${POSTGRES_USER}" - DB_PASSWORD: "${POSTGRES_PASSWORD}" - CANTON_AUTH_AUDIENCE: "${CANTON_AUTH_AUDIENCE}" - CANTON_AUTH_SECRET: "${CANTON_AUTH_SECRET}" - CANTON_ADMIN_USER_ID: "${CANTON_ADMIN_USER_ID}" - LOG_LEVEL_STDOUT: INFO - LOG_LEVEL_CANTON: INFO - ports: - - "${CANTON_LEDGER_API_PORT}:5011" - - "${CANTON_ADMIN_API_PORT}:5012" - - "${CANTON_JSON_API_PORT}:7575" - - "${CANTON_SEQUENCER_PUBLIC_PORT}:5001" - - "${CANTON_HTTP_HEALTH_PORT}:7000" - volumes: - - ./config/canton/app.conf:/app/app.conf:ro - - ./config/canton/bootstrap.canton:/app/bootstrap.canton:ro - - ./dars:/dars - entrypoint: ["/app/bin/canton"] - command: - - daemon - - --no-tty - - --log-encoder=json - - --log-level-stdout=INFO - - --log-level-canton=INFO - - --log-file-appender=off - - --config - - /app/app.conf - - --bootstrap - - /app/bootstrap.canton - depends_on: - postgres: - condition: service_healthy - healthcheck: - test: ["CMD-SHELL", "bash -c '/dev/null || exit 1"] - interval: 5s - timeout: 3s - retries: 20 - start_period: 5s - -volumes: - postgres-data: diff --git a/canton-barebones/package.json b/canton-barebones/package.json index a322848a..9188123e 100644 --- a/canton-barebones/package.json +++ b/canton-barebones/package.json @@ -3,10 +3,10 @@ "private": true, "version": "0.1.0", "scripts": { - "up": "docker compose up -d --build", - "down": "docker compose down", + "up": "./scripts/up.sh", + "down": "./scripts/down.sh", "health": "./scripts/health-check.sh", - "token": "node ./scripts/mint-token.mjs wallet-service", + "token": "node ./scripts/mint-token.mjs", "lint": "biome check", "lint:fix": "biome check --write", "format": "biome format --write", diff --git a/canton-barebones/scripts/deploy-dar.sh b/canton-barebones/scripts/deploy-dar.sh index ba55ae94..c2c1032d 100755 --- a/canton-barebones/scripts/deploy-dar.sh +++ b/canton-barebones/scripts/deploy-dar.sh @@ -7,6 +7,14 @@ if [ "$#" -ne 1 ]; then fi ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if [ -f "$ROOT/.env" ]; then + set -a + # shellcheck disable=SC1091 + source "$ROOT/.env" + set +a +fi + DAR_PATH="$1" if [ ! -f "$DAR_PATH" ]; then @@ -14,18 +22,34 @@ if [ ! -f "$DAR_PATH" ]; then exit 1 fi -mkdir -p "$ROOT/dars" -DAR_NAME="$(basename "$DAR_PATH")" -cp "$DAR_PATH" "$ROOT/dars/$DAR_NAME" - -docker compose --project-directory "$ROOT" exec -T canton /app/bin/canton \ - run /dev/stdin \ - --no-tty \ - -C canton.remote-participants.participant1.ledger-api.address=127.0.0.1,canton.remote-participants.participant1.ledger-api.port=5011,canton.remote-participants.participant1.admin-api.address=127.0.0.1,canton.remote-participants.participant1.admin-api.port=5012 <