@aztec-labs/simulator 6.0.0-nightly.20260829

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 (215) hide show
  1. package/README.md +57 -0
  2. package/dest/client.d.ts +6 -0
  3. package/dest/client.d.ts.map +1 -0
  4. package/dest/client.js +3 -0
  5. package/dest/common/errors.d.ts +51 -0
  6. package/dest/common/errors.d.ts.map +1 -0
  7. package/dest/common/errors.js +154 -0
  8. package/dest/common/index.d.ts +3 -0
  9. package/dest/common/index.d.ts.map +1 -0
  10. package/dest/common/index.js +2 -0
  11. package/dest/common/stats/index.d.ts +2 -0
  12. package/dest/common/stats/index.d.ts.map +1 -0
  13. package/dest/common/stats/index.js +1 -0
  14. package/dest/common/stats/stats.d.ts +4 -0
  15. package/dest/common/stats/stats.d.ts.map +1 -0
  16. package/dest/common/stats/stats.js +10 -0
  17. package/dest/private/acvm/acvm.d.ts +43 -0
  18. package/dest/private/acvm/acvm.d.ts.map +1 -0
  19. package/dest/private/acvm/acvm.js +65 -0
  20. package/dest/private/acvm/acvm_types.d.ts +10 -0
  21. package/dest/private/acvm/acvm_types.d.ts.map +1 -0
  22. package/dest/private/acvm/acvm_types.js +3 -0
  23. package/dest/private/acvm/deserialize.d.ts +35 -0
  24. package/dest/private/acvm/deserialize.d.ts.map +1 -0
  25. package/dest/private/acvm/deserialize.js +50 -0
  26. package/dest/private/acvm/index.d.ts +5 -0
  27. package/dest/private/acvm/index.d.ts.map +1 -0
  28. package/dest/private/acvm/index.js +4 -0
  29. package/dest/private/acvm/serialize.d.ts +41 -0
  30. package/dest/private/acvm/serialize.d.ts.map +1 -0
  31. package/dest/private/acvm/serialize.js +99 -0
  32. package/dest/private/acvm_native.d.ts +41 -0
  33. package/dest/private/acvm_native.d.ts.map +1 -0
  34. package/dest/private/acvm_native.js +147 -0
  35. package/dest/private/acvm_wasm.d.ts +16 -0
  36. package/dest/private/acvm_wasm.d.ts.map +1 -0
  37. package/dest/private/acvm_wasm.js +67 -0
  38. package/dest/private/acvm_wasm_with_blobs.d.ts +20 -0
  39. package/dest/private/acvm_wasm_with_blobs.d.ts.map +1 -0
  40. package/dest/private/acvm_wasm_with_blobs.js +35 -0
  41. package/dest/private/circuit_simulator.d.ts +35 -0
  42. package/dest/private/circuit_simulator.d.ts.map +1 -0
  43. package/dest/private/circuit_simulator.js +43 -0
  44. package/dest/private/factory.d.ts +12 -0
  45. package/dest/private/factory.d.ts.map +1 -0
  46. package/dest/private/factory.js +30 -0
  47. package/dest/public/avm/testing/account_proof_fetcher.d.ts +2 -0
  48. package/dest/public/avm/testing/account_proof_fetcher.d.ts.map +1 -0
  49. package/dest/public/avm/testing/account_proof_fetcher.js +152 -0
  50. package/dest/public/avm/testing/base_avm_simulation_tester.d.ts +39 -0
  51. package/dest/public/avm/testing/base_avm_simulation_tester.d.ts.map +1 -0
  52. package/dest/public/avm/testing/base_avm_simulation_tester.js +121 -0
  53. package/dest/public/avm/testing/utils.d.ts +32 -0
  54. package/dest/public/avm/testing/utils.d.ts.map +1 -0
  55. package/dest/public/avm/testing/utils.js +66 -0
  56. package/dest/public/avm_simulator.d.ts +28 -0
  57. package/dest/public/avm_simulator.d.ts.map +1 -0
  58. package/dest/public/avm_simulator.js +4 -0
  59. package/dest/public/avm_simulator_pool.d.ts +84 -0
  60. package/dest/public/avm_simulator_pool.d.ts.map +1 -0
  61. package/dest/public/avm_simulator_pool.js +311 -0
  62. package/dest/public/cdb_ipc_server.d.ts +40 -0
  63. package/dest/public/cdb_ipc_server.d.ts.map +1 -0
  64. package/dest/public/cdb_ipc_server.js +153 -0
  65. package/dest/public/contracts_db_checkpoint.d.ts +16 -0
  66. package/dest/public/contracts_db_checkpoint.d.ts.map +1 -0
  67. package/dest/public/contracts_db_checkpoint.js +30 -0
  68. package/dest/public/db_interfaces.d.ts +68 -0
  69. package/dest/public/db_interfaces.d.ts.map +1 -0
  70. package/dest/public/db_interfaces.js +3 -0
  71. package/dest/public/executor_metrics.d.ts +21 -0
  72. package/dest/public/executor_metrics.d.ts.map +1 -0
  73. package/dest/public/executor_metrics.js +64 -0
  74. package/dest/public/executor_metrics_interface.d.ts +10 -0
  75. package/dest/public/executor_metrics_interface.d.ts.map +1 -0
  76. package/dest/public/executor_metrics_interface.js +1 -0
  77. package/dest/public/fixtures/amm_test.d.ts +10 -0
  78. package/dest/public/fixtures/amm_test.d.ts.map +1 -0
  79. package/dest/public/fixtures/amm_test.js +213 -0
  80. package/dest/public/fixtures/bulk_test.d.ts +6 -0
  81. package/dest/public/fixtures/bulk_test.d.ts.map +1 -0
  82. package/dest/public/fixtures/bulk_test.js +289 -0
  83. package/dest/public/fixtures/custom_bytecode_tester.d.ts +34 -0
  84. package/dest/public/fixtures/custom_bytecode_tester.d.ts.map +1 -0
  85. package/dest/public/fixtures/custom_bytecode_tester.js +53 -0
  86. package/dest/public/fixtures/index.d.ts +10 -0
  87. package/dest/public/fixtures/index.d.ts.map +1 -0
  88. package/dest/public/fixtures/index.js +9 -0
  89. package/dest/public/fixtures/public_processor_test_env.d.ts +30 -0
  90. package/dest/public/fixtures/public_processor_test_env.d.ts.map +1 -0
  91. package/dest/public/fixtures/public_processor_test_env.js +73 -0
  92. package/dest/public/fixtures/public_tx_simulation_tester.d.ts +77 -0
  93. package/dest/public/fixtures/public_tx_simulation_tester.d.ts.map +1 -0
  94. package/dest/public/fixtures/public_tx_simulation_tester.js +176 -0
  95. package/dest/public/fixtures/simple_contract_data_source.d.ts +36 -0
  96. package/dest/public/fixtures/simple_contract_data_source.d.ts.map +1 -0
  97. package/dest/public/fixtures/simple_contract_data_source.js +95 -0
  98. package/dest/public/fixtures/token_test.d.ts +12 -0
  99. package/dest/public/fixtures/token_test.d.ts.map +1 -0
  100. package/dest/public/fixtures/token_test.js +96 -0
  101. package/dest/public/fixtures/utils.d.ts +26 -0
  102. package/dest/public/fixtures/utils.d.ts.map +1 -0
  103. package/dest/public/fixtures/utils.js +170 -0
  104. package/dest/public/fuzzing/avm_fuzzer_simulator.d.ts +65 -0
  105. package/dest/public/fuzzing/avm_fuzzer_simulator.d.ts.map +1 -0
  106. package/dest/public/fuzzing/avm_fuzzer_simulator.js +181 -0
  107. package/dest/public/fuzzing/avm_simulator_bin.d.ts +2 -0
  108. package/dest/public/fuzzing/avm_simulator_bin.d.ts.map +1 -0
  109. package/dest/public/fuzzing/avm_simulator_bin.js +108 -0
  110. package/dest/public/index.d.ts +10 -0
  111. package/dest/public/index.d.ts.map +1 -0
  112. package/dest/public/index.js +6 -0
  113. package/dest/public/public_db_sources.d.ts +83 -0
  114. package/dest/public/public_db_sources.d.ts.map +1 -0
  115. package/dest/public/public_db_sources.js +297 -0
  116. package/dest/public/public_errors.d.ts +12 -0
  117. package/dest/public/public_errors.d.ts.map +1 -0
  118. package/dest/public/public_errors.js +13 -0
  119. package/dest/public/public_processor/guarded_merkle_tree.d.ts +52 -0
  120. package/dest/public/public_processor/guarded_merkle_tree.d.ts.map +1 -0
  121. package/dest/public/public_processor/guarded_merkle_tree.js +116 -0
  122. package/dest/public/public_processor/public_processor.d.ts +71 -0
  123. package/dest/public/public_processor/public_processor.d.ts.map +1 -0
  124. package/dest/public/public_processor/public_processor.js +858 -0
  125. package/dest/public/public_processor/public_processor_metrics.d.ts +30 -0
  126. package/dest/public/public_processor/public_processor_metrics.d.ts.map +1 -0
  127. package/dest/public/public_processor/public_processor_metrics.js +116 -0
  128. package/dest/public/public_tx_simulator/dumping_public_tx_simulator.d.ts +17 -0
  129. package/dest/public/public_tx_simulator/dumping_public_tx_simulator.d.ts.map +1 -0
  130. package/dest/public/public_tx_simulator/dumping_public_tx_simulator.js +39 -0
  131. package/dest/public/public_tx_simulator/factories.d.ts +14 -0
  132. package/dest/public/public_tx_simulator/factories.d.ts.map +1 -0
  133. package/dest/public/public_tx_simulator/factories.js +28 -0
  134. package/dest/public/public_tx_simulator/index.d.ts +6 -0
  135. package/dest/public/public_tx_simulator/index.d.ts.map +1 -0
  136. package/dest/public/public_tx_simulator/index.js +3 -0
  137. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts +53 -0
  138. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts.map +1 -0
  139. package/dest/public/public_tx_simulator/public_tx_simulator.js +127 -0
  140. package/dest/public/public_tx_simulator/public_tx_simulator_base.d.ts +24 -0
  141. package/dest/public/public_tx_simulator/public_tx_simulator_base.d.ts.map +1 -0
  142. package/dest/public/public_tx_simulator/public_tx_simulator_base.js +31 -0
  143. package/dest/public/public_tx_simulator/public_tx_simulator_interface.d.ts +32 -0
  144. package/dest/public/public_tx_simulator/public_tx_simulator_interface.d.ts.map +1 -0
  145. package/dest/public/public_tx_simulator/public_tx_simulator_interface.js +1 -0
  146. package/dest/public/side_effect_errors.d.ts +44 -0
  147. package/dest/public/side_effect_errors.d.ts.map +1 -0
  148. package/dest/public/side_effect_errors.js +75 -0
  149. package/dest/public/test_executor_metrics.d.ts +56 -0
  150. package/dest/public/test_executor_metrics.d.ts.map +1 -0
  151. package/dest/public/test_executor_metrics.js +305 -0
  152. package/dest/public/unique_class_ids.d.ts +37 -0
  153. package/dest/public/unique_class_ids.d.ts.map +1 -0
  154. package/dest/public/unique_class_ids.js +61 -0
  155. package/dest/public/utils.d.ts +3 -0
  156. package/dest/public/utils.d.ts.map +1 -0
  157. package/dest/public/utils.js +18 -0
  158. package/dest/server.d.ts +7 -0
  159. package/dest/server.d.ts.map +1 -0
  160. package/dest/server.js +5 -0
  161. package/package.json +109 -0
  162. package/src/client.ts +5 -0
  163. package/src/common/errors.ts +219 -0
  164. package/src/common/index.ts +2 -0
  165. package/src/common/stats/index.ts +1 -0
  166. package/src/common/stats/stats.ts +20 -0
  167. package/src/private/acvm/acvm.ts +114 -0
  168. package/src/private/acvm/acvm_types.ts +11 -0
  169. package/src/private/acvm/deserialize.ts +58 -0
  170. package/src/private/acvm/index.ts +4 -0
  171. package/src/private/acvm/serialize.ts +123 -0
  172. package/src/private/acvm_native.ts +200 -0
  173. package/src/private/acvm_wasm.ts +80 -0
  174. package/src/private/acvm_wasm_with_blobs.ts +55 -0
  175. package/src/private/circuit_simulator.ts +91 -0
  176. package/src/private/factory.ts +40 -0
  177. package/src/public/avm/testing/account_proof.json +553 -0
  178. package/src/public/avm/testing/account_proof_fetcher.ts +166 -0
  179. package/src/public/avm/testing/base_avm_simulation_tester.ts +162 -0
  180. package/src/public/avm/testing/utils.ts +114 -0
  181. package/src/public/avm_simulator.ts +29 -0
  182. package/src/public/avm_simulator_pool.ts +355 -0
  183. package/src/public/cdb_ipc_server.ts +198 -0
  184. package/src/public/contracts_db_checkpoint.ts +41 -0
  185. package/src/public/db_interfaces.ts +76 -0
  186. package/src/public/executor_metrics.ts +102 -0
  187. package/src/public/executor_metrics_interface.ts +20 -0
  188. package/src/public/fixtures/amm_test.ts +331 -0
  189. package/src/public/fixtures/bulk_test.ts +194 -0
  190. package/src/public/fixtures/custom_bytecode_tester.ts +83 -0
  191. package/src/public/fixtures/index.ts +13 -0
  192. package/src/public/fixtures/public_processor_test_env.ts +88 -0
  193. package/src/public/fixtures/public_tx_simulation_tester.ts +315 -0
  194. package/src/public/fixtures/simple_contract_data_source.ts +122 -0
  195. package/src/public/fixtures/token_test.ts +148 -0
  196. package/src/public/fixtures/utils.ts +269 -0
  197. package/src/public/fuzzing/avm_fuzzer_simulator.ts +302 -0
  198. package/src/public/fuzzing/avm_simulator_bin.ts +156 -0
  199. package/src/public/index.ts +16 -0
  200. package/src/public/public_db_sources.ts +405 -0
  201. package/src/public/public_errors.ts +14 -0
  202. package/src/public/public_processor/guarded_merkle_tree.ts +161 -0
  203. package/src/public/public_processor/public_processor.ts +657 -0
  204. package/src/public/public_processor/public_processor_metrics.ts +138 -0
  205. package/src/public/public_tx_simulator/dumping_public_tx_simulator.ts +66 -0
  206. package/src/public/public_tx_simulator/factories.ts +61 -0
  207. package/src/public/public_tx_simulator/index.ts +8 -0
  208. package/src/public/public_tx_simulator/public_tx_simulator.ts +190 -0
  209. package/src/public/public_tx_simulator/public_tx_simulator_base.ts +37 -0
  210. package/src/public/public_tx_simulator/public_tx_simulator_interface.ts +33 -0
  211. package/src/public/side_effect_errors.ts +96 -0
  212. package/src/public/test_executor_metrics.ts +399 -0
  213. package/src/public/unique_class_ids.ts +79 -0
  214. package/src/public/utils.ts +16 -0
  215. package/src/server.ts +6 -0
