@distrohelena/canton-typescript-sdk 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/README.md +333 -1
  2. package/dist/client/canton-client-options.d.ts +3 -0
  3. package/dist/client/canton-client-options.js +2 -0
  4. package/dist/client/service-registry.js +3 -2
  5. package/dist/core/errors/grpc-transport-error.d.ts +14 -0
  6. package/dist/core/errors/grpc-transport-error.js +76 -0
  7. package/dist/core/signing/command-signer.interface.d.ts +2 -0
  8. package/dist/core/transports/transport.interface.d.ts +2 -2
  9. package/dist/core/types/canton-hash-purpose.d.ts +2 -1
  10. package/dist/core/types/canton-hash-purpose.js +1 -0
  11. package/dist/core/types/daml-numeric.d.ts +6 -0
  12. package/dist/core/types/daml-numeric.js +14 -0
  13. package/dist/core/types/daml-party.d.ts +6 -0
  14. package/dist/core/types/daml-party.js +14 -0
  15. package/dist/core/types/disclosed-contract.d.ts +13 -0
  16. package/dist/core/types/disclosed-contract.js +15 -0
  17. package/dist/core/types/requests/create-decentralized-party-request.d.ts +41 -0
  18. package/dist/core/types/requests/create-decentralized-party-request.js +82 -0
  19. package/dist/core/types/requests/create-external-party-request.d.ts +42 -0
  20. package/dist/core/types/requests/create-external-party-request.js +47 -0
  21. package/dist/core/types/requests/finalize-decentralized-party-request.d.ts +39 -0
  22. package/dist/core/types/requests/finalize-decentralized-party-request.js +42 -0
  23. package/dist/core/types/requests/get-active-contracts-page-request.d.ts +15 -2
  24. package/dist/core/types/requests/get-active-contracts-page-request.js +4 -0
  25. package/dist/core/types/requests/submit-command-request.d.ts +5 -0
  26. package/dist/core/types/requests/submit-command-request.js +4 -0
  27. package/dist/core/types/topology/topology-signature-format.d.ts +2 -1
  28. package/dist/core/types/topology/topology-signature-format.js +1 -0
  29. package/dist/daml-lf/container/dar-archive-entry.d.ts +8 -0
  30. package/dist/daml-lf/container/dar-archive-entry.js +8 -0
  31. package/dist/daml-lf/container/dar-archive-loader.js +14 -0
  32. package/dist/daml-lf/container/dar-archive.d.ts +6 -0
  33. package/dist/daml-lf/container/dar-archive.js +4 -0
  34. package/dist/daml-lf/container/dar-source-bundle-loader.d.ts +5 -0
  35. package/dist/daml-lf/container/dar-source-bundle-loader.js +33 -0
  36. package/dist/daml-lf/container/dar-source-bundle.d.ts +45 -0
  37. package/dist/daml-lf/container/dar-source-bundle.js +11 -0
  38. package/dist/daml-lf/container/dar-source-file-entry.d.ts +8 -0
  39. package/dist/daml-lf/container/dar-source-file-entry.js +8 -0
  40. package/dist/daml-lf/daml-lf-compilation.d.ts +12 -0
  41. package/dist/daml-lf/daml-lf-compilation.js +35 -0
  42. package/dist/daml-lf/index.d.ts +11 -0
  43. package/dist/daml-lf/index.js +10 -0
  44. package/dist/daml-lf/interpreter/daml-lf-builtin-dispatch.d.ts +43 -0
  45. package/dist/daml-lf/interpreter/daml-lf-builtin-dispatch.js +933 -0
  46. package/dist/daml-lf/interpreter/daml-lf-evaluator.d.ts +95 -0
  47. package/dist/daml-lf/interpreter/daml-lf-evaluator.js +1131 -0
  48. package/dist/daml-lf/interpreter/daml-lf-interpreter-scaffold.d.ts +3 -0
  49. package/dist/daml-lf/interpreter/daml-lf-interpreter-scaffold.js +6 -1
  50. package/dist/daml-lf/interpreter/daml-lf-lexical-scope.d.ts +13 -0
  51. package/dist/daml-lf/interpreter/daml-lf-lexical-scope.js +46 -0
  52. package/dist/daml-lf/interpreter/daml-lf-runtime-frame.d.ts +16 -0
  53. package/dist/daml-lf/interpreter/daml-lf-runtime-frame.js +15 -0
  54. package/dist/daml-lf/interpreter/daml-lf-runtime-value.d.ts +21 -0
  55. package/dist/daml-lf/interpreter/daml-lf-runtime-value.js +4 -1
  56. package/dist/daml-lf/interpreter/daml-lf-step-kind.d.ts +7 -0
  57. package/dist/daml-lf/interpreter/daml-lf-step-kind.js +8 -0
  58. package/dist/daml-lf/interpreter/daml-lf-trace-sink.interface.d.ts +30 -0
  59. package/dist/daml-lf/interpreter/daml-lf-trace-sink.interface.js +1 -0
  60. package/dist/daml-lf/model/daml-lf-builtin-type.d.ts +3 -0
  61. package/dist/daml-lf/model/daml-lf-builtin-type.js +3 -0
  62. package/dist/daml-lf/model/daml-lf-choice.d.ts +5 -0
  63. package/dist/daml-lf/model/daml-lf-choice.js +4 -0
  64. package/dist/daml-lf/model/daml-lf-expression-path.d.ts +2 -0
  65. package/dist/daml-lf/model/daml-lf-expression-path.js +81 -0
  66. package/dist/daml-lf/model/daml-lf-expression.d.ts +146 -0
  67. package/dist/daml-lf/model/daml-lf-expression.js +71 -0
  68. package/dist/daml-lf/model/daml-lf-template.d.ts +2 -0
  69. package/dist/daml-lf/model/daml-lf-template.js +2 -0
  70. package/dist/daml-lf/model/daml-lf-type.d.ts +2 -0
  71. package/dist/daml-lf/model/daml-lf-type.js +4 -0
  72. package/dist/daml-lf/model/lf-2-model-mapper.d.ts +5 -0
  73. package/dist/daml-lf/model/lf-2-model-mapper.js +445 -12
  74. package/dist/debugger/errors/replay-determinism.exception.d.ts +3 -0
  75. package/dist/debugger/errors/replay-determinism.exception.js +3 -0
  76. package/dist/debugger/errors/replay-missing-package.exception.d.ts +3 -0
  77. package/dist/debugger/errors/replay-missing-package.exception.js +3 -0
  78. package/dist/debugger/errors/replay-missing-source.exception.d.ts +3 -0
  79. package/dist/debugger/errors/replay-missing-source.exception.js +3 -0
  80. package/dist/debugger/errors/replay-source-map.exception.d.ts +3 -0
  81. package/dist/debugger/errors/replay-source-map.exception.js +3 -0
  82. package/dist/debugger/errors/replay-state-hydration.exception.d.ts +3 -0
  83. package/dist/debugger/errors/replay-state-hydration.exception.js +3 -0
  84. package/dist/debugger/errors/replay-unsupported-lf-construct.exception.d.ts +3 -0
  85. package/dist/debugger/errors/replay-unsupported-lf-construct.exception.js +3 -0
  86. package/dist/debugger/errors/replay-unsupported-update.exception.d.ts +3 -0
  87. package/dist/debugger/errors/replay-unsupported-update.exception.js +3 -0
  88. package/dist/debugger/index.d.ts +31 -0
  89. package/dist/debugger/index.js +31 -0
  90. package/dist/debugger/ledger-replay-debugger-client.d.ts +31 -0
  91. package/dist/debugger/ledger-replay-debugger-client.js +53 -0
  92. package/dist/debugger/replay/ledger-replay-environment-builder.d.ts +95 -0
  93. package/dist/debugger/replay/ledger-replay-environment-builder.js +249 -0
  94. package/dist/debugger/replay/ledger-replay-session-loader.d.ts +78 -0
  95. package/dist/debugger/replay/ledger-replay-session-loader.js +565 -0
  96. package/dist/debugger/replay/replay-artifact-resolver.d.ts +30 -0
  97. package/dist/debugger/replay/replay-artifact-resolver.js +210 -0
  98. package/dist/debugger/replay/replay-determinism-validator.d.ts +12 -0
  99. package/dist/debugger/replay/replay-determinism-validator.js +161 -0
  100. package/dist/debugger/replay/replay-entrypoint-definition-resolver.d.ts +40 -0
  101. package/dist/debugger/replay/replay-entrypoint-definition-resolver.js +337 -0
  102. package/dist/debugger/replay/replay-entrypoint.d.ts +22 -0
  103. package/dist/debugger/replay/replay-entrypoint.js +14 -0
  104. package/dist/debugger/replay/replay-ledger-value-normalizer.d.ts +7 -0
  105. package/dist/debugger/replay/replay-ledger-value-normalizer.js +194 -0
  106. package/dist/debugger/replay/replay-update-loader.d.ts +24 -0
  107. package/dist/debugger/replay/replay-update-loader.js +94 -0
  108. package/dist/debugger/replay/replay-update-visibility-validator.d.ts +19 -0
  109. package/dist/debugger/replay/replay-update-visibility-validator.js +19 -0
  110. package/dist/debugger/session/in-memory-replay-session-store.d.ts +27 -0
  111. package/dist/debugger/session/in-memory-replay-session-store.js +109 -0
  112. package/dist/debugger/session/replay-phase.d.ts +7 -0
  113. package/dist/debugger/session/replay-phase.js +8 -0
  114. package/dist/debugger/session/replay-scope.d.ts +10 -0
  115. package/dist/debugger/session/replay-scope.js +10 -0
  116. package/dist/debugger/session/replay-session-metadata.d.ts +10 -0
  117. package/dist/debugger/session/replay-session-metadata.js +10 -0
  118. package/dist/debugger/session/replay-session-request.d.ts +6 -0
  119. package/dist/debugger/session/replay-session-request.js +10 -0
  120. package/dist/debugger/session/replay-session.d.ts +12 -0
  121. package/dist/debugger/session/replay-session.js +10 -0
  122. package/dist/debugger/session/replay-source-location.d.ts +17 -0
  123. package/dist/debugger/session/replay-source-location.js +16 -0
  124. package/dist/debugger/session/replay-stack-frame.d.ts +8 -0
  125. package/dist/debugger/session/replay-stack-frame.js +8 -0
  126. package/dist/debugger/session/replay-state-delta.d.ts +29 -0
  127. package/dist/debugger/session/replay-state-delta.js +24 -0
  128. package/dist/debugger/session/replay-step-advance-result.d.ts +24 -0
  129. package/dist/debugger/session/replay-step-advance-result.js +16 -0
  130. package/dist/debugger/session/replay-step.d.ts +30 -0
  131. package/dist/debugger/session/replay-step.js +24 -0
  132. package/dist/debugger/session/replay-value-preview.d.ts +8 -0
  133. package/dist/debugger/session/replay-value-preview.js +15 -0
  134. package/dist/debugger/source/daml-source-mapper.d.ts +12 -0
  135. package/dist/debugger/source/daml-source-mapper.js +21 -0
  136. package/dist/debugger/source/dar-source-map-metadata.d.ts +26 -0
  137. package/dist/debugger/source/dar-source-map-metadata.js +10 -0
  138. package/dist/debugger/source/source-coverage-validator.d.ts +8 -0
  139. package/dist/debugger/source/source-coverage-validator.js +16 -0
  140. package/dist/debugger/source/source-indexed-compilation.d.ts +45 -0
  141. package/dist/debugger/source/source-indexed-compilation.js +110 -0
  142. package/dist/debugger/source/source-mapping-precision.d.ts +5 -0
  143. package/dist/debugger/source/source-mapping-precision.js +4 -0
  144. package/dist/index.d.ts +21 -0
  145. package/dist/index.js +13 -0
  146. package/dist/query/cache/memory-query-cache.d.ts +9 -0
  147. package/dist/query/cache/memory-query-cache.js +24 -0
  148. package/dist/query/cache/query-cache-store.d.ts +5 -0
  149. package/dist/query/cache/query-cache-store.js +1 -0
  150. package/dist/query/canton-manager-options.d.ts +17 -0
  151. package/dist/query/canton-manager-options.js +1 -0
  152. package/dist/query/canton-manager.d.ts +11 -0
  153. package/dist/query/canton-manager.js +42 -0
  154. package/dist/query/errors/pqs-query-error.d.ts +10 -0
  155. package/dist/query/errors/pqs-query-error.js +11 -0
  156. package/dist/query/errors/pqs-schema-profile-error.d.ts +3 -0
  157. package/dist/query/errors/pqs-schema-profile-error.js +3 -0
  158. package/dist/query/errors/query-capability-error.d.ts +7 -0
  159. package/dist/query/errors/query-capability-error.js +10 -0
  160. package/dist/query/grpc/grpc-contract-query-client.d.ts +32 -0
  161. package/dist/query/grpc/grpc-contract-query-client.js +126 -0
  162. package/dist/query/model-types.d.ts +258 -0
  163. package/dist/query/model-types.js +14 -0
  164. package/dist/query/pqs/pqs-pool.d.ts +13 -0
  165. package/dist/query/pqs/pqs-pool.js +18 -0
  166. package/dist/query/pqs/pqs-query-client.d.ts +49 -0
  167. package/dist/query/pqs/pqs-query-client.js +324 -0
  168. package/dist/query/pqs/pqs-schema-profile.d.ts +25 -0
  169. package/dist/query/pqs/pqs-schema-profile.js +120 -0
  170. package/dist/query/pqs/pqs-sql-compiler.d.ts +7 -0
  171. package/dist/query/pqs/pqs-sql-compiler.js +132 -0
  172. package/dist/query/pqs/read-only-sql.d.ts +1 -0
  173. package/dist/query/pqs/read-only-sql.js +35 -0
  174. package/dist/query/query-client.d.ts +61 -0
  175. package/dist/query/query-client.js +1 -0
  176. package/dist/query/query-source.d.ts +4 -0
  177. package/dist/query/query-source.js +5 -0
  178. package/dist/services/command/command-service-client.d.ts +2 -2
  179. package/dist/services/commands/command-submission-pipeline.d.ts +2 -2
  180. package/dist/services/party-management/decentralized-party-lifecycle.d.ts +6 -0
  181. package/dist/services/party-management/decentralized-party-lifecycle.js +82 -0
  182. package/dist/services/party-management/party-management-service-client.d.ts +16 -1
  183. package/dist/services/party-management/party-management-service-client.js +142 -1
  184. package/dist/services/topology-manager-write/topology-signed-transaction-assembler.js +4 -0
  185. package/dist/testing/campaign/campaign-artifact.d.ts +20 -0
  186. package/dist/testing/campaign/campaign-artifact.js +147 -0
  187. package/dist/testing/campaign/campaign-definition.d.ts +9 -0
  188. package/dist/testing/campaign/campaign-definition.js +78 -0
  189. package/dist/testing/campaign/campaign-metrics.d.ts +32 -0
  190. package/dist/testing/campaign/campaign-metrics.js +26 -0
  191. package/dist/testing/campaign/campaign-model.d.ts +11 -0
  192. package/dist/testing/campaign/campaign-model.js +21 -0
  193. package/dist/testing/campaign/campaign-runner.d.ts +99 -0
  194. package/dist/testing/campaign/campaign-runner.js +194 -0
  195. package/dist/testing/campaign/campaign-scheduler.d.ts +40 -0
  196. package/dist/testing/campaign/campaign-scheduler.js +75 -0
  197. package/dist/testing/campaign/campaign-types.d.ts +52 -0
  198. package/dist/testing/campaign/campaign-types.js +11 -0
  199. package/dist/testing/daml/daml-action-arbitrary.d.ts +13 -0
  200. package/dist/testing/daml/daml-action-arbitrary.js +24 -0
  201. package/dist/testing/daml/daml-choice-action-arbitrary.d.ts +18 -0
  202. package/dist/testing/daml/daml-choice-action-arbitrary.js +27 -0
  203. package/dist/testing/daml/daml-create-action-arbitrary.d.ts +19 -0
  204. package/dist/testing/daml/daml-create-action-arbitrary.js +34 -0
  205. package/dist/testing/daml/daml-testing-catalog.d.ts +38 -0
  206. package/dist/testing/daml/daml-testing-catalog.js +40 -0
  207. package/dist/testing/daml/daml-value-arbitrary.d.ts +8 -0
  208. package/dist/testing/daml/daml-value-arbitrary.js +43 -0
  209. package/dist/testing/daml/declarative-campaign-arbitrary.d.ts +15 -0
  210. package/dist/testing/daml/declarative-campaign-arbitrary.js +30 -0
  211. package/dist/testing/errors/invariant-campaign-failure.d.ts +24 -0
  212. package/dist/testing/errors/invariant-campaign-failure.js +37 -0
  213. package/dist/testing/errors/testing-configuration-error.d.ts +3 -0
  214. package/dist/testing/errors/testing-configuration-error.js +6 -0
  215. package/dist/testing/handlers/handler.d.ts +19 -0
  216. package/dist/testing/handlers/handler.js +31 -0
  217. package/dist/testing/index.d.ts +22 -0
  218. package/dist/testing/index.js +22 -0
  219. package/dist/testing/runtime/campaign-isolation.d.ts +14 -0
  220. package/dist/testing/runtime/campaign-isolation.js +23 -0
  221. package/dist/testing/runtime/canton-test-runtime.d.ts +58 -0
  222. package/dist/testing/runtime/canton-test-runtime.js +138 -0
  223. package/dist/testing/runtime/declarative-action-executor.d.ts +16 -0
  224. package/dist/testing/runtime/declarative-action-executor.js +41 -0
  225. package/dist/testing/targets/target.d.ts +49 -0
  226. package/dist/testing/targets/target.js +121 -0
  227. package/dist/transports/grpc/grpc-channel-factory.js +31 -1
  228. package/dist/transports/grpc/grpc-transport.d.ts +2 -2
  229. package/dist/transports/grpc/grpc-transport.js +11 -8
  230. package/dist/transports/grpc/mappers/commands-mapper.js +23 -2
  231. package/dist/transports/grpc/mappers/contracts-mapper.d.ts +3 -1
  232. package/dist/transports/grpc/mappers/contracts-mapper.js +16 -3
  233. package/dist/transports/grpc/mappers/interactive-command-mapper.d.ts +4 -1
  234. package/dist/transports/grpc/mappers/interactive-command-mapper.js +3 -8
  235. package/dist/transports/grpc/mappers/topology-manager-write-mapper.js +59 -1
  236. package/dist/transports/json/mappers/commands-mapper.js +20 -6
  237. package/node/.generated/start-local/additional-config.extra-participants.conf +37 -0
  238. package/node/.generated/start-local/additional-config.extra-validators.conf +35 -0
  239. package/node/.generated/start-local/compose-extra-participants.yaml +147 -0
  240. package/node/.generated/start-local/extra-participants.env +30 -0
  241. package/node/es256-jwt.mjs +177 -0
  242. package/node/start-local.sh +886 -0
  243. package/node/stop-local.sh +200 -0
  244. package/node/test-start-local.sh +354 -0
  245. package/node/test-stop-local.sh +112 -0
  246. package/package.json +21 -2
