Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
avm_simulate_napi.cpp
Go to the documentation of this file.
2
3#include <array>
4#include <memory>
5#include <vector>
6
16#include "barretenberg/wsdb/generated/wsdb_ipc_client.hpp"
17
18namespace bb::nodejs {
19namespace {
20
21// Log levels from TS foundation/src/log/log-levels.ts: ['silent', 'fatal', 'error', 'warn', 'info', 'verbose', 'debug',
22// 'trace'] Map: 0=silent, 1=fatal, 2=error, 3=warn, 4=info, 5=verbose, 6=debug, 7=trace
23
24// Helper to set logging level based on TS log level
25inline void set_logging_from_level(int ts_log_level)
26{
27 // Map TS log level (0-7) to C++ LogLevel enum
28 // TS: 0=silent, 1=fatal, 2=error, 3=warn, 4=info, 5=verbose, 6=debug, 7=trace
29 // C++: SILENT=0, FATAL=1, ERROR=2, WARN=3, INFO=4, VERBOSE=5, DEBUG=6, TRACE=7
30 // They map 1:1
31 if (ts_log_level >= 0 && ts_log_level <= 7) {
32 bb_log_level = static_cast<LogLevel>(ts_log_level);
33 } else {
34 log_warn("Invalid log level from TypeScript: ", ts_log_level, ". Using default.");
35 }
36}
37
38// Map C++ LogLevel enum to TypeScript log level string
39// C++ LogLevel: SILENT=0, FATAL=1, ERROR=2, WARN=3, INFO=4, VERBOSE=5, DEBUG=6, TRACE=7
40// TS LogLevels: ['silent', 'fatal', 'error', 'warn', 'info', 'verbose', 'debug', 'trace']
41inline const char* cpp_log_level_to_ts(LogLevel level)
42{
43 switch (level) {
45 return "silent";
46 case LogLevel::FATAL:
47 return "fatal";
48 case LogLevel::ERROR:
49 return "error";
50 case LogLevel::WARN:
51 return "warn";
52 case LogLevel::INFO:
53 return "info";
55 return "verbose";
56 case LogLevel::DEBUG:
57 return "debug";
58 case LogLevel::TRACE:
59 return "trace";
60 default:
61 return "info";
62 }
63}
64
65// Helper to create a LogFunction wrapper from a ThreadSafeFunction
66// This allows C++ logging to call back to TypeScript logger from worker threads
67LogFunction create_log_function_from_tsfn(const std::shared_ptr<Napi::ThreadSafeFunction>& logger_tsfn)
68{
69 return [logger_tsfn](LogLevel level, const std::string& msg) {
70 // Convert C++ LogLevel to TS log level string
71 const char* ts_level = cpp_log_level_to_ts(level);
72
73 // Call TypeScript logger function on the JS main thread
74 // Using BlockingCall to ensure synchronous execution
75 // Ignore errors - logging failures shouldn't crash the simulation
76 // NOTE: We copy the string because it might be destroyed before the callback is called.
77 logger_tsfn->BlockingCall([ts_level, msg](Napi::Env env, Napi::Function js_logger) {
78 // Create arguments: (level: string, msg: string)
79 auto level_js = Napi::String::New(env, ts_level);
80 auto msg_js = Napi::String::New(env, msg);
81 js_logger.Call({ level_js, msg_js });
82 });
83 };
84}
85
86// Callback method names
87constexpr const char* CALLBACK_GET_CONTRACT_INSTANCE = "getContractInstance";
88constexpr const char* CALLBACK_GET_CONTRACT_CLASS = "getContractClass";
89constexpr const char* CALLBACK_ADD_CONTRACTS = "addContracts";
90constexpr const char* CALLBACK_GET_BYTECODE = "getBytecodeCommitment";
91constexpr const char* CALLBACK_GET_DEBUG_NAME = "getDebugFunctionName";
92constexpr const char* CALLBACK_CREATE_CHECKPOINT = "createCheckpoint";
93constexpr const char* CALLBACK_COMMIT_CHECKPOINT = "commitCheckpoint";
94constexpr const char* CALLBACK_REVERT_CHECKPOINT = "revertCheckpoint";
95
96// RAII helper to automatically release thread-safe functions
97// Used inside the async lambda to ensure cleanup in all code paths
98class TsfnReleaser {
100
101 public:
102 explicit TsfnReleaser(std::vector<std::shared_ptr<Napi::ThreadSafeFunction>> tsfns)
103 : tsfns_(std::move(tsfns))
104 {}
105
106 ~TsfnReleaser()
107 {
108 for (auto& tsfn : tsfns_) {
109 if (tsfn) {
110 tsfn->Release();
111 }
112 }
113 }
114
115 // Prevent copying and moving
116 TsfnReleaser(const TsfnReleaser&) = delete;
117 TsfnReleaser& operator=(const TsfnReleaser&) = delete;
118 TsfnReleaser(TsfnReleaser&&) = delete;
119 TsfnReleaser& operator=(TsfnReleaser&&) = delete;
120};
121
122// Helper to create thread-safe function wrapper
123inline std::shared_ptr<Napi::ThreadSafeFunction> make_tsfn(Napi::Env env, Napi::Function fn, const char* name)
124{
125 return std::make_shared<Napi::ThreadSafeFunction>(Napi::ThreadSafeFunction::New(env, fn, name, 0, 1));
126}
127
128// Bundle all contract-related thread-safe functions with named access
129struct ContractTsfns {
138
140 {
143 }
144};
145
146// Helper to validate and extract contract provider callbacks
147struct ContractCallbacks {
148 static constexpr const char* ALL_METHODS[] = { CALLBACK_GET_CONTRACT_INSTANCE, CALLBACK_GET_CONTRACT_CLASS,
149 CALLBACK_ADD_CONTRACTS, CALLBACK_GET_BYTECODE,
150 CALLBACK_GET_DEBUG_NAME, CALLBACK_CREATE_CHECKPOINT,
151 CALLBACK_COMMIT_CHECKPOINT, CALLBACK_REVERT_CHECKPOINT };
152
153 static void validate(Napi::Env env, Napi::Object provider)
154 {
155 for (const char* method : ALL_METHODS) {
156 if (!provider.Has(method)) {
157 throw Napi::TypeError::New(
158 env, std::string("contractProvider must have ") + method + " method. Missing methods: " + method);
159 }
160 }
161 }
162
163 static Napi::Function get(Napi::Object provider, const char* name)
164 {
165 return provider.Get(name).As<Napi::Function>();
166 }
167};
168} // namespace
169
170Napi::Value AvmSimulateNapi::simulate(const Napi::CallbackInfo& cb_info)
171{
172 Napi::Env env = cb_info.Env();
173
174 // Validate arguments - expects 3-6 arguments
175 // arg[0]: inputs Buffer (required)
176 // arg[1]: contractProvider object (required)
177 // arg[2]: worldStateHandle external (required)
178 // arg[3]: logLevel number (optional) - index into TS LogLevels array, -1 if omitted
179 // arg[4]: loggerFunction (optional) - can be null/undefined
180 // arg[5]: cancellationToken external (optional)
181 if (cb_info.Length() < 3) {
182 throw Napi::TypeError::New(
183 env,
184 "Wrong number of arguments. Expected 3-6 arguments: inputs Buffer, contractProvider "
185 "object, worldStateHandle, optional logLevel, optional loggerFunction, and optional cancellationToken.");
186 }
187
188 /*******************************
189 *** AvmFastSimulationInputs ***
190 *******************************/
191 if (!cb_info[0].IsBuffer()) {
192 throw Napi::TypeError::New(env,
193 "First argument must be a Buffer containing serialized AvmFastSimulationInputs");
194 }
195 // Extract the inputs buffer
196 auto inputs_buffer = cb_info[0].As<Napi::Buffer<uint8_t>>();
197 size_t length = inputs_buffer.Length();
198 // Copy the buffer data into C++ memory (we can't access Napi objects from worker thread)
199 auto data = std::make_shared<std::vector<uint8_t>>(inputs_buffer.Data(), inputs_buffer.Data() + length);
200
201 /***********************************
202 *** ContractProvider (required) ***
203 ***********************************/
204 if (!cb_info[1].IsObject()) {
205 throw Napi::TypeError::New(env, "Second argument must be a contractProvider object");
206 }
207 // Extract and validate contract provider callbacks
208 auto contract_provider = cb_info[1].As<Napi::Object>();
209 ContractCallbacks::validate(env, contract_provider);
210 // Create thread-safe function wrappers for callbacks
211 // These allow us to call TypeScript from the C++ worker thread
212 ContractTsfns tsfns{
213 .instance = make_tsfn(env,
214 ContractCallbacks::get(contract_provider, CALLBACK_GET_CONTRACT_INSTANCE),
215 CALLBACK_GET_CONTRACT_INSTANCE),
216 .class_ = make_tsfn(
217 env, ContractCallbacks::get(contract_provider, CALLBACK_GET_CONTRACT_CLASS), CALLBACK_GET_CONTRACT_CLASS),
218 .add_contracts =
219 make_tsfn(env, ContractCallbacks::get(contract_provider, CALLBACK_ADD_CONTRACTS), CALLBACK_ADD_CONTRACTS),
220 .bytecode =
221 make_tsfn(env, ContractCallbacks::get(contract_provider, CALLBACK_GET_BYTECODE), CALLBACK_GET_BYTECODE),
222 .debug_name =
223 make_tsfn(env, ContractCallbacks::get(contract_provider, CALLBACK_GET_DEBUG_NAME), CALLBACK_GET_DEBUG_NAME),
224 .create_checkpoint = make_tsfn(
225 env, ContractCallbacks::get(contract_provider, CALLBACK_CREATE_CHECKPOINT), CALLBACK_CREATE_CHECKPOINT),
226 .commit_checkpoint = make_tsfn(
227 env, ContractCallbacks::get(contract_provider, CALLBACK_COMMIT_CHECKPOINT), CALLBACK_COMMIT_CHECKPOINT),
228 .revert_checkpoint = make_tsfn(
229 env, ContractCallbacks::get(contract_provider, CALLBACK_REVERT_CHECKPOINT), CALLBACK_REVERT_CHECKPOINT),
230 };
231
232 /**********************************
233 *** WSDB IPC path (required) ***
234 **********************************/
235 if (!cb_info[2].IsString()) {
236 throw Napi::TypeError::New(env, "Third argument must be a WSDB IPC path (string)");
237 }
238 std::string wsdb_ipc_path = cb_info[2].As<Napi::String>().Utf8Value();
239
240 /***************************
241 *** LogLevel (optional) ***
242 ***************************/
243 int log_level = -1;
244 if (cb_info.Length() > 3 && cb_info[3].IsNumber()) {
245 log_level = cb_info[3].As<Napi::Number>().Int32Value();
246 set_logging_from_level(log_level);
247 }
248
249 /*********************************
250 *** LoggerFunction (optional) ***
251 *********************************/
252 std::shared_ptr<Napi::ThreadSafeFunction> logger_tsfn = nullptr;
253 if (cb_info.Length() > 4 && !cb_info[4].IsNull() && !cb_info[4].IsUndefined()) {
254 if (cb_info[4].IsFunction()) {
255 // Logger function provided - create thread-safe wrapper
256 auto logger_function = cb_info[4].As<Napi::Function>();
257 logger_tsfn = make_tsfn(env, logger_function, "LoggerCallback");
258 // Create LogFunction wrapper and set it as the global log function
259 // This will be used by C++ logging macros (info, debug, vinfo, important)
260 set_log_function(create_log_function_from_tsfn(logger_tsfn));
261 } else {
262 throw Napi::TypeError::New(env, "Fifth argument must be a logger function, null, or undefined");
263 }
264 }
265
266 /*************************************
267 *** Cancellation Token (optional) ***
268 *************************************/
269 avm2::simulation::CancellationTokenPtr cancellation_token = nullptr;
270 if (cb_info.Length() > 5 && cb_info[5].IsExternal()) {
271 auto token_external = cb_info[5].As<Napi::External<avm2::simulation::CancellationToken>>();
272 // Wrap the raw pointer in a shared_ptr that does NOT delete (since the External owns it)
274 token_external.Data(), [](avm2::simulation::CancellationToken*) {
275 // No-op deleter: the External (via shared_ptr destructor callback) owns the token
276 });
277 }
278
279 /**********************************************************
280 *** Create Deferred Promise and launch async operation ***
281 **********************************************************/
282
285 env, deferred, [data, tsfns, logger_tsfn, wsdb_ipc_path, cancellation_token](msgpack::sbuffer& result_buffer) {
286 // Collect all thread-safe functions including logger for cleanup
287 auto all_tsfns = tsfns.to_vector();
288 all_tsfns.push_back(logger_tsfn);
289 // Ensure all thread-safe functions are released in all code paths
290 TsfnReleaser releaser = TsfnReleaser(std::move(all_tsfns));
291
292 try {
293 // Deserialize inputs from msgpack
295 msgpack::object_handle obj_handle =
296 msgpack::unpack(reinterpret_cast<const char*>(data->data()), data->size());
297 msgpack::object obj = obj_handle.get();
298 obj.convert(inputs);
299
300 // Create TsCallbackContractDB with TypeScript callbacks
301 TsCallbackContractDB contract_db(*tsfns.instance,
302 *tsfns.class_,
303 *tsfns.add_contracts,
304 *tsfns.bytecode,
305 *tsfns.debug_name,
306 *tsfns.create_checkpoint,
307 *tsfns.commit_checkpoint,
308 *tsfns.revert_checkpoint);
309
310 // Connect to aztec-wsdb and wrap in a WsdbIpcMerkleDB that implements
311 // LowLevelMerkleDBInterface. The connection is per-simulation; aztec-wsdb is a
312 // long-running server that the TS layer spawned and owns.
313 bb::wsdb::WsdbIpcClient wsdb_client(wsdb_ipc_path);
315
316 avm2::AvmSimAPI avm;
317 avm2::TxSimulationResult result = avm.simulate(inputs, contract_db, merkle_db, cancellation_token);
318
319 // Serialize the simulation result with msgpack into the return buffer to TS.
320 msgpack::pack(result_buffer, result);
321 } catch (const avm2::simulation::CancelledException& e) {
322 // Cancellation is an expected condition, rethrow with context
323 throw std::runtime_error("Simulation cancelled");
324 } catch (const std::exception& e) {
325 // Rethrow with context (RAII wrappers will clean up automatically)
326 throw std::runtime_error(std::string("AVM simulation failed: ") + e.what());
327 } catch (...) {
328 throw std::runtime_error("AVM simulation failed with unknown exception");
329 }
330 });
331
332 return deferred->Promise();
333}
334
335Napi::Value AvmSimulateNapi::simulateWithHintedDbs(const Napi::CallbackInfo& cb_info)
336{
337 Napi::Env env = cb_info.Env();
338
339 // Validate arguments - expects 2 arguments
340 // arg[0]: inputs Buffer (required) - AvmProvingInputs
341 // arg[1]: logLevel number (required) - index into TS LogLevels array
342 if (cb_info.Length() < 2) {
343 throw Napi::TypeError::New(env,
344 "Wrong number of arguments. Expected 2 arguments: AvmProvingInputs/AvmCircuitInputs "
345 "msgpack Buffer and logLevel.");
346 }
347
348 if (!cb_info[0].IsBuffer()) {
349 throw Napi::TypeError::New(
350 env, "First argument must be a Buffer containing serialized AvmProvingInputs/AvmCircuitInputs");
351 }
352
353 if (!cb_info[1].IsNumber()) {
354 throw Napi::TypeError::New(env, "Second argument must be a log level number (0-7)");
355 }
356
357 // Extract log level and set logging flags
358 int log_level = cb_info[1].As<Napi::Number>().Int32Value();
359 set_logging_from_level(log_level);
360
361 // Extract the inputs buffer
362 auto inputs_buffer = cb_info[0].As<Napi::Buffer<uint8_t>>();
363 size_t length = inputs_buffer.Length();
364
365 // Copy the buffer data into C++ memory (we can't access Napi objects from worker thread)
366 auto data = std::make_shared<std::vector<uint8_t>>(inputs_buffer.Data(), inputs_buffer.Data() + length);
367
368 // Create a deferred promise
370
371 // Run on a dedicated std::thread (not libuv pool)
372 ThreadedAsyncOperation::Run(env, deferred, [data](msgpack::sbuffer& result_buffer) {
373 try {
374 // Deserialize inputs from msgpack
376 msgpack::object_handle obj_handle =
377 msgpack::unpack(reinterpret_cast<const char*>(data->data()), data->size());
378 msgpack::object obj = obj_handle.get();
379 obj.convert(inputs);
380
381 // Create AVM Sim API and run simulation with the hinted DBs
382 // All hints are already in the inputs, so no runtime contract DB callbacks needed
383 avm2::AvmSimAPI avm;
385
386 // Serialize the simulation result with msgpack into the return buffer to TS.
387 msgpack::pack(result_buffer, result);
388 } catch (const std::exception& e) {
389 // Rethrow with context
390 throw std::runtime_error(std::string("AVM simulation with hinted DBs failed: ") + e.what());
391 } catch (...) {
392 throw std::runtime_error("AVM simulation with hinted DBs failed with unknown exception");
393 }
394 });
395
396 return deferred->Promise();
397}
398
399Napi::Value AvmSimulateNapi::createCancellationToken(const Napi::CallbackInfo& cb_info)
400{
401 Napi::Env env = cb_info.Env();
402
403 // Create a new CancellationToken. We use a shared_ptr to manage the lifetime,
404 // and the destructor callback in the External will clean it up when GC runs.
405 auto* token = new avm2::simulation::CancellationToken();
406
407 // Create an External with a destructor callback that deletes the token
408 return Napi::External<avm2::simulation::CancellationToken>::New(
409 env, token, [](Napi::Env /*env*/, avm2::simulation::CancellationToken* t) { delete t; });
410}
411
412Napi::Value AvmSimulateNapi::cancelSimulation(const Napi::CallbackInfo& cb_info)
413{
414 Napi::Env env = cb_info.Env();
415
416 if (cb_info.Length() < 1 || !cb_info[0].IsExternal()) {
417 throw Napi::TypeError::New(env, "Expected a CancellationToken External as argument");
418 }
419
420 auto token_external = cb_info[0].As<Napi::External<avm2::simulation::CancellationToken>>();
421 avm2::simulation::CancellationToken* token = token_external.Data();
422
423 // Signal cancellation - this is thread-safe (atomic store)
424 token->cancel();
425
426 return env.Undefined();
427}
428
429} // namespace bb::nodejs
std::shared_ptr< Napi::ThreadSafeFunction > class_
std::shared_ptr< Napi::ThreadSafeFunction > debug_name
std::shared_ptr< Napi::ThreadSafeFunction > instance
std::vector< std::shared_ptr< Napi::ThreadSafeFunction > > tsfns_
std::shared_ptr< Napi::ThreadSafeFunction > revert_checkpoint
std::shared_ptr< Napi::ThreadSafeFunction > commit_checkpoint
std::shared_ptr< Napi::ThreadSafeFunction > create_checkpoint
std::shared_ptr< Napi::ThreadSafeFunction > bytecode
std::shared_ptr< Napi::ThreadSafeFunction > add_contracts
StrictMock< MockHighLevelMerkleDB > merkle_db
StrictMock< MockContractDB > contract_db
TxSimulationResult simulate_with_hinted_dbs(const AvmProvingInputs &inputs)
TxSimulationResult simulate(const FastSimulationInputs &inputs, simulation::ContractDBInterface &contract_db, simulation::LowLevelMerkleDBInterface &merkle_db, simulation::CancellationTokenPtr cancellation_token=nullptr)
A thread-safe cancellation token for C++ AVM simulation.
void cancel()
Signal cancellation. Called from TypeScript thread.
Exception thrown when simulation is cancelled.
static Napi::Value simulate(const Napi::CallbackInfo &info)
NAPI function to simulate AVM execution.
static Napi::Value simulateWithHintedDbs(const Napi::CallbackInfo &info)
NAPI function to simulate AVM execution with pre-collected hints.
static Napi::Value createCancellationToken(const Napi::CallbackInfo &info)
Create a cancellation token that can be used to cancel a simulation.
static Napi::Value cancelSimulation(const Napi::CallbackInfo &info)
Cancel a simulation by signaling the provided cancellation token.
static void Run(Napi::Env env, std::shared_ptr< Napi::Promise::Deferred > deferred, async_fn fn)
Definition async_op.hpp:110
Implementation of ContractDBInterface that uses NAPI callbacks to TypeScript.
#define log_warn(...)
Definition log.hpp:91
std::function< void(LogLevel level, const std::string &msg)> LogFunction
Definition log.hpp:76
LogLevel
Definition log.hpp:63
AvmProvingInputs inputs
LogLevel bb_log_level
Definition log.cpp:9
void set_log_function(LogFunction new_log_function)
Definition log.cpp:21
std::shared_ptr< CancellationToken > CancellationTokenPtr
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::byte * data
LowLevelMerkleDBInterface implementation backed by WSDB IPC.