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
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,20 @@ npx canton-dev-tools verify-dars
npx canton-dev-tools backup-dar --package WrappedAssets-v01 --version 0.0.1
npx canton-dev-tools check-dar-version-policy --all
npx canton-dev-tools check-upgrade-compat
npx canton-dev-tools sync-splice-dars --config splice-dars.json
npx canton-dev-tools sync-splice-dars
```

`backup-dar` / version-policy / upgrade-compat skip `Test` packages by default. Pass `--package` with the daml.yaml name, source dir, or a fuzzy alias (e.g. `wrappedAssets`).

### `sync-splice-dars` config

Create `splice-dars.json` in the repo root (or pass `--config` / `CANTON_SPLICE_DARS_CONFIG`):
By default, sync uses the packaged pin at `config/default-splice-dars.json` (MainNet Splice
**0.6.14** / commit `398919a5b13479877fd61587003ba7a4ba00091b`, token-standard + amulet DARs).
Optional overrides, in order:

1. `--config <path>`
2. `CANTON_SPLICE_DARS_CONFIG`
3. repo-root `splice-dars.json` (per-repo pin when it must differ from the shared default)

```json
{
Expand Down
2 changes: 1 addition & 1 deletion bin/canton-dev-tools
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Environment:
CANTON_LOCALNET_AUTH_MODE oauth2 (package default) or shared-secret
CANTON_LOCALNET_INFRA_ONLY Defaults to true for this package binary
SPLICE_REF / SPLICE_REPO Overrides for sync-splice-dars
CANTON_SPLICE_DARS_CONFIG Path to splice-dars.json
CANTON_SPLICE_DARS_CONFIG Optional path override (else packaged default / splice-dars.json)
USAGE
}

Expand Down
70 changes: 70 additions & 0 deletions config/default-splice-dars.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"spliceRef": "398919a5b13479877fd61587003ba7a4ba00091b",
"requiredDars": [
{
"file": "splice-amulet-0.1.16.dar",
"sha256": "d799e1750bce6099daa996049a318e78f8b424e4e091268012fc3dc111b7a4cb"
},
{
"file": "splice-amulet-0.1.19.dar",
"sha256": "003554a4f3e6a6ee56f5edd55f0be3d521c4b075fc0c3efdc45e114561ad390d"
},
{
"file": "splice-api-featured-app-v1-1.0.0.dar",
"sha256": "01df2e1b86e3a1b9ab55e97e42f280563c9d1fbe909963c74bef62dd642aa3ee"
},
{
"file": "splice-api-token-allocation-instruction-v1-1.0.0.dar",
"sha256": "e2607ca3a1d735a82d3066b78132aa8f94b1886c99a5f14148742d252c7220a2"
},
{
"file": "splice-api-token-allocation-instruction-v2-1.0.0.dar",
"sha256": "31de6be30385936105b24c8ea544bb25f6eede0d0c008154a11462bb6d14a17b"
},
{
"file": "splice-api-token-allocation-request-v2-1.0.0.dar",
"sha256": "aea00bf92c841807c7b02a82eaf2a1599ec8a0f11b2de05edd31fc140a2426b9"
},
{
"file": "splice-api-token-allocation-v1-1.0.0.dar",
"sha256": "c3f3b447142577ea4fa7d912ca11cd6821de7588e324e8877425932a02fccaa1"
},
{
"file": "splice-api-token-allocation-v2-1.0.0.dar",
"sha256": "e349d3a1952cdd52ed55333bef35e9ba40e6e0379521d65556c6aa2f50d9a1fd"
},
{
"file": "splice-api-token-burn-mint-v1-1.0.0.dar",
"sha256": "a18e85c4841a278bce000df8329c3f0e2fee3b30b55dd6a31492d10a72b4f9c1"
},
{
"file": "splice-api-token-holding-v1-1.0.0.dar",
"sha256": "ef75f8eb41a65810221784fdb78bb9dfac7cb22245aba14fa7cb7f69c34e0175"
},
{
"file": "splice-api-token-holding-v2-1.0.0.dar",
"sha256": "f044b34d7a28c2b6c8a540af720297d55450a889b451dc4eed64a9914f1d6b00"
},
{
"file": "splice-api-token-metadata-v1-1.0.0.dar",
"sha256": "455eb160cb5abd4ae9918a6fbb9dad471f721adda39f0e5c76feef08d05637fc"
},
{
"file": "splice-api-token-transfer-events-v2-1.0.0.dar",
"sha256": "9a1ebe5aece0a7f1a49069fd7aba0ced92c4ae94e5e3d49cdb097733a87dddbc"
},
{
"file": "splice-api-token-transfer-instruction-v1-1.0.0.dar",
"sha256": "e4c73aa7ae73fb2fc330b938ffb99f568792321640ba4b9472902aa8d742c994"
},
{
"file": "splice-api-token-transfer-instruction-v2-1.0.0.dar",
"sha256": "720e766456b33abdd26bfc21e54d19413559f0a7ee52000e12d3b6c308199faa"
},
{
"file": "splice-token-standard-utils-2.0.0.dar",
"sha256": "60cd68514b48efacea343722c3692436f300d2e33f98d9e5bc0673347961aca0"
}
],
"syncAdminProtos": true
}
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fairmint/canton-dev-tools",
"version": "0.1.0",
"version": "0.1.4",
"description": "Shared LocalNet CLI, DAML package tooling, and Canton integration-test helpers for Fairmint",
"keywords": [
"canton",
Expand Down Expand Up @@ -46,6 +46,7 @@
},
"files": [
"bin/canton-dev-tools",
"config/default-splice-dars.json",
"scripts/localnet-cloud.sh",
"scripts/install-dpm-sdks.sh",
"dist/**"
Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ DAML package commands (run from a multi-package repo root):
backup-dar Backup a built DAR into dars/
check-dar-version-policy Enforce DAR version / deployment tag policy
check-upgrade-compat Run dpm upgrade-check against backups
sync-splice-dars Fetch pinned Splice DARs from splice-dars.json
sync-splice-dars Fetch pinned Splice DARs (packaged default or splice-dars.json)
install-dpm-sdks Install Daml SDKs from daml.yaml (shell helper)

Common options:
Expand Down
10 changes: 7 additions & 3 deletions src/daml/check-dar-version-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,13 @@ function loadLockAtRef(rootDir: string, ref: string): DarsLock {
return { version: 1, packages: {} };
}

const parsed = parseDarsLockContent(gitText(rootDir, ['show', `${ref}:dars/dars.lock`]), `${ref}:dars/dars.lock`, {
requireVersion1: true,
});
const parsed = parseDarsLockContent(
gitText(rootDir, ['show', `${ref}:dars/dars.lock`]),
`${ref}:dars/dars.lock`,
{
requireVersion1: true,
}
);
return parsed;
}

