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