@@ -0,0 +1,355 @@
1
+ import { AvmService } from '@aztec/bb-avm-sim';
2
+
3
+ import { AbortError } from '@aztec-labs/foundation/error';
4
+ import { type Logger, createLogger } from '@aztec-labs/foundation/log';
5
+ import { sleep } from '@aztec-labs/foundation/sleep';
6
+
7
+ import type { AvmContractsDBContext, AvmSimulator } from './avm_simulator.js';
8
+ import { CdbIpcServer } from './cdb_ipc_server.js';
9
+
10
+ export interface AvmSimulatorPoolOptions {
11
+ /** Maximum number of concurrent AVM processes. If not set, defaults to AVM_MAX_CONCURRENT_SIMULATIONS env var or 4. */
12
+ maxSize?: number;
13
+ /** Path to the bb-avm-sim binary. If omitted, the generated package resolves it. */
14
+ avmBinaryPath?: string;
15
+ /** IPC path for the shared WSDB server. */
16
+ wsdbIpcPath: string;
17
+ /** Optional logger function for AVM process output. */
18
+ logger?: (msg: string) => void;
19
+ /**
20
+ * Flat delay between environmental spawn failures (default 1s). No backoff: sequencers live on
21
+ * ~6s slots, so sleeping longer than this after a failure costs whole blocks while the machine may
22
+ * have recovered — and a spawn attempt is cheap. Spawning never gives up on its own; the caller's
23
+ * deadline (abort signal) is the bound.
24
+ */
25
+ spawnRetryIntervalMs?: number;
26
+ /** Process spawner override. Test hook; defaults to spawning a real bb-avm-sim via the generated AvmService. */
27
+ spawnProcess?: (options: AvmProcessSpawnOptions) => Promise<AvmProcessHandle>;
28
+ }
29
+
30
+ /** Options handed to the process spawner for each new pool slot. */
31
+ export interface AvmProcessSpawnOptions {
32
+ binaryPath?: string;
33
+ wsdbIpcPath: string;
34
+ cdbIpcPath: string;
35
+ logger?: (msg: string) => void;
36
+ }
37
+
38
+ /**
39
+ * A handle to a single bb-avm-sim service: it runs serialized simulations and connects back to the
40
+ * shared CDB/WSDB servers for state, but is unaware of which fork's contract data it is reading — that is
41
+ * routed by the fork id baked into the input buffer. The underlying service owns its process lifecycle
42
+ * (including respawn-on-death), so a handle stays usable for the life of the pool.
43
+ */
44
+ export interface AvmProcessHandle {
45
+ simulate(inputBuffer: Uint8Array, signal?: AbortSignal): Promise<Uint8Array>;
46
+ simulateWithHints(inputBuffer: Uint8Array): Promise<Uint8Array>;
47
+ destroy(): Promise<void>;
48
+ }
49
+
50
+ /**
51
+ * The generated service flags errors caused by the death of the underlying process (rather than by the
52
+ * request itself) with `retry: true`. That distinction is interpreted here and goes no further: process
53
+ * lifecycle is invisible to the pool's callers.
54
+ */
55
+ function isProcessFailure(err: unknown): boolean {
56
+ return err instanceof Error && (err as Error & { retry?: unknown }).retry === true;
57
+ }
58
+
59
+ /** Sleep that wakes early (without throwing) when the signal aborts; callers re-check the signal. */
60
+ async function abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
61
+ if (!signal) {
62
+ return sleep(ms);
63
+ }
64
+ let onAbort: () => void;
65
+ const aborted = new Promise<void>(resolve => {
66
+ onAbort = resolve;
67
+ signal.addEventListener('abort', onAbort, { once: true });
68
+ });
69
+ try {
70
+ await Promise.race([sleep(ms), aborted]);
71
+ } finally {
72
+ signal.removeEventListener('abort', onAbort!);
73
+ }
74
+ }
75
+
76
+ // After a cancellation is requested, how long the C++ process gets to cancel cooperatively (SIGUSR1
77
+ // checkpoint) before it is SIGKILLed. Killing is safe: the service respawns its process lazily, so the
78
+ // pool slot recovers instead of being leaked to a wedged simulation.
79
+ const CANCEL_KILL_GRACE_MS = 5_000;
80
+
81
+ /**
82
+ * The public-execution AVM backend: a lazily-grown pool of bb-avm-sim services plus the CDB server that
83
+ * answers those processes' contract-data callbacks. Callers hold this as an {@link AvmSimulator}; the pool,
84
+ * the CDB server, its IPC path, and fork-id routing are all hidden behind that interface. Each `simulate`
85
+ * registers the call's contracts DB on the CDB server for the duration of the simulation (keyed by fork id
86
+ * so concurrent simulations on different forks don't collide) and unregisters it once the call returns.
87
+ *
88
+ * Process lifecycle is invisible to callers, exactly as when the simulator ran in-process: a simulation
89
+ * either produces a result, fails on its own merits, or runs until the caller's deadline aborts it.
90
+ * Environmental trouble is absorbed here — spawn failures retry indefinitely on a backoff ladder (bounded
91
+ * only by the caller's abort signal), and a simulation whose process dies is re-issued on the respawned
92
+ * process. The one deliberate exception: an input that kills the process twice is treated as a failing
93
+ * transaction, so a simulator-crashing tx gets evicted instead of burning a process per block forever.
94
+ */
95
+ export class AvmSimulatorPool implements AvmSimulator {
96
+ private slots: AvmProcessHandle[] = [];
97
+ private available: number[] = [];
98
+ private waiters: Array<{ resolve: (simulator: AvmProcessHandle) => void; reject: (error: Error) => void }> = [];
99
+ private createdCount = 0;
100
+ private destroyed = false;
101
+ private log: Logger;
102
+ private maxSize: number;
103
+ private cdbServer: CdbIpcServer;
104
+ private readonly spawnRetryIntervalMs: number;
105
+ private readonly spawnProcess: (options: AvmProcessSpawnOptions) => Promise<AvmProcessHandle>;
106
+
107
+ constructor(private options: AvmSimulatorPoolOptions) {
108
+ this.log = createLogger('simulator:avm-pool');
109
+ this.maxSize = options.maxSize ?? parseInt(process.env.AVM_MAX_CONCURRENT_SIMULATIONS ?? '4', 10);
110
+ this.cdbServer = new CdbIpcServer();
111
+ this.spawnRetryIntervalMs = options.spawnRetryIntervalMs ?? 1_000;
112
+ this.spawnProcess = options.spawnProcess ?? (spawnOptions => AvmSimulatorProcess.spawn(spawnOptions));
113
+ }
114
+
115
+ static async spawn(options: AvmSimulatorPoolOptions): Promise<AvmSimulatorPool> {
116
+ const pool = new AvmSimulatorPool(options);
117
+ // Always start one process up front so the first simulate() doesn't pay process spawn/connect cost.
118
+ // This is also where configuration errors (missing binary) surface, fast and fatally.
119
+ await pool.prewarm();
120
+ return pool;
121
+ }
122
+
123
+ async [Symbol.asyncDispose](): Promise<void> {
124
+ await this.destroy();
125
+ }
126
+
127
+ async simulate(inputBuffer: Uint8Array, context: AvmContractsDBContext, signal?: AbortSignal): Promise<Uint8Array> {
128
+ // Register the fork's contracts DB so the C++ AVM's callbacks (which carry this fork id) route to it,
129
+ // and unregister once the simulation returns — registration is only needed while the call is running.
130
+ this.cdbServer.registerFork(context.forkId, context.contractsDB, context.timestamp);
131
+ try {
132
+ return await this.runOnPool(simulator => simulator.simulate(inputBuffer, signal), signal);
133
+ } finally {
134
+ this.cdbServer.unregisterFork(context.forkId);
135
+ }
136
+ }
137
+
138
+ async simulateWithHints(inputBuffer: Uint8Array): Promise<Uint8Array> {
139
+ // The hinted path makes no contract-data callbacks, so no CDB registration is needed.
140
+ return await this.runOnPool(simulator => simulator.simulateWithHints(inputBuffer));
141
+ }
142
+
143
+ /** Destroy all AVM services in the pool and close the CDB server. */
144
+ async destroy(): Promise<void> {
145
+ this.destroyed = true;
146
+ for (const waiter of this.waiters.splice(0)) {
147
+ waiter.reject(new Error('AVM simulator pool destroyed'));
148
+ }
149
+
150
+ await Promise.all(this.slots.map(slot => slot.destroy()));
151
+
152
+ this.slots = [];
153
+ this.available = [];
154
+ this.createdCount = 0;
155
+ await this.cdbServer.close();
156
+ this.log.info('AVM simulator pool destroyed');
157
+ }
158
+
159
+ /**
160
+ * Eagerly spawn up to `count` AVM processes (capped at maxSize) and leave them available, so the
161
+ * first simulate() doesn't pay process spawn/connect cost. Idempotent.
162
+ */
163
+ async prewarm(count = 1): Promise<void> {
164
+ const target = Math.min(count, this.maxSize);
165
+ const created: AvmProcessHandle[] = [];
166
+ while (this.createdCount < target) {
167
+ created.push(await this.checkout());
168
+ }
169
+ // Hand the checked-out processes back to the pool so checkout() reuses them.
170
+ for (const simulator of created) {
171
+ this.return(simulator);
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Run a call on a pooled service. A call that fails because its process died is re-issued on the
177
+ * respawned process; a second death for the same input is attributed to the input and surfaces as an
178
+ * ordinary error (the pre-IPC equivalent — a native crash — took down the whole node, so a failed tx
179
+ * is strictly gentler). Non-process failures surface as-is: they are the simulation's own verdict.
180
+ */
181
+ private async runOnPool<T>(fn: (simulator: AvmProcessHandle) => Promise<T>, signal?: AbortSignal): Promise<T> {
182
+ for (let attempt = 0; ; attempt++) {
183
+ const simulator = await this.checkout(signal);
184
+ try {
185
+ return await fn(simulator);
186
+ } catch (err) {
187
+ if (!isProcessFailure(err) || signal?.aborted) {
188
+ throw err;
189
+ }
190
+ if (attempt > 0) {
191
+ const message = err instanceof Error ? err.message : String(err);
192
+ throw new Error(
193
+ `AVM simulator process died twice running this simulation; attributing the failure to the input: ${message}`,
194
+ { cause: err },
195
+ );
196
+ }
197
+ this.log.warn(`AVM process died during simulation; re-issuing once on the respawned process`, { err });
198
+ } finally {
199
+ this.return(simulator);
200
+ }
201
+ }
202
+ }
203
+
204
+ /** Check out an AVM service from the pool, blocking until one is free. Caller must return() it when done. */
205
+ private async checkout(signal?: AbortSignal): Promise<AvmProcessHandle> {
206
+ if (this.destroyed) {
207
+ throw new Error('AVM simulator pool destroyed');
208
+ }
209
+ const idx = this.available.pop();
210
+ if (idx !== undefined) {
211
+ return this.slots[idx];
212
+ }
213
+
214
+ if (this.createdCount < this.maxSize) {
215
+ return await this.createSlot(signal);
216
+ }
217
+
218
+ return new Promise<AvmProcessHandle>((resolve, reject) => {
219
+ const waiter = { resolve, reject };
220
+ if (signal) {
221
+ const onAbort = () => {
222
+ const at = this.waiters.indexOf(waiter);
223
+ if (at >= 0) {
224
+ this.waiters.splice(at, 1);
225
+ reject(new AbortError('AVM checkout aborted'));
226
+ }
227
+ };
228
+ signal.addEventListener('abort', onAbort, { once: true });
229
+ waiter.resolve = simulator => {
230
+ signal.removeEventListener('abort', onAbort);
231
+ resolve(simulator);
232
+ };
233
+ waiter.reject = err => {
234
+ signal.removeEventListener('abort', onAbort);
235
+ reject(err);
236
+ };
237
+ }
238
+ this.waiters.push(waiter);
239
+ });
240
+ }
241
+
242
+ /** Return an AVM service to the pool after use. */
243
+ private return(simulator: AvmProcessHandle): void {
244
+ const waiter = this.waiters.shift();
245
+ if (waiter) {
246
+ waiter.resolve(simulator);
247
+ } else {
248
+ const idx = this.slots.indexOf(simulator);
249
+ if (idx >= 0) {
250
+ this.available.push(idx);
251
+ }
252
+ }
253
+ }
254
+
255
+ private async createSlot(signal?: AbortSignal): Promise<AvmProcessHandle> {
256
+ // Reserve the slot count synchronously, before the async spawn, so concurrent checkouts can't all
257
+ // observe `createdCount < maxSize` and overshoot the pool (the count is only bumped once control has
258
+ // yielded on the await). Roll back the reservation if the spawn is abandoned.
259
+ this.createdCount++;
260
+ try {
261
+ const simulator = await this.spawnUntilUp(signal);
262
+ this.slots.push(simulator);
263
+ this.log.debug(`Created AVM pool slot (${this.createdCount}/${this.maxSize})`);
264
+ return simulator;
265
+ } catch (err) {
266
+ this.createdCount--;
267
+ throw err;
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Spawn a service, retrying environmental failures on a flat cadence indefinitely — the bound is
273
+ * the caller's own deadline (abort signal), matching the pre-IPC contract where a simulation either
274
+ * completed or was deadlined out. The cadence is deliberately fast and constant: an attempt is cheap,
275
+ * a slow one self-paces inside the backend's connect backstop, and backing off would cost a
276
+ * ~6s-slot sequencer whole blocks after the machine has already recovered. Configuration errors
277
+ * (missing binary, flagged non-retryable by the generated service) throw immediately; the boot-time
278
+ * prewarm is where those are meant to surface.
279
+ */
280
+ private async spawnUntilUp(signal?: AbortSignal): Promise<AvmProcessHandle> {
281
+ for (let failures = 0; ; failures++) {
282
+ if (this.destroyed) {
283
+ throw new Error('AVM simulator pool destroyed');
284
+ }
285
+ if (signal?.aborted) {
286
+ throw new AbortError('AVM process spawn aborted');
287
+ }
288
+ try {
289
+ return await this.spawnProcess({
290
+ binaryPath: this.options.avmBinaryPath,
291
+ wsdbIpcPath: this.options.wsdbIpcPath,
292
+ cdbIpcPath: this.cdbServer.ipcPath,
293
+ logger: this.options.logger,
294
+ });
295
+ } catch (err) {
296
+ if (!isProcessFailure(err)) {
297
+ throw err;
298
+ }
299
+ this.log.warn(
300
+ `Failed to spawn AVM process (attempt ${failures + 1}); retrying in ${this.spawnRetryIntervalMs}ms`,
301
+ { err },
302
+ );
303
+ await abortableSleep(this.spawnRetryIntervalMs, signal);
304
+ }
305
+ }
306
+ }
307
+ }
308
+
309
+ class AvmSimulatorProcess implements AvmProcessHandle {
310
+ private constructor(private service: AvmService) {}
311
+
312
+ static async spawn(options: AvmProcessSpawnOptions): Promise<AvmSimulatorProcess> {
313
+ const service = await AvmService.spawn({
314
+ binaryPath: options.binaryPath,
315
+ transport: 'uds',
316
+ logger: options.logger,
317
+ extraArgs: ['--wsdb', options.wsdbIpcPath, '--cdb', options.cdbIpcPath],
318
+ // Each simulation is self-contained (state comes from the WSDB/CDB servers, routed by fork id),
319
+ // so a fresh process can safely serve the next call after a death.
320
+ respawn: true,
321
+ });
322
+ return new AvmSimulatorProcess(service);
323
+ }
324
+
325
+ public async simulate(inputBuffer: Uint8Array, signal?: AbortSignal): Promise<Uint8Array> {
326
+ let killTimer: NodeJS.Timeout | undefined;
327
+ // Cooperative cancellation first: SIGUSR1 makes the C++ process stop at its next cancellation
328
+ // checkpoint. If it doesn't respond within the grace (wedged in a slow op), SIGKILL it — the
329
+ // service respawns lazily, so this reclaims the pool slot rather than leaking it.
330
+ const onAbort = () => {
331
+ this.service.sendProcessSignal('SIGUSR1');
332
+ killTimer = setTimeout(() => this.service.sendProcessSignal('SIGKILL'), CANCEL_KILL_GRACE_MS);
333
+ };
334
+ if (signal?.aborted) {
335
+ onAbort();
336
+ }
337
+ signal?.addEventListener('abort', onAbort, { once: true });
338
+ try {
339
+ return (await this.service.simulate({ inputs: inputBuffer })).result;
340
+ } finally {
341
+ signal?.removeEventListener('abort', onAbort);
342
+ if (killTimer !== undefined) {
343
+ clearTimeout(killTimer);
344
+ }
345
+ }
346
+ }
347
+
348
+ public async simulateWithHints(inputBuffer: Uint8Array): Promise<Uint8Array> {
349
+ return (await this.service.simulateWithHints({ inputs: inputBuffer })).result;
350
+ }
351
+
352
+ public async destroy(): Promise<void> {
353
+ await this.service.destroy();
354
+ }
355
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * UDS server for AVM CDB requests.
3
+ *
4
+ * Transport (socket, framing, per-connection response ordering) is handled by the shared
5
+ * `UdsIpcServer` from `@aztec/ipc-runtime`; message dispatch comes from the generated CDB
6
+ * server. This class only implements the generated handler interface and routes requests to
7
+ * PublicContractsDB instances by fork ID.
8
+ */
9
+ import {
10
+ type CdbAddContracts,
11
+ type CdbAddContractsResponse,
12
+ type CdbCommitCheckpoint,
13
+ type CdbCommitCheckpointResponse,
14
+ type CdbCreateCheckpoint,
15
+ type CdbCreateCheckpointResponse,
16
+ type CdbGetBytecodeCommitment,
17
+ type CdbGetBytecodeCommitmentResponse,
18
+ type CdbGetContractClass,
19
+ type CdbGetContractClassResponse,
20
+ type CdbGetContractInstance,
21
+ type CdbGetContractInstanceResponse,
22
+ type CdbGetDebugFunctionName,
23
+ type CdbGetDebugFunctionNameResponse,
24
+ type Handler as CdbHandler,
25
+ type CdbRevertCheckpoint,
26
+ type CdbRevertCheckpointResponse,
27
+ handleRequest,
28
+ } from '@aztec/cdb';
29
+ import { UdsIpcServer } from '@aztec/ipc-runtime';
30
+
31
+ import { Fr } from '@aztec-labs/foundation/curves/bn254';
32
+ import { type Logger, createLogger } from '@aztec-labs/foundation/log';
33
+ import { FunctionSelector } from '@aztec-labs/stdlib/abi';
34
+ import { AztecAddress } from '@aztec-labs/stdlib/aztec-address';
35
+ import { ContractDeploymentData, type ContractInstanceWithAddress } from '@aztec-labs/stdlib/contract';
36
+ import { Decoder, Encoder } from 'msgpackr';
37
+ import * as os from 'node:os';
38
+ import * as path from 'node:path';
39
+ import { threadId } from 'node:worker_threads';
40
+
41
+ import type { PublicContractsDB } from './public_db_sources.js';
42
+
43
+ const encoder = new Encoder({ useRecords: false });
44
+ const decoder = new Decoder({ useRecords: false });
45
+
46
+ let instanceCounter = 0;
47
+
48
+ function toFieldBuffer(field: Fr | AztecAddress): Buffer {
49
+ return field.toBuffer();
50
+ }
51
+
52
+ /** Serializes contract instances to the AVM CDB wire shape. */
53
+ export function serializeContractInstance(instance: ContractInstanceWithAddress): Record<string, unknown> {
54
+ return {
55
+ salt: toFieldBuffer(instance.salt),
56
+ deployer: toFieldBuffer(instance.deployer),
57
+ currentContractClassId: toFieldBuffer(instance.currentContractClassId),
58
+ originalContractClassId: toFieldBuffer(instance.originalContractClassId),
59
+ initializationHash: toFieldBuffer(instance.initializationHash),
60
+ immutablesHash: toFieldBuffer(instance.immutablesHash),
61
+ publicKeys: {
62
+ npkMHash: toFieldBuffer(instance.publicKeys.npkMHash),
63
+ ivpkM: {
64
+ x: toFieldBuffer(instance.publicKeys.ivpkM.x),
65
+ y: toFieldBuffer(instance.publicKeys.ivpkM.y),
66
+ },
67
+ ovpkMHash: toFieldBuffer(instance.publicKeys.ovpkMHash),
68
+ tpkMHash: toFieldBuffer(instance.publicKeys.tpkMHash),
69
+ mspkMHash: toFieldBuffer(instance.publicKeys.mspkMHash),
70
+ fbpkMHash: toFieldBuffer(instance.publicKeys.fbpkMHash),
71
+ },
72
+ };
73
+ }
74
+
75
+ /** Serializes contract classes to the AVM CDB wire shape. */
76
+ function serializeContractClass(contractClass: {
77
+ id: Fr;
78
+ artifactHash: Fr;
79
+ privateFunctionsRoot: Fr;
80
+ packedBytecode: Buffer;
81
+ }): Record<string, unknown> {
82
+ return {
83
+ id: toFieldBuffer(contractClass.id),
84
+ artifactHash: toFieldBuffer(contractClass.artifactHash),
85
+ privateFunctionsRoot: toFieldBuffer(contractClass.privateFunctionsRoot),
86
+ packedBytecode: contractClass.packedBytecode,
87
+ };
88
+ }
89
+
90
+ /** Routes AVM CDB IPC requests to registered PublicContractsDB forks. */
91
+ export class CdbIpcServer implements CdbHandler {
92
+ public readonly ipcPath: string;
93
+ private server: Promise<UdsIpcServer>;
94
+ private log: Logger;
95
+ /** Maps WSDB fork IDs to contracts DB/timestamp pairs for concurrent simulations. */
96
+ private forks = new Map<number, { db: PublicContractsDB; timestamp: bigint }>();
97
+
98
+ constructor() {
99
+ this.log = createLogger('cdb-ipc-server');
100
+ this.ipcPath = path.join(os.tmpdir(), `cdb-ts-${process.pid}-${threadId}-${instanceCounter++}.sock`);
101
+
102
+ // Listen asynchronously; the C++ AVM retries connecting until the socket is up.
103
+ this.server = UdsIpcServer.listen(this.ipcPath, (_clientId, request) => handleRequest(this, request));
104
+ this.server.then(
105
+ () => this.log.debug(`CDB IPC server listening on ${this.ipcPath}`),
106
+ err => this.log.error(`CDB IPC server failed to listen on ${this.ipcPath}`, { err }),
107
+ );
108
+ }
109
+
110
+ /** Register a PublicContractsDB for a given WSDB fork ID. */
111
+ registerFork(forkId: number, contractsDB: PublicContractsDB, timestamp: bigint): void {
112
+ this.forks.set(forkId, { db: contractsDB, timestamp });
113
+ }
114
+
115
+ /** Unregister a fork's contracts DB (call after simulation completes). */
116
+ unregisterFork(forkId: number): void {
117
+ this.forks.delete(forkId);
118
+ }
119
+
120
+ /** Close the server and all active connections. */
121
+ async close(): Promise<void> {
122
+ const server = await this.server.catch(() => undefined);
123
+ await server?.close();
124
+ }
125
+
126
+ async [Symbol.asyncDispose](): Promise<void> {
127
+ await this.close();
128
+ }
129
+
130
+ /** Look up the contracts DB for a given fork ID, throwing if not registered. */
131
+ private getFork(forkId: number): { db: PublicContractsDB; timestamp: bigint } {
132
+ const fork = this.forks.get(forkId);
133
+ if (!fork) {
134
+ const registered = Array.from(this.forks.keys()).sort((a, b) => a - b);
135
+ throw new Error(
136
+ `CDB server: no contracts DB registered for forkId ${forkId} (registered=[${registered.join(',')}])`,
137
+ );
138
+ }
139
+ return fork;
140
+ }
141
+
142
+ async getContractInstance(command: CdbGetContractInstance): Promise<CdbGetContractInstanceResponse> {
143
+ const { db, timestamp } = this.getFork(command.forkId);
144
+ const address = AztecAddress.fromBuffer(Buffer.from(command.address));
145
+ const instance = await db.getContractInstance(address, timestamp);
146
+ return { instance: instance ? encoder.encode(serializeContractInstance(instance)) : null };
147
+ }
148
+
149
+ async getContractClass(command: CdbGetContractClass): Promise<CdbGetContractClassResponse> {
150
+ const { db } = this.getFork(command.forkId);
151
+ const classId = Fr.fromBuffer(Buffer.from(command.classId));
152
+ const contractClass = await db.getContractClass(classId);
153
+ return { contractClass: contractClass ? encoder.encode(serializeContractClass(contractClass)) : null };
154
+ }
155
+
156
+ async getBytecodeCommitment(command: CdbGetBytecodeCommitment): Promise<CdbGetBytecodeCommitmentResponse> {
157
+ const { db } = this.getFork(command.forkId);
158
+ const classId = Fr.fromBuffer(Buffer.from(command.classId));
159
+ const commitment = await db.getBytecodeCommitment(classId);
160
+ return { commitment: commitment ? toFieldBuffer(commitment) : null };
161
+ }
162
+
163
+ async getDebugFunctionName(command: CdbGetDebugFunctionName): Promise<CdbGetDebugFunctionNameResponse> {
164
+ const { db } = this.getFork(command.forkId);
165
+ const address = AztecAddress.fromBuffer(Buffer.from(command.address));
166
+ const selectorField = Fr.fromBuffer(Buffer.from(command.selector));
167
+ const selector = FunctionSelector.fromFieldOrUndefined(selectorField);
168
+ const name = selector ? await db.getDebugFunctionName(address, selector) : undefined;
169
+ return { name: name ?? null };
170
+ }
171
+
172
+ addContracts(command: CdbAddContracts): Promise<CdbAddContractsResponse> {
173
+ const { db } = this.getFork(command.forkId);
174
+ const contractDeploymentData = ContractDeploymentData.fromPlainObject(
175
+ decoder.decode(command.contractDeploymentData),
176
+ );
177
+ db.addContractsFromLogs(contractDeploymentData);
178
+ return Promise.resolve({});
179
+ }
180
+
181
+ createCheckpoint(command: CdbCreateCheckpoint): Promise<CdbCreateCheckpointResponse> {
182
+ const { db } = this.getFork(command.forkId);
183
+ db.createCheckpoint();
184
+ return Promise.resolve({});
185
+ }
186
+
187
+ commitCheckpoint(command: CdbCommitCheckpoint): Promise<CdbCommitCheckpointResponse> {
188
+ const { db } = this.getFork(command.forkId);
189
+ db.commitCheckpoint();
190
+ return Promise.resolve({});
191
+ }
192
+
193
+ revertCheckpoint(command: CdbRevertCheckpoint): Promise<CdbRevertCheckpointResponse> {
194
+ const { db } = this.getFork(command.forkId);
195
+ db.revertCheckpoint();
196
+ return Promise.resolve({});
197
+ }
198
+ }
@@ -0,0 +1,41 @@
1
+ import type { Fr } from '@aztec-labs/foundation/curves/bn254';
2
+ import type { AztecAddress } from '@aztec-labs/stdlib/aztec-address';
3
+ import type { ContractClassPublic, ContractInstanceWithAddress } from '@aztec-labs/stdlib/contract';
4
+
5
+ export class ContractsDbCheckpoint {
6
+ private instances: Map<string, ContractInstanceWithAddress> = new Map();
7
+ private classes: Map<string, ContractClassPublic> = new Map();
8
+ private bytecodeCommitments: Map<string, Fr> = new Map();
9
+
10
+ public addInstance(address: AztecAddress, instance: ContractInstanceWithAddress): void {
11
+ this.instances.set(address.toString(), instance);
12
+ }
13
+
14
+ public addClass(classId: Fr, contractClass: ContractClassPublic): void {
15
+ this.classes.set(classId.toString(), contractClass);
16
+ }
17
+
18
+ public addBytecodeCommitment(classId: Fr, commitment: Fr): void {
19
+ this.bytecodeCommitments.set(classId.toString(), commitment);
20
+ }
21
+
22
+ public getInstance(address: AztecAddress): ContractInstanceWithAddress | undefined {
23
+ return this.instances.get(address.toString());
24
+ }
25
+
26
+ public getClass(classId: Fr): ContractClassPublic | undefined {
27
+ return this.classes.get(classId.toString());
28
+ }
29
+
30
+ public getBytecodeCommitment(classId: Fr): Fr | undefined {
31
+ return this.bytecodeCommitments.get(classId.toString());
32
+ }
33
+
34
+ public fork(): ContractsDbCheckpoint {
35
+ const copy = new ContractsDbCheckpoint();
36
+ this.instances.forEach((value, key) => copy.instances.set(key, value));
37
+ this.classes.forEach((value, key) => copy.classes.set(key, value));
38
+ this.bytecodeCommitments.forEach((value, key) => copy.bytecodeCommitments.set(key, value));
39
+ return copy;
40
+ }
41
+ }
@@ -0,0 +1,76 @@
1
+ import type { Fr } from '@aztec-labs/foundation/curves/bn254';
2
+ import type { FunctionSelector } from '@aztec-labs/stdlib/abi';
3
+ import type { AztecAddress } from '@aztec-labs/stdlib/aztec-address';
4
+ import type { ContractClassPublic, ContractInstanceWithAddress } from '@aztec-labs/stdlib/contract';
5
+ import type { UInt64 } from '@aztec-labs/stdlib/types';
6
+
7
+ /**
8
+ * Database interface for providing access to public state.
9
+ */
10
+ export interface PublicStateDBInterface {
11
+ /**
12
+ * Reads a value from public storage, returning zero if none.
13
+ * @param contract - Owner of the storage.
14
+ * @param slot - Slot to read in the contract storage.
15
+ * @returns The current value in the storage slot.
16
+ */
17
+ storageRead(contract: AztecAddress, slot: Fr): Promise<Fr>;
18
+
19
+ /**
20
+ * Records a write to public storage.
21
+ * @param contract - Owner of the storage.
22
+ * @param slot - Slot to read in the contract storage.
23
+ * @param newValue - The new value to store.
24
+ */
25
+ storageWrite(contract: AztecAddress, slot: Fr, newValue: Fr): Promise<void>;
26
+ }
27
+
28
+ /**
29
+ * Database interface for providing access to public contract data.
30
+ */
31
+ export interface PublicContractsDBInterface {
32
+ /**
33
+ * Returns a contract instance given its address and the given timestamp, or undefined if not exists.
34
+ * @param address - Address of the contract.
35
+ * @param timestamp - Timestamp to get the contract instance at. Contract updates might change the instance.
36
+ * @returns The contract instance or undefined if not found.
37
+ */
38
+ getContractInstance(address: AztecAddress, timestamp: UInt64): Promise<ContractInstanceWithAddress | undefined>;
39
+
40
+ /**
41
+ * Returns a publicly deployed contract class.
42
+ * @param contractClassId - ID of the contract class.
43
+ * @returns The contract class or undefined if not found
44
+ */
45
+ getContractClass(contractClassId: Fr): Promise<ContractClassPublic | undefined>;
46
+
47
+ /**
48
+ * Returns the commitment to the bytecode of a contract class.
49
+ * @param contractClassId - ID of the contract class.
50
+ * @returns The commitment to the bytecode or undefined if not found.
51
+ */
52
+ getBytecodeCommitment(contractClassId: Fr): Promise<Fr | undefined>;
53
+
54
+ /**
55
+ * Returns the function name of a contract's function given its selector.
56
+ * @param contractAddress - Address of the contract.
57
+ * @param selector - Selector of the function.
58
+ * @returns The name of the function or undefined if not found.
59
+ */
60
+ getDebugFunctionName(contractAddress: AztecAddress, selector: FunctionSelector): Promise<string | undefined>;
61
+
62
+ /**
63
+ * Creates a checkpoint of the current contract state.
64
+ */
65
+ createCheckpoint(): void;
66
+
67
+ /**
68
+ * Commits the current checkpoint, merging its state with the parent.
69
+ */
70
+ commitCheckpoint(): void;
71
+
72
+ /**
73
+ * Reverts the current checkpoint, discarding its state.
74
+ */
75
+ revertCheckpoint(): void;
76
+ }