Expand Down
8 changes: 1 addition & 7 deletions src/daml/dar-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,7 @@ export function getDarLockKey(packageName: string, version: string, darName: str
['version', version],
['darName', darName],
] as const) {
if (
!value ||
value.includes('/') ||
value.includes('\\') ||
value === '.' ||
value === '..'
) {
if (!value || value.includes('/') || value.includes('\\') || value === '.' || value === '..') {
throw new Error(`Unsafe ${label} for DAR lock key: ${value}`);
}
}
Expand Down
12 changes: 10 additions & 2 deletions src/daml/prepare-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as yaml from 'yaml';
import { assertSafeRelativePath, normalizeRelativePath, resolveContainedPath } from './sync-splice-dars';
import {
assertSafeRelativePath,
normalizeRelativePath,
resolveContainedPath,
} from './sync-splice-dars';

export interface PrepareBuildOptions {
rootDir: string;
Expand Down Expand Up @@ -148,7 +152,11 @@ export function prepareBuild(options: PrepareBuildOptions): string[] {

const generatedPackages: string[] = [];
for (const [sourceDir, packageName] of packageNames) {
const sourcePath = resolveContainedPath(rootDir, sourceDir, 'multi-package.yaml packages entry');
const sourcePath = resolveContainedPath(
rootDir,
sourceDir,
'multi-package.yaml packages entry'
);
const targetPath = resolveContainedPath(buildRoot, packageName, 'daml.yaml name');
assertPathInsideRoot(rootDir, targetPath, 'prepare-build target');
copyDir(sourcePath, targetPath);
Expand Down
38 changes: 27 additions & 11 deletions src/daml/sync-splice-dars.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
/**
* Sync pinned Splice DARs (and optionally Canton admin protos) into a consumer repo.
*
* Requires a JSON config (default `splice-dars.json` in the repo root) so each consumer
* owns SPLICE_REF + REQUIRED_DARS. Env overrides: SPLICE_REPO, SPLICE_REF, CANTON_SPLICE_DARS_CONFIG.
* Config resolution order:
* 1. `--config` / `options.configPath`
* 2. `CANTON_SPLICE_DARS_CONFIG`
* 3. consumer `splice-dars.json` (optional per-repo override)
* 4. packaged default at `config/default-splice-dars.json` (shared Fairmint pin)
*
* Env overrides: SPLICE_REPO, SPLICE_REF, CANTON_SPLICE_DARS_CONFIG.
*/

import { execFileSync } from 'node:child_process';
Expand Down Expand Up @@ -60,19 +65,32 @@ const DEFAULT_DARS_RELATIVE_DIR = 'libs/splice/daml/dars';
const DEFAULT_ADMIN_PROTO_RELATIVE_DIR = 'libs/splice/canton/community/admin-api/src/main/protobuf';
const SYNC_STATE_FILENAME = '.canton-splice-sync-state.json';

function resolveConfigPath(rootDir: string, configPath?: string): string {
/** Packaged shared pin shipped with `@fairmint/canton-dev-tools`. */
export function getDefaultSpliceDarsConfigPath(): string {
return path.join(__dirname, '..', '..', 'config', 'default-splice-dars.json');
}

/**
* Resolve which splice-dars config file to load.
* Consumer `splice-dars.json` overrides the packaged default when present.
*/
export function resolveSyncSpliceDarsConfigPath(rootDir: string, configPath?: string): string {
if (configPath) return path.resolve(configPath);
if (process.env['CANTON_SPLICE_DARS_CONFIG']) {
return path.resolve(process.env['CANTON_SPLICE_DARS_CONFIG']);
}
return path.join(rootDir, 'splice-dars.json');
const consumerConfig = path.join(rootDir, 'splice-dars.json');
if (fs.existsSync(consumerConfig)) {
return consumerConfig;
}
return getDefaultSpliceDarsConfigPath();
}

export function loadSyncSpliceDarsConfig(configPath: string): SyncSpliceDarsConfig {
if (!fs.existsSync(configPath)) {
throw new Error(
`Splice DAR config not found: ${configPath}\n` +
'Create splice-dars.json (or pass --config) with spliceRef + requiredDars.\n' +
'Expected packaged config/default-splice-dars.json, or create splice-dars.json / pass --config.\n' +
'See README for the expected shape.'
);
}
Expand Down Expand Up @@ -440,8 +458,7 @@ function syncSpliceDarsFiles(rootDir: string, config: SyncSpliceDarsConfig): voi
DEFAULT_ADMIN_PROTO_RELATIVE_DIR,
'adminProtoRelativeDir'
);
const adminProtoSourceRelative =
config.adminProtoSourceDir || DEFAULT_ADMIN_PROTO_SOURCE_DIR;
const adminProtoSourceRelative = config.adminProtoSourceDir || DEFAULT_ADMIN_PROTO_SOURCE_DIR;
const packageService = config.adminProtoPackageService || DEFAULT_ADMIN_PROTO_PACKAGE_SERVICE;
const syncAdminProtos = config.syncAdminProtos !== false;
if (syncAdminProtos) {
Expand All @@ -464,9 +481,7 @@ function syncSpliceDarsFiles(rootDir: string, config: SyncSpliceDarsConfig): voi
console.log(`Fetching Splice DARs from ${spliceRepo} at ${spliceRef}`);
runGit(['clone', '--filter=blob:none', '--sparse', '--no-checkout', spliceRepo, cloneDir]);
runGit(['checkout', spliceRef], cloneDir);
const sparsePaths = syncAdminProtos
? ['daml/dars', adminProtoSourceRelative]
: ['daml/dars'];
const sparsePaths = syncAdminProtos ? ['daml/dars', adminProtoSourceRelative] : ['daml/dars'];
runGit(['sparse-checkout', 'set', ...sparsePaths], cloneDir);

fs.mkdirSync(darsDir, { recursive: true });
Expand Down Expand Up @@ -537,7 +552,8 @@ export function syncSpliceDars(options: SyncSpliceDarsOptions): void {
const rootDir = path.resolve(options.rootDir);
const force = options.force ?? false;
const config =
options.config ?? loadSyncSpliceDarsConfig(resolveConfigPath(rootDir, options.configPath));
options.config ??
loadSyncSpliceDarsConfig(resolveSyncSpliceDarsConfigPath(rootDir, options.configPath));

for (const dar of config.requiredDars) {
assertSafeRelativePath(dar.file, 'requiredDars.file');
Expand Down
4 changes: 3 additions & 1 deletion src/daml/verify-dars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ export function runVerifyDarsCli(options: VerifyDarsOptions): void {
);
console.error(' 2. If changes were accidental, restore the original DAR files');
console.error(' 3. Never modify backed-up DAR files directly');
console.error(' 4. Run with --update to fix dars.lock hash/size/untracked (use with caution)\n');
console.error(
' 4. Run with --update to fix dars.lock hash/size/untracked (use with caution)\n'
);
} else if (result.missing > 0) {
console.error(
'Tracked DAR files are missing from disk. Restore them before re-running --update.\n'
Expand Down
5 changes: 1 addition & 4 deletions src/testing/testConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,7 @@ export function buildLocalnetClientConfig(): ClientConfig {
/** @deprecated Prefer {@link buildLocalnetClientConfig}. */
export const buildIntegrationTestClientConfig = buildLocalnetClientConfig;

function buildAuthConfig(
apiType: string,
options: { preferOAuth?: boolean } = {}
): AuthConfig {
function buildAuthConfig(apiType: string, options: { preferOAuth?: boolean } = {}): AuthConfig {
const apiPrefix = `FAIRMINT_TEST_${apiType}`;
const authUrl = getEnv('FAIRMINT_TEST_AUTH_URL');
const clientId =
Expand Down
8 changes: 6 additions & 2 deletions test/unit/cip56/transferSmokeTraffic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ import {

describe('CIP-56 transfer smoke traffic error classification', (): void => {
it('detects HTTP 429 and traffic-balance messages in nested causes', (): void => {
const cause = new Error('ApiError: HTTP 429 [request: post http://localhost:3903/api/validator/v0/admin/users]');
const cause = new Error(
'ApiError: HTTP 429 [request: post http://localhost:3903/api/validator/v0/admin/users]'
);
const wrapped = new Error(
'UnknownMutationOutcomeError: The outcome of POST http://localhost:3903/api/validator/v0/admin/users is unknown after 1 request attempt',
{ cause }
);
expect(isTransientValidatorTrafficError(wrapped)).toBe(true);
expect(collectErrorMessages(wrapped)).toContain('HTTP 429');

const traffic = new Error('ABORTED: Traffic balance below reserved traffic amount (0 < 200000)');
const traffic = new Error(
'ABORTED: Traffic balance below reserved traffic amount (0 < 200000)'
);
expect(isTransientValidatorTrafficError(traffic)).toBe(true);
});

Expand Down
20 changes: 4 additions & 16 deletions test/unit/daml/packages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,10 @@ describe('daml package discovery + prepare-build', (): void => {
});

it('rejects multi-package source dirs and daml.yaml names that escape the build tree', (): void => {
writeFileSync(
join(rootDir, 'multi-package.yaml'),
`packages:\n - ../../../.github\n`
);
writeFileSync(join(rootDir, 'multi-package.yaml'), `packages:\n - ../../../.github\n`);
expect(() => prepareBuild({ rootDir })).toThrow(/Unsafe/);

writeFileSync(
join(rootDir, 'multi-package.yaml'),
`packages:\n - WrappedAssets-v01\n`
);
writeFileSync(join(rootDir, 'multi-package.yaml'), `packages:\n - WrappedAssets-v01\n`);
writeFileSync(
join(rootDir, 'WrappedAssets-v01', 'daml.yaml'),
`sdk-version: 3.5.2\nname: ../../../.github\nsource: daml\nversion: 0.0.1\ndependencies: []\n`
Expand All @@ -179,16 +173,10 @@ describe('daml package discovery + prepare-build', (): void => {
});

it('rejects escaping source dirs and daml.yaml names during package discovery', (): void => {
writeFileSync(
join(rootDir, 'multi-package.yaml'),
`packages:\n - ../outside\n`
);
writeFileSync(join(rootDir, 'multi-package.yaml'), `packages:\n - ../outside\n`);
expect(() => discoverPackages(rootDir)).toThrow(/Unsafe/);

writeFileSync(
join(rootDir, 'multi-package.yaml'),
`packages:\n - WrappedAssets-v01\n`
);
writeFileSync(join(rootDir, 'multi-package.yaml'), `packages:\n - WrappedAssets-v01\n`);
writeFileSync(
join(rootDir, 'WrappedAssets-v01', 'daml.yaml'),
`sdk-version: 3.5.2\nname: ../escape\nsource: daml\nversion: 0.0.1\ndependencies: []\n`
Expand Down
Loading
Loading