@@ -0,0 +1,886 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
5
+ REPO_ROOT="$(cd -- "$SCRIPT_DIR/.." && pwd)"
6
+ DOCKER_COMPOSE_CMD=()
7
+
8
+ load_repo_root_env() {
9
+ local env_file="$REPO_ROOT/.env"
10
+ if [[ -f "$env_file" ]]; then
11
+ local existing_cn_quickstart_dir="${CN_QUICKSTART_DIR:-}"
12
+ set -a
13
+ # shellcheck disable=SC1090
14
+ source "$env_file"
15
+ set +a
16
+ if [[ -n "$existing_cn_quickstart_dir" ]]; then
17
+ CN_QUICKSTART_DIR="$existing_cn_quickstart_dir"
18
+ fi
19
+ fi
20
+ }
21
+
22
+ normalize_quickstart_dir_candidate() {
23
+ local candidate="$1"
24
+
25
+ if [[ -z "$candidate" ]]; then
26
+ return 1
27
+ fi
28
+
29
+ if [[ -d "$candidate/quickstart" ]]; then
30
+ candidate="$candidate/quickstart"
31
+ fi
32
+
33
+ if [[ -f "$candidate/compose.yaml" && -d "$candidate/docker/modules" ]]; then
34
+ cd -- "$candidate" && pwd
35
+ return 0
36
+ fi
37
+
38
+ return 1
39
+ }
40
+
41
+ resolve_quickstart_dir() {
42
+ local candidate
43
+ for candidate in \
44
+ "${CN_QUICKSTART_DIR:-}" \
45
+ "$SCRIPT_DIR/../cn-quickstart/quickstart" \
46
+ "$SCRIPT_DIR/../../cn-quickstart/quickstart"
47
+ do
48
+ if normalize_quickstart_dir_candidate "$candidate"; then
49
+ return 0
50
+ fi
51
+ done
52
+
53
+ echo "Unable to locate CN Quickstart. Set CN_QUICKSTART_DIR in $REPO_ROOT/.env or place the checkout in a supported location." >&2
54
+ return 1
55
+ }
56
+
57
+ make_target_exists() {
58
+ local target="$1"
59
+ make -qp 2>/dev/null | grep -Eq "^${target}:"
60
+ }
61
+
62
+ read_env_value_from_files() {
63
+ local key="$1"
64
+ shift
65
+
66
+ local file
67
+ for file in "$@"; do
68
+ if [[ -f "$file" ]]; then
69
+ local line
70
+ line="$(grep -E "^${key}=" "$file" | tail -n 1 || true)"
71
+ if [[ -n "$line" ]]; then
72
+ printf '%s\n' "${line#*=}"
73
+ return 0
74
+ fi
75
+ fi
76
+ done
77
+ return 1
78
+ }
79
+
80
+ read_env_value() {
81
+ read_env_value_from_files "$1" .env.local .env
82
+ }
83
+
84
+ generated_dir() {
85
+ printf '%s\n' "${START_LOCAL_GENERATED_DIR:-$SCRIPT_DIR/.generated/start-local}"
86
+ }
87
+
88
+ resolve_es256_enabled() {
89
+ local value="${LOCALNET_ES256_JWT:-0}"
90
+ if [[ "$value" != "0" && "$value" != "1" ]]; then
91
+ echo "LOCALNET_ES256_JWT must be 0 or 1." >&2
92
+ return 1
93
+ fi
94
+ printf '%s\n' "$value"
95
+ }
96
+
97
+ es256_runtime_dir() {
98
+ printf '%s\n' "${START_LOCAL_ES256_RUNTIME_DIR:-$REPO_ROOT/.generated/localnet-es256}"
99
+ }
100
+
101
+ prepare_es256_runtime_files() {
102
+ local runtime_dir
103
+ runtime_dir="$(es256_runtime_dir)"
104
+ local enabled
105
+ enabled="$(resolve_es256_enabled)"
106
+ if [[ "$enabled" != "1" ]]; then
107
+ return 0
108
+ fi
109
+
110
+ local private_key_path="$runtime_dir/es256-private-key.pem"
111
+ local certificate_path="$runtime_dir/es256-certificate.pem"
112
+ local token_path="$runtime_dir/ledger-api-user.token"
113
+ local compose_file="$runtime_dir/compose-es256.yaml"
114
+ local canton_config_file="$runtime_dir/canton-es256.conf"
115
+ local subject="${LOCALNET_ES256_SUBJECT:-ledger-api-user}"
116
+ local ttl_seconds="${LOCALNET_ES256_TOKEN_TTL_SECONDS:-600}"
117
+
118
+ if [[ -n "${LOCALNET_ES256_PRIVATE_KEY_PATH:-}" || -n "${LOCALNET_ES256_CERTIFICATE_PATH:-}" ]]; then
119
+ if [[ -z "${LOCALNET_ES256_PRIVATE_KEY_PATH:-}" || -z "${LOCALNET_ES256_CERTIFICATE_PATH:-}" ]]; then
120
+ echo "LOCALNET_ES256_PRIVATE_KEY_PATH and LOCALNET_ES256_CERTIFICATE_PATH must be set together." >&2
121
+ return 1
122
+ fi
123
+ private_key_path="$LOCALNET_ES256_PRIVATE_KEY_PATH"
124
+ certificate_path="$LOCALNET_ES256_CERTIFICATE_PATH"
125
+ if [[ ! -r "$private_key_path" || ! -r "$certificate_path" ]]; then
126
+ echo "Supplied ES256 private key or certificate is not readable." >&2
127
+ return 1
128
+ fi
129
+ else
130
+ if [[ "${LOCALNET_ES256_ROTATE:-0}" == "1" ]]; then
131
+ rm -f "$private_key_path" "$certificate_path"
132
+ fi
133
+ if [[ ! -f "$private_key_path" ]]; then
134
+ mkdir -p "$runtime_dir"
135
+ node "$SCRIPT_DIR/es256-jwt.mjs" init --runtime-dir "$runtime_dir" >/dev/null
136
+ fi
137
+ if [[ ! -f "$certificate_path" ]]; then
138
+ openssl req -x509 -new -sha256 -days 3650 -key "$private_key_path" -subj "/CN=localnet-es256" -out "$certificate_path" >/dev/null 2>&1
139
+ fi
140
+ fi
141
+
142
+ mkdir -p "$runtime_dir"
143
+ node "$SCRIPT_DIR/es256-jwt.mjs" mint \
144
+ --private-key-path "$private_key_path" \
145
+ --subject "$subject" \
146
+ --audience "https://canton.network.global/es256" \
147
+ --ttl-seconds "$ttl_seconds" > "$token_path"
148
+ chmod 600 "$token_path"
149
+
150
+ cat > "$canton_config_file" <<EOF
151
+ include file("/app/base-app.conf")
152
+
153
+ EOF
154
+ local participant profile
155
+ for participant in app-provider app-user sv; do
156
+ case "$participant" in
157
+ app-provider) profile="$APP_PROVIDER_PROFILE" ;;
158
+ app-user) profile="$APP_USER_PROFILE" ;;
159
+ sv) profile="$SV_PROFILE" ;;
160
+ esac
161
+ if [[ "$profile" != "off" ]]; then
162
+ cat >> "$canton_config_file" <<EOF
163
+ canton.participants.${participant}.ledger-api.auth-services = [
164
+ {
165
+ type = unsafe-jwt-hmac-256
166
+ target-audience = "https://canton.network.global"
167
+ secret = "unsafe"
168
+ },
169
+ {
170
+ type = jwt-es-256-crt
171
+ certificate = "/app/es256-certificate.pem"
172
+ target-audience = "https://canton.network.global/es256"
173
+ }
174
+ ]
175
+ EOF
176
+ fi
177
+ done
178
+
179
+ cat > "$compose_file" <<EOF
180
+ services:
181
+ canton:
182
+ volumes:
183
+ - "${LOCALNET_DIR}/conf/canton/app.conf:/app/base-app.conf:ro"
184
+ - "$canton_config_file:/app/app.conf:ro"
185
+ - "$certificate_path:/app/es256-certificate.pem:ro"
186
+ EOF
187
+ export LOCALNET_ES256_COMPOSE_FILE="$compose_file"
188
+ export LOCALNET_ES256_TOKEN_PATH="$token_path"
189
+ }
190
+
191
+ append_es256_args() {
192
+ local -n compose_args_ref="$1"
193
+ if [[ "$(resolve_es256_enabled)" == "1" ]]; then
194
+ compose_args_ref+=( -f "$LOCALNET_ES256_COMPOSE_FILE" )
195
+ fi
196
+ }
197
+
198
+ resolve_docker_compose_cmd() {
199
+ if (( ${#DOCKER_COMPOSE_CMD[@]} > 0 )); then
200
+ return 0
201
+ fi
202
+
203
+ if docker compose version >/dev/null 2>&1; then
204
+ DOCKER_COMPOSE_CMD=(docker compose)
205
+ return 0
206
+ fi
207
+
208
+ if docker-compose version >/dev/null 2>&1; then
209
+ DOCKER_COMPOSE_CMD=(docker-compose)
210
+ return 0
211
+ fi
212
+
213
+ echo "Unable to find a working Docker Compose command. Install 'docker compose' or 'docker-compose'." >&2
214
+ return 1
215
+ }
216
+
217
+ docker_compose() {
218
+ resolve_docker_compose_cmd
219
+ "${DOCKER_COMPOSE_CMD[@]}" "$@"
220
+ }
221
+
222
+ resolve_extra_participants() {
223
+ local value="${EXTRA_PARTICIPANTS:-0}"
224
+ if [[ ! "$value" =~ ^[0-9]+$ ]]; then
225
+ echo "EXTRA_PARTICIPANTS must be a non-negative integer." >&2
226
+ return 1
227
+ fi
228
+ if [[ "$value" == "0" ]]; then
229
+ printf '0\n'
230
+ return 0
231
+ fi
232
+ printf '%s\n' "$value"
233
+ }
234
+
235
+ load_localnet_common_env() {
236
+ local common_env_file="$1"
237
+ set -a
238
+ # shellcheck disable=SC1090
239
+ source "$common_env_file"
240
+ set +a
241
+ PARTICIPANT_LEDGER_API_PORT_SUFFIX="${PARTICIPANT_LEDGER_API_PORT_SUFFIX:-901}"
242
+ PARTICIPANT_ADMIN_API_PORT_SUFFIX="${PARTICIPANT_ADMIN_API_PORT_SUFFIX:-902}"
243
+ PARTICIPANT_JSON_API_PORT_SUFFIX="${PARTICIPANT_JSON_API_PORT_SUFFIX:-975}"
244
+ VALIDATOR_ADMIN_API_PORT_SUFFIX="${VALIDATOR_ADMIN_API_PORT_SUFFIX:-903}"
245
+ CANTON_HTTP_HEALTHCHECK_PORT_SUFFIX="${CANTON_HTTP_HEALTHCHECK_PORT_SUFFIX:-900}"
246
+ CANTON_GRPC_HEALTHCHECK_PORT_SUFFIX="${CANTON_GRPC_HEALTHCHECK_PORT_SUFFIX:-961}"
247
+ }
248
+
249
+ participant_port() {
250
+ local prefix="$1"
251
+ local suffix="$2"
252
+ printf '%s%s\n' "$prefix" "$suffix"
253
+ }
254
+
255
+ published_port() {
256
+ local container_port="$1"
257
+ local test_port
258
+ test_port="${TEST_PORT:-$(read_env_value TEST_PORT || true)}"
259
+ if [[ -n "$test_port" ]]; then
260
+ printf '%s%s\n' "$test_port" "$container_port"
261
+ return 0
262
+ fi
263
+ printf '%s\n' "$container_port"
264
+ }
265
+
266
+ extra_pqs_postgres_port() {
267
+ local index="$1"
268
+ printf '554%s\n' "$index"
269
+ }
270
+
271
+ resolve_party_hint_base() {
272
+ local compose_env_file="$1"
273
+
274
+ (
275
+ set -a
276
+ if [[ -f .env ]]; then
277
+ # shellcheck disable=SC1091
278
+ source .env
279
+ fi
280
+ if [[ -f .env.local ]]; then
281
+ # shellcheck disable=SC1091
282
+ source .env.local
283
+ fi
284
+ # shellcheck disable=SC1090
285
+ source "$compose_env_file"
286
+ set +a
287
+ printf '%s\n' "${PARTY_HINT:-${DOCKER_NETWORK:-localnet}-localparty-1}"
288
+ )
289
+ }
290
+
291
+ write_extra_participant_runtime_files() {
292
+ local count="$1"
293
+ local output_dir="$2"
294
+ local party_hint_base="$3"
295
+ local auth_mode="$4"
296
+ mkdir -p "$output_dir"
297
+
298
+ local env_file="$output_dir/extra-participants.env"
299
+ local canton_config_file="$output_dir/additional-config.extra-participants.conf"
300
+ local splice_config_file="$output_dir/additional-config.extra-validators.conf"
301
+ local compose_file="$output_dir/compose-extra-participants.yaml"
302
+
303
+ {
304
+ printf 'START_LOCAL_GENERATED_DIR=%s\n' "$output_dir"
305
+ local index
306
+ for ((index = 1; index <= count; index++)); do
307
+ local prefix=$((index + 4))
308
+ local name="extra-${index}"
309
+ local database="participant-${name}"
310
+ local party_hint="extra_${index}_${party_hint_base}"
311
+ local pqs_database="pqs-${name}"
312
+ local pqs_name="pqs-${name}"
313
+ local pqs_user_name="${name}-pqs-user"
314
+ local pqs_postgres_name="postgres-pqs-${name}"
315
+ local pqs_postgres_published_port
316
+ local validator_database="validator-${name}"
317
+ local validator_party_hint="validator_${index}_${party_hint_base}"
318
+ local validator_onboarding_secret="${name}-validator-onboarding-secret"
319
+ local validator_user_name="ledger-api-user"
320
+ local wallet_admin_user_name="$name"
321
+ local auth_audience="https://canton.network.global"
322
+ local ledger_port admin_port json_port validator_admin_port http_health_port grpc_health_port
323
+ local published_ledger_port published_admin_port published_json_port published_validator_admin_port
324
+ ledger_port="$(participant_port "$prefix" "$PARTICIPANT_LEDGER_API_PORT_SUFFIX")"
325
+ admin_port="$(participant_port "$prefix" "$PARTICIPANT_ADMIN_API_PORT_SUFFIX")"
326
+ json_port="$(participant_port "$prefix" "$PARTICIPANT_JSON_API_PORT_SUFFIX")"
327
+ validator_admin_port="$(participant_port "$prefix" "$VALIDATOR_ADMIN_API_PORT_SUFFIX")"
328
+ http_health_port="$(participant_port "$prefix" "$CANTON_HTTP_HEALTHCHECK_PORT_SUFFIX")"
329
+ grpc_health_port="$(participant_port "$prefix" "$CANTON_GRPC_HEALTHCHECK_PORT_SUFFIX")"
330
+ pqs_postgres_published_port="$(extra_pqs_postgres_port "$index")"
331
+ published_ledger_port="$(published_port "$ledger_port")"
332
+ published_admin_port="$(published_port "$admin_port")"
333
+ published_json_port="$(published_port "$json_port")"
334
+ published_validator_admin_port="$(published_port "$validator_admin_port")"
335
+
336
+ printf 'CREATE_DATABASE_EXTRA_%s=%s\n' "$index" "$database"
337
+ printf 'CREATE_DATABASE_EXTRA_VALIDATOR_%s=%s\n' "$index" "$validator_database"
338
+ printf 'CREATE_DATABASE_EXTRA_PQS_%s=%s\n' "$index" "$pqs_database"
339
+ printf 'EXTRA_PARTICIPANT_%s_NAME=%s\n' "$index" "$name"
340
+ printf 'EXTRA_PARTICIPANT_%s_DB=%s\n' "$index" "$database"
341
+ printf 'EXTRA_PARTICIPANT_%s_PARTY_HINT=%s\n' "$index" "$party_hint"
342
+ printf 'EXTRA_PARTICIPANT_%s_LEDGER_API_PORT=%s\n' "$index" "$ledger_port"
343
+ printf 'EXTRA_PARTICIPANT_%s_ADMIN_API_PORT=%s\n' "$index" "$admin_port"
344
+ printf 'EXTRA_PARTICIPANT_%s_JSON_API_PORT=%s\n' "$index" "$json_port"
345
+ printf 'EXTRA_PARTICIPANT_%s_HTTP_HEALTHCHECK_PORT=%s\n' "$index" "$http_health_port"
346
+ printf 'EXTRA_PARTICIPANT_%s_GRPC_HEALTHCHECK_PORT=%s\n' "$index" "$grpc_health_port"
347
+ printf 'EXTRA_PARTICIPANT_%s_LEDGER_API_PUBLISHED_PORT=%s\n' "$index" "$published_ledger_port"
348
+ printf 'EXTRA_PARTICIPANT_%s_ADMIN_API_PUBLISHED_PORT=%s\n' "$index" "$published_admin_port"
349
+ printf 'EXTRA_PARTICIPANT_%s_JSON_API_PUBLISHED_PORT=%s\n' "$index" "$published_json_port"
350
+ printf 'EXTRA_VALIDATOR_%s_DB=%s\n' "$index" "$validator_database"
351
+ printf 'EXTRA_VALIDATOR_%s_PARTY_HINT=%s\n' "$index" "$validator_party_hint"
352
+ printf 'EXTRA_VALIDATOR_%s_ONBOARDING_SECRET=%s\n' "$index" "$validator_onboarding_secret"
353
+ printf 'EXTRA_VALIDATOR_%s_ADMIN_API_PORT=%s\n' "$index" "$validator_admin_port"
354
+ printf 'EXTRA_VALIDATOR_%s_ADMIN_API_PUBLISHED_PORT=%s\n' "$index" "$published_validator_admin_port"
355
+ printf 'EXTRA_VALIDATOR_%s_USER_NAME=%s\n' "$index" "$validator_user_name"
356
+ printf 'EXTRA_VALIDATOR_%s_WALLET_ADMIN_USER_NAME=%s\n' "$index" "$wallet_admin_user_name"
357
+ printf 'EXTRA_VALIDATOR_%s_AUTH_AUDIENCE=%s\n' "$index" "$auth_audience"
358
+ printf 'EXTRA_VALIDATOR_%s_AUTH_MODE=%s\n' "$index" "$auth_mode"
359
+ printf 'EXTRA_PQS_%s_NAME=%s\n' "$index" "$pqs_name"
360
+ printf 'EXTRA_PQS_%s_USER_NAME=%s\n' "$index" "$pqs_user_name"
361
+ printf 'EXTRA_PQS_%s_POSTGRES_NAME=%s\n' "$index" "$pqs_postgres_name"
362
+ printf 'EXTRA_PQS_%s_POSTGRES_DB=%s\n' "$index" "$pqs_database"
363
+ printf 'EXTRA_PQS_%s_POSTGRES_PUBLISHED_PORT=%s\n' "$index" "$pqs_postgres_published_port"
364
+ printf 'EXTRA_PQS_%s_CONFIG_FILE=%s-pqs.conf\n' "$index" "$name"
365
+ done
366
+ } > "$env_file"
367
+
368
+ {
369
+ local index
370
+ for ((index = 1; index <= count; index++)); do
371
+ cat <<EOF
372
+ canton.participants.extra-${index} = \$\${_participant} {
373
+ storage.config.properties.databaseName = "participant-extra-${index}"
374
+ init {
375
+ generate-topology-transactions-and-keys = true
376
+ identity.type = auto
377
+ }
378
+ monitoring {
379
+ http-health-server.port = \${EXTRA_PARTICIPANT_${index}_HTTP_HEALTHCHECK_PORT}
380
+ grpc-health-server.port = \${EXTRA_PARTICIPANT_${index}_GRPC_HEALTHCHECK_PORT}
381
+ }
382
+ http-ledger-api.port = \${EXTRA_PARTICIPANT_${index}_JSON_API_PORT}
383
+ admin-api.port = \${EXTRA_PARTICIPANT_${index}_ADMIN_API_PORT}
384
+ ledger-api.port = \${EXTRA_PARTICIPANT_${index}_LEDGER_API_PORT}
385
+ ledger-api {
386
+ auth-services = [{
387
+ type = unsafe-jwt-hmac-256
388
+ target-audience = "\${EXTRA_VALIDATOR_${index}_AUTH_AUDIENCE}"
389
+ secret = "unsafe"
390
+ }]
391
+
392
+ user-management-service.additional-admin-user-id = "\${EXTRA_VALIDATOR_${index}_USER_NAME}"
393
+ }
394
+ }
395
+
396
+ EOF
397
+ done
398
+ } > "$canton_config_file"
399
+
400
+ if [[ "$(resolve_es256_enabled)" == "1" ]]; then
401
+ local index
402
+ for ((index = 1; index <= count; index++)); do
403
+ cat >> "$canton_config_file" <<EOF
404
+ canton.participants.extra-${index}.ledger-api.auth-services = [
405
+ {
406
+ type = unsafe-jwt-hmac-256
407
+ target-audience = "https://canton.network.global"
408
+ secret = "unsafe"
409
+ },
410
+ {
411
+ type = jwt-es-256-crt
412
+ certificate = "/app/es256-certificate.pem"
413
+ target-audience = "https://canton.network.global/es256"
414
+ }
415
+ ]
416
+
417
+ EOF
418
+ done
419
+ fi
420
+
421
+ {
422
+ local index
423
+ for ((index = 1; index <= count; index++)); do
424
+ local validator_backend_ref='$${_validator_backend}'
425
+ cat <<EOF
426
+ canton.validator-apps.extra-${index}-validator_backend = ${validator_backend_ref} {
427
+ onboarding.secret = "\${EXTRA_VALIDATOR_${index}_ONBOARDING_SECRET}"
428
+ domain-migration-dump-path = "/domain-upgrade-dump/domain_migration_dump-extra-${index}.json"
429
+ storage.config.properties.databaseName = "\${EXTRA_VALIDATOR_${index}_DB}"
430
+ admin-api.port = \${EXTRA_VALIDATOR_${index}_ADMIN_API_PORT}
431
+ canton-identifier-config.participant = extra-${index}
432
+ participant-client {
433
+ admin-api.port = \${EXTRA_PARTICIPANT_${index}_ADMIN_API_PORT}
434
+ ledger-api {
435
+ client-config.port = \${EXTRA_PARTICIPANT_${index}_LEDGER_API_PORT}
436
+ auth-config = {
437
+ type = "self-signed"
438
+ user = "\${EXTRA_VALIDATOR_${index}_USER_NAME}"
439
+ audience = "\${EXTRA_VALIDATOR_${index}_AUTH_AUDIENCE}"
440
+ secret = "unsafe"
441
+ }
442
+ }
443
+ }
444
+ auth = {
445
+ algorithm = "hs-256-unsafe"
446
+ audience = "\${EXTRA_VALIDATOR_${index}_AUTH_AUDIENCE}"
447
+ secret = "unsafe"
448
+ }
449
+ ledger-api-user = "\${EXTRA_VALIDATOR_${index}_USER_NAME}"
450
+ validator-wallet-users.0 = "\${EXTRA_VALIDATOR_${index}_WALLET_ADMIN_USER_NAME}"
451
+ validator-party-hint = "\${EXTRA_VALIDATOR_${index}_PARTY_HINT}"
452
+
453
+ domains.global.buy-extra-traffic {
454
+ min-topup-interval = \${?MIN_TRAFFIC_TOPUP_INTERVAL}
455
+ target-throughput = \${?TARGET_TRAFFIC_THROUGHPUT}
456
+ }
457
+ }
458
+
459
+ canton.sv-apps.sv.expected-validator-onboardings += { secret = "\${EXTRA_VALIDATOR_${index}_ONBOARDING_SECRET}" }
460
+
461
+ EOF
462
+ done
463
+ } > "$splice_config_file"
464
+
465
+ {
466
+ cat <<'EOF'
467
+ volumes:
468
+ EOF
469
+ local index
470
+ for ((index = 1; index <= count; index++)); do
471
+ cat <<EOF
472
+ postgres-pqs-extra-${index}:
473
+ EOF
474
+ done
475
+ cat <<'EOF'
476
+
477
+ services:
478
+ postgres:
479
+ env_file:
480
+ - ${START_LOCAL_GENERATED_DIR}/extra-participants.env
481
+ canton:
482
+ env_file:
483
+ - ${START_LOCAL_GENERATED_DIR}/extra-participants.env
484
+ environment:
485
+ ADDITIONAL_CONFIG_EXTRA_PARTICIPANTS: |
486
+ EOF
487
+ sed 's/^/ /' "$canton_config_file"
488
+ cat <<'EOF'
489
+ ports:
490
+ EOF
491
+ for ((index = 1; index <= count; index++)); do
492
+ cat <<EOF
493
+ - "\${EXTRA_PARTICIPANT_${index}_LEDGER_API_PUBLISHED_PORT}:\${EXTRA_PARTICIPANT_${index}_LEDGER_API_PORT}"
494
+ - "\${EXTRA_PARTICIPANT_${index}_ADMIN_API_PUBLISHED_PORT}:\${EXTRA_PARTICIPANT_${index}_ADMIN_API_PORT}"
495
+ - "\${EXTRA_PARTICIPANT_${index}_JSON_API_PUBLISHED_PORT}:\${EXTRA_PARTICIPANT_${index}_JSON_API_PORT}"
496
+ EOF
497
+ done
498
+ cat <<'EOF'
499
+ splice:
500
+ env_file:
501
+ - ${START_LOCAL_GENERATED_DIR}/extra-participants.env
502
+ environment:
503
+ ADDITIONAL_CONFIG_EXTRA_VALIDATORS: |
504
+ EOF
505
+ sed -e 's/\${?/\$\${?/g' -e 's/^/ /' "$splice_config_file"
506
+ cat <<'EOF'
507
+ ports:
508
+ EOF
509
+ for ((index = 1; index <= count; index++)); do
510
+ cat <<EOF
511
+ - "\${EXTRA_VALIDATOR_${index}_ADMIN_API_PUBLISHED_PORT}:\${EXTRA_VALIDATOR_${index}_ADMIN_API_PORT}"
512
+ EOF
513
+ done
514
+ for ((index = 1; index <= count; index++)); do
515
+ cat <<EOF
516
+ postgres-pqs-extra-${index}:
517
+ image: "postgres:\${POSTGRES_VERSION}"
518
+ container_name: postgres-pqs-extra-${index}
519
+ volumes:
520
+ - postgres-pqs-extra-${index}:/var/lib/postgresql/data
521
+ - \${LOCALNET_DIR}/docker/postgres/postgres-entrypoint.sh:/postgres-entrypoint.sh
522
+ environment:
523
+ POSTGRES_USER: \${DB_USER}
524
+ POSTGRES_PASSWORD: \${DB_PASSWORD}
525
+ POSTGRES_DB: postgres
526
+ CREATE_DATABASE_EXTRA_PQS_${index}: \${EXTRA_PQS_${index}_POSTGRES_DB}
527
+ ports:
528
+ - "\${EXTRA_PQS_${index}_POSTGRES_PUBLISHED_PORT}:5432"
529
+ entrypoint: /postgres-entrypoint.sh
530
+ healthcheck:
531
+ test: "pg_isready -U \${DB_USER} -d postgres"
532
+ interval: 10s
533
+ timeout: 3s
534
+ retries: 3
535
+ start_period: 30s
536
+ user: "postgres"
537
+ command:
538
+ - postgres
539
+ - -c
540
+ - max_connections=1000
541
+ pqs-extra-${index}:
542
+ image: \${SCRIBE_IMAGE}:\${SCRIBE_VERSION}
543
+ container_name: pqs-extra-${index}
544
+ environment:
545
+ SCRIBE_SOURCE_LEDGER_HOST: canton
546
+ SCRIBE_SOURCE_LEDGER_PORT: \${EXTRA_PARTICIPANT_${index}_LEDGER_API_PORT}
547
+ SCRIBE_SOURCE_LEDGER_AUTH: OAuth
548
+ SCRIBE_TARGET_POSTGRES_HOST: postgres-pqs-extra-${index}
549
+ SCRIBE_TARGET_POSTGRES_PORT: 5432
550
+ SCRIBE_TARGET_POSTGRES_DATABASE: \${EXTRA_PQS_${index}_POSTGRES_DB}
551
+ SCRIBE_TARGET_POSTGRES_USERNAME: \${DB_USER}
552
+ SCRIBE_TARGET_POSTGRES_PASSWORD: \${DB_PASSWORD}
553
+ SCRIBE_CONFIG: /onboarding/extra-${index}-pqs.conf
554
+ volumes:
555
+ - onboarding:/onboarding
556
+ command:
557
+ - pipeline
558
+ - ledger
559
+ - postgres-document
560
+ depends_on:
561
+ canton:
562
+ condition: service_healthy
563
+ splice-onboarding:
564
+ condition: service_healthy
565
+ postgres-pqs-extra-${index}:
566
+ condition: service_healthy
567
+ restart: on-failure:100
568
+ EOF
569
+ done
570
+ } > "$compose_file"
571
+ }
572
+
573
+ append_extra_participant_args() {
574
+ local count="$1"
575
+ local auth_mode="$2"
576
+ local -n compose_args_ref="$3"
577
+ local output_dir
578
+ output_dir="$(generated_dir)"
579
+
580
+ if (( count == 0 )); then
581
+ rm -rf "$output_dir"
582
+ return 0
583
+ fi
584
+
585
+ write_extra_participant_runtime_files "$count" "$output_dir" "$PARTY_HINT_BASE" "$auth_mode"
586
+ compose_args_ref+=(
587
+ -f "$output_dir/compose-extra-participants.yaml"
588
+ --env-file "$output_dir/extra-participants.env"
589
+ )
590
+ }
591
+
592
+ extra_participants_healthy() {
593
+ local count="$1"
594
+ if (( count == 0 )); then
595
+ return 0
596
+ fi
597
+
598
+ local checks=()
599
+ local index
600
+ for ((index = 1; index <= count; index++)); do
601
+ local prefix=$((index + 4))
602
+ local health_port
603
+ health_port="$(participant_port "$prefix" "$CANTON_HTTP_HEALTHCHECK_PORT_SUFFIX")"
604
+ checks+=("curl -sf --max-time 2 http://localhost:${health_port}/health > /dev/null")
605
+ done
606
+
607
+ local joined_checks
608
+ joined_checks="$(printf '%s && ' "${checks[@]}")"
609
+ joined_checks="${joined_checks% && }"
610
+ docker exec canton bash -lc "$joined_checks" >/dev/null 2>&1
611
+ }
612
+
613
+ prerequisite_services() {
614
+ local auth_mode="$1"
615
+ local services=(postgres canton)
616
+ if [[ "$auth_mode" == "oauth2" ]]; then
617
+ services=(keycloak nginx-keycloak "${services[@]}")
618
+ fi
619
+ printf '%s\n' "${services[@]}"
620
+ }
621
+
622
+ dependent_services() {
623
+ printf '%s\n' splice splice-onboarding pqs-app-provider pqs-sv
624
+ }
625
+
626
+ extra_pqs_services() {
627
+ local count="$1"
628
+ local index
629
+ for ((index = 1; index <= count; index++)); do
630
+ printf 'pqs-extra-%s\n' "$index"
631
+ done
632
+ }
633
+
634
+ wait_for_container_health() {
635
+ local container="$1"
636
+ local attempts="${2:-30}"
637
+ local delay_seconds="${3:-2}"
638
+ local status
639
+
640
+ for ((i = 1; i <= attempts; i++)); do
641
+ status="$(docker inspect --format '{{.State.Health.Status}}' "$container" 2>/dev/null | tail -n 1 || true)"
642
+ if [[ "$status" == "healthy" ]]; then
643
+ return 0
644
+ fi
645
+ sleep "$delay_seconds"
646
+ done
647
+
648
+ echo "$container did not become healthy after $((attempts * delay_seconds)) seconds." >&2
649
+ return 1
650
+ }
651
+
652
+ wait_for_canton_health() {
653
+ local extra_participants="${1:-0}"
654
+ local attempts=30
655
+ local delay_seconds=2
656
+ local status
657
+
658
+ for ((i = 1; i <= attempts; i++)); do
659
+ status="$(docker inspect --format '{{.State.Health.Status}}' canton 2>/dev/null | tail -n 1 || true)"
660
+ if [[ "$status" == "healthy" ]] && extra_participants_healthy "$extra_participants"; then
661
+ return 0
662
+ fi
663
+ sleep "$delay_seconds"
664
+ done
665
+
666
+ echo "canton did not become healthy after $((attempts * delay_seconds)) seconds." >&2
667
+ return 1
668
+ }
669
+
670
+ provision_extra_pqs() {
671
+ local count="$1"
672
+ if (( count == 0 )); then
673
+ return 0
674
+ fi
675
+
676
+ local index
677
+ for ((index = 1; index <= count; index++)); do
678
+ local name="extra-${index}"
679
+ local json_port
680
+ json_port="$(published_port "$(participant_port "$((index + 4))" "$PARTICIPANT_JSON_API_PORT_SUFFIX")")"
681
+
682
+ docker exec \
683
+ -e EXTRA_PARTICIPANT_NAME="$name" \
684
+ -e EXTRA_PARTICIPANT_JSON_API_PORT="$json_port" \
685
+ -e EXTRA_PARTICIPANT_PARTY_HINT="extra_${index}_${PARTY_HINT_BASE}" \
686
+ -e EXTRA_PQS_USER_NAME="${name}-pqs-user" \
687
+ -e EXTRA_PQS_CONFIG_FILE="${name}-pqs.conf" \
688
+ splice-onboarding \
689
+ bash -lc '
690
+ set -euo pipefail
691
+ source /app/utils.sh
692
+ participant="canton:${EXTRA_PARTICIPANT_JSON_API_PORT}"
693
+ admin_token="$(generate_jwt "participant_admin" "$AUTH_APP_PROVIDER_AUDIENCE")"
694
+ party="$(allocate_party "$admin_token" "$EXTRA_PARTICIPANT_PARTY_HINT" "$participant")"
695
+ create_user "$admin_token" "$EXTRA_PQS_USER_NAME" "$EXTRA_PQS_USER_NAME" "$party" "$participant"
696
+ grant_rights "$admin_token" "$EXTRA_PQS_USER_NAME" "$party" "ReadAs" "$participant"
697
+ pqs_user_token="$(generate_jwt "$EXTRA_PQS_USER_NAME" "$AUTH_APP_PROVIDER_AUDIENCE")"
698
+ share_file "$EXTRA_PQS_CONFIG_FILE" <<EOF
699
+ pipeline.datasource=TransactionTreeStream
700
+ pipeline.ledger.start=Oldest
701
+ pipeline.oauth.accessToken="${pqs_user_token}"
702
+ EOF
703
+ '
704
+
705
+ grant_user_read_as_any_party_right \
706
+ "${name}-pqs-user" \
707
+ "https://canton.network.global" \
708
+ "$json_port"
709
+ done
710
+ }
711
+
712
+ grant_validator_read_as_any_party_right() {
713
+ local validator_user_name="$1"
714
+ local auth_audience="$2"
715
+ local participant_json_port="$3"
716
+
717
+ docker exec \
718
+ -e VALIDATOR_USER_NAME="$validator_user_name" \
719
+ -e VALIDATOR_AUTH_AUDIENCE="$auth_audience" \
720
+ -e PARTICIPANT_JSON_API_PORT="$participant_json_port" \
721
+ splice-onboarding \
722
+ bash -lc '
723
+ set -euo pipefail
724
+ source /app/utils.sh
725
+ participant="canton:${PARTICIPANT_JSON_API_PORT}"
726
+ validator_token="$(generate_jwt "$VALIDATOR_USER_NAME" "$VALIDATOR_AUTH_AUDIENCE")"
727
+ payload="$(cat <<EOF
728
+ {
729
+ "userId": "${VALIDATOR_USER_NAME}",
730
+ "identityProviderId": "",
731
+ "rights": [
732
+ {"kind":{"CanReadAsAnyParty":{"value":{}}}}
733
+ ]
734
+ }
735
+ EOF
736
+ )"
737
+ curl_check "http://$participant/v2/users/$VALIDATOR_USER_NAME/rights" "$validator_token" "application/json" \
738
+ --data-raw "$payload" >/dev/null
739
+ '
740
+ }
741
+
742
+ grant_user_read_as_any_party_right() {
743
+ local user_name="$1"
744
+ local auth_audience="$2"
745
+ local participant_json_port="$3"
746
+
747
+ docker exec \
748
+ -e TARGET_USER_NAME="$user_name" \
749
+ -e TARGET_AUTH_AUDIENCE="$auth_audience" \
750
+ -e PARTICIPANT_JSON_API_PORT="$participant_json_port" \
751
+ splice-onboarding \
752
+ bash -lc '
753
+ set -euo pipefail
754
+ source /app/utils.sh
755
+ participant="canton:${PARTICIPANT_JSON_API_PORT}"
756
+ admin_token="$(generate_jwt "participant_admin" "$TARGET_AUTH_AUDIENCE")"
757
+ payload="$(cat <<EOF
758
+ {
759
+ "userId": "${TARGET_USER_NAME}",
760
+ "identityProviderId": "",
761
+ "rights": [
762
+ {"kind":{"CanReadAsAnyParty":{"value":{}}}}
763
+ ]
764
+ }
765
+ EOF
766
+ )"
767
+ curl_check "http://$participant/v2/users/$TARGET_USER_NAME/rights" "$admin_token" "application/json" \
768
+ --data-raw "$payload" >/dev/null
769
+ '
770
+ }
771
+
772
+ grant_localnet_validator_read_rights() {
773
+ local extra_participants="${1:-0}"
774
+ local validator_user_name="ledger-api-user"
775
+ local auth_audience="https://canton.network.global"
776
+
777
+ grant_validator_read_as_any_party_right \
778
+ "$validator_user_name" \
779
+ "$auth_audience" \
780
+ "$(published_port "$(participant_port 3 "$PARTICIPANT_JSON_API_PORT_SUFFIX")")"
781
+
782
+ grant_validator_read_as_any_party_right \
783
+ "$validator_user_name" \
784
+ "$auth_audience" \
785
+ "$(published_port "$(participant_port 4 "$PARTICIPANT_JSON_API_PORT_SUFFIX")")"
786
+
787
+ local index
788
+ for ((index = 1; index <= extra_participants; index++)); do
789
+ grant_validator_read_as_any_party_right \
790
+ "$validator_user_name" \
791
+ "$auth_audience" \
792
+ "$(published_port "$(participant_port "$((index + 4))" "$PARTICIPANT_JSON_API_PORT_SUFFIX")")"
793
+ done
794
+ }
795
+
796
+ start_ledger_stack() {
797
+ local auth_mode="${1:-shared-secret}"
798
+ local modules_dir="$QUICKSTART_DIR/docker/modules"
799
+ local localnet_dir="$modules_dir/localnet"
800
+ local splice_version
801
+ splice_version="$(read_env_value SPLICE_VERSION || true)"
802
+ export MODULES_DIR="$modules_dir"
803
+ export LOCALNET_DIR="$localnet_dir"
804
+ export LOCALNET_ENV_DIR="$localnet_dir/env"
805
+ export IMAGE_TAG="${IMAGE_TAG:-$splice_version}"
806
+ export APP_PROVIDER_PROFILE=on
807
+ export APP_USER_PROFILE=off
808
+ export SV_PROFILE=on
809
+ export PQS_SV_PROFILE=on
810
+ load_localnet_common_env "$localnet_dir/env/common.env"
811
+ prepare_es256_runtime_files
812
+ local extra_participants
813
+ extra_participants="$(resolve_extra_participants)"
814
+ if (( extra_participants > 0 )) && [[ "$auth_mode" != "shared-secret" ]]; then
815
+ echo "EXTRA_PARTICIPANTS with extra PQS currently supports AUTH_MODE=shared-secret only." >&2
816
+ return 1
817
+ fi
818
+ PARTY_HINT_BASE="$(resolve_party_hint_base "$localnet_dir/compose.env")"
819
+ export PARTY_HINT_BASE
820
+ local compose_args=(
821
+ -f compose.yaml
822
+ -f "$localnet_dir/compose.yaml"
823
+ -f "$modules_dir/splice-onboarding/compose.yaml"
824
+ -f "$modules_dir/pqs/compose.yaml"
825
+ --env-file .env
826
+ --env-file .env.local
827
+ --env-file "$localnet_dir/compose.env"
828
+ --env-file "$localnet_dir/env/common.env"
829
+ --env-file "$modules_dir/pqs/compose.env"
830
+ --profile app-provider
831
+ --profile pqs-app-provider
832
+ --profile pqs-sv
833
+ )
834
+
835
+ if [[ "$auth_mode" == "oauth2" ]]; then
836
+ compose_args+=(
837
+ -f "$modules_dir/keycloak/compose.yaml"
838
+ --env-file "$modules_dir/keycloak/compose.env"
839
+ --profile keycloak
840
+ )
841
+ fi
842
+
843
+ append_extra_participant_args "$extra_participants" "$auth_mode" compose_args
844
+ append_es256_args compose_args
845
+ docker_compose "${compose_args[@]}" down -v --remove-orphans
846
+ mapfile -t startup_services < <(prerequisite_services "$auth_mode")
847
+ mapfile -t followup_services < <(dependent_services)
848
+
849
+ docker_compose "${compose_args[@]}" up -d --no-recreate "${startup_services[@]}"
850
+ wait_for_canton_health "$extra_participants"
851
+
852
+ docker_compose "${compose_args[@]}" up -d --no-recreate "${followup_services[@]}"
853
+
854
+ if (( extra_participants > 0 )); then
855
+ wait_for_container_health splice 60 2
856
+ wait_for_container_health splice-onboarding 60 2
857
+ if [[ "$auth_mode" == "shared-secret" ]]; then
858
+ grant_localnet_validator_read_rights "$extra_participants"
859
+ fi
860
+ provision_extra_pqs "$extra_participants"
861
+ local extra_pqs_services_list=()
862
+ mapfile -t extra_pqs_services_list < <(extra_pqs_services "$extra_participants")
863
+ docker_compose "${compose_args[@]}" up -d --no-recreate "${extra_pqs_services_list[@]}"
864
+ fi
865
+ }
866
+
867
+ load_repo_root_env
868
+ QUICKSTART_DIR="$(resolve_quickstart_dir)"
869
+ cd "$QUICKSTART_DIR"
870
+
871
+ if [[ ! -f .env.local ]]; then
872
+ echo ".env.local not found. Bootstrapping Quickstart with 'make setup'."
873
+ make setup
874
+ fi
875
+
876
+ if [[ "$(resolve_es256_enabled)" != "1" ]] && make_target_exists start-local-ledger; then
877
+ make start-local-ledger
878
+ else
879
+ auth_mode="$(read_env_value AUTH_MODE || true)"
880
+ auth_mode="${auth_mode:-shared-secret}"
881
+ echo "start-local-ledger target not found. Starting ledger-only compose stack directly."
882
+ start_ledger_stack "$auth_mode"
883
+ if [[ "$(resolve_es256_enabled)" == "1" ]]; then
884
+ echo "ES256 bearer token written to $LOCALNET_ES256_TOKEN_PATH"
885
+ fi
886
+ fi