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
21
22// Helper to set logging level based on TS log level
23inline void set_logging_from_level(int ts_log_level)
24{
25 // Map TS log level (0-7) to C++ LogLevel enum
26 // TS: 0=silent, 1=fatal, 2=error, 3=warn, 4=info, 5=verbose, 6=debug, 7=trace
27 // C++: SILENT=0, FATAL=1, ERROR=2, WARN=3, INFO=4, VERBOSE=5, DEBUG=6, TRACE=7
28 // They map 1:1
29 if (ts_log_level >= 0 && ts_log_level <= 7) {
30 bb_log_level = static_cast<LogLevel>(ts_log_level);
31 } else {
32 log_warn("Invalid log level from TypeScript: ", ts_log_level, ". Using default.");
33 }
34}
35
36// Map C++ LogLevel enum to TypeScript log level string
37// C++ LogLevel: SILENT=0, FATAL=1, ERROR=2, WARN=3, INFO=4, VERBOSE=5, DEBUG=6, TRACE=7
38// TS LogLevels: ['silent', 'fatal', 'error', 'warn', 'info', 'verbose', 'debug', 'trace']
39inline const char* cpp_log_level_to_ts(LogLevel level)
40{
41 switch (level) {
43 return "silent";
44 case LogLevel::FATAL:
45 return "fatal";
46 case LogLevel::ERROR:
47 return "error";
48 case LogLevel::WARN:
49 return "warn";
50 case LogLevel::INFO:
51 return "info";
53 return "verbose";
54 case LogLevel::DEBUG:
55 return "debug";
56 case LogLevel::TRACE:
57 return "trace";
58 default:
59 return "info";
60 }
61}
62
63// Helper to create a LogFunction wrapper from a ThreadSafeFunction
64// This allows C++ logging to call back to TypeScript logger from worker threads
65LogFunction create_log_function_from_tsfn(const std::shared_ptr<Napi::ThreadSafeFunction>& logger_tsfn)
66{
67 return [logger_tsfn](LogLevel level, const std::string& msg) {
68 // Convert C++ LogLevel to TS log level string
69 const char* ts_level = cpp_log_level_to_ts(level);
70
71 // Call TypeScript logger function on the JS main thread
72 // Using BlockingCall to ensure synchronous execution
73 // Ignore errors - logging failures shouldn't crash the simulation
74 // NOTE: We copy the string because it might be destroyed before the callback is called.
75 logger_tsfn->BlockingCall([ts_level, msg](Napi::Env env, Napi::Function js_logger) {
76 // Create arguments: (level: string, msg: string)
77 auto level_js = Napi::String::New(env, ts_level);
78 auto msg_js = Napi::String::New(env, msg);
79 js_logger.Call({ level_js, msg_js });
80 });
81 };
82}
83
84// Callback method names
85constexpr const char* CALLBACK_GET_CONTRACT_INSTANCE = "getContractInstance";
86constexpr const char* CALLBACK_GET_CONTRACT_CLASS = "getContractClass";
87constexpr const char* CALLBACK_ADD_CONTRACTS = "addContracts";
88constexpr const char* CALLBACK_GET_BYTECODE = "getBytecodeCommitment";
89constexpr const char* CALLBACK_GET_DEBUG_NAME = "getDebugFunctionName";
90constexpr const char* CALLBACK_CREATE_CHECKPOINT = "createCheckpoint";
91constexpr const char* CALLBACK_COMMIT_CHECKPOINT = "commitCheckpoint";
92constexpr const char* CALLBACK_REVERT_CHECKPOINT = "revertCheckpoint";
93
94// RAII helper to automatically release thread-safe functions
95// Used inside the async lambda to ensure cleanup in all code paths
96class TsfnReleaser {
98
99 public:
100 explicit TsfnReleaser(std::vector<std::shared_ptr<Napi::ThreadSafeFunction>> tsfns)
101 : tsfns_(std::move(tsfns))
102 {}
103
104 ~TsfnReleaser()
105 {
106 for (auto& tsfn : tsfns_) {
107 if (tsfn) {
108 tsfn->Release();
109 }
110 }
111 }
112
113 // Prevent copying and moving
114 TsfnReleaser(const TsfnReleaser&) = delete;
115 TsfnReleaser& operator=(const TsfnReleaser&) = delete;
116 TsfnReleaser(TsfnReleaser&&) = delete;
117 TsfnReleaser& operator=(TsfnReleaser&&) = delete;
118};
119
120// Helper to create thread-safe function wrapper
121inline std::shared_ptr<Napi::ThreadSafeFunction> make_tsfn(Napi::Env env, Napi::Function fn, const char* name)
122{
123 return std::make_shared<Napi::ThreadSafeFunction>(Napi::ThreadSafeFunction::New(env, fn, name, 0, 1));
124}
125
126// Bundle all contract-related thread-safe functions with named access
127struct ContractTsfns {
136
138 {
141 }
142};
143
144// Helper to validate and extract contract provider callbacks
145struct ContractCallbacks {
146 static constexpr const char* ALL_METHODS[] = { CALLBACK_GET_CONTRACT_INSTANCE, CALLBACK_GET_CONTRACT_CLASS,
147 CALLBACK_ADD_CONTRACTS, CALLBACK_GET_BYTECODE,
148 CALLBACK_GET_DEBUG_NAME, CALLBACK_CREATE_CHECKPOINT,
149 CALLBACK_COMMIT_CHECKPOINT, CALLBACK_REVERT_CHECKPOINT };
150
151 static void validate(Napi::Env env, Napi::Object provider)
152 {
153 for (const char* method : ALL_METHODS) {
154 if (!provider.Has(method)) {
155 throw Napi::TypeError::New(
156 env, std::string("contractProvider must have ") + method + " method. Missing methods: " + method);
157 }
158 }
159 }
160
161 static Napi::Function get(Napi::Object provider, const char* name)
162 {
163 return provider.Get(name).As<Napi::Function>();
164 }
165};
166} // namespace
167
168Napi::Value AvmSimulateNapi::simulate(const Napi::CallbackInfo& cb_info)
169{
170 Napi::Env env = cb_info.Env();
171
172 // Validate arguments - expects 3-6 arguments
173 // arg[0]: inputs Buffer (required)
174 // arg[1]: contractProvider object (required)
175 // arg[2]: worldStateHandle external (required)
176 // arg[3]: logLevel number (optional) - index into TS LogLevels array, -1 if omitted
177 // arg[4]: loggerFunction (optional) - can be null/undefined
178 // arg[5]: cancellationToken external (optional)
179 if (cb_info.Length() < 3) {
180 throw Napi::TypeError::New(
181 env,
182 "Wrong number of arguments. Expected 3-6 arguments: inputs Buffer, contractProvider "
183 "object, worldStateHandle, optional logLevel, optional loggerFunction, and optional cancellationToken.");
184 }
185
186 /*******************************
187 *** AvmFastSimulationInputs ***
188 *******************************/
189 if (!cb_info[0].IsBuffer()) {
190 throw Napi::TypeError::New(env,
191 "First argument must be a Buffer containing serialized AvmFastSimulationInputs");
192 }
193 // Extract the inputs buffer
194 auto inputs_buffer = cb_info[0].As<Napi::Buffer<uint8_t>>();
195 size_t length = inputs_buffer.Length();
196 // Copy the buffer data into C++ memory (we can't access Napi objects from worker thread)
197 auto data = std::make_shared<std::vector<uint8_t>>(inputs_buffer.Data(), inputs_buffer.Data() + length);
198
199 /***********************************
200 *** ContractProvider (required) ***
201 ***********************************/
202 if (!cb_info[1].IsObject()) {
203 throw Napi::TypeError::New(env, "Second argument must be a contractProvider object");
204 }
205 // Extract and validate contract provider callbacks
206 auto contract_provider = cb_info[1].As<Napi::Object>();
207 ContractCallbacks::validate(env, contract_provider);
208 // Create thread-safe function wrappers for callbacks
209 // These allow us to call TypeScript from the C++ worker thread
210 ContractTsfns tsfns{
211 .instance = make_tsfn(env,
212 ContractCallbacks::get(contract_provider, CALLBACK_GET_CONTRACT_INSTANCE),
213 CALLBACK_GET_CONTRACT_INSTANCE),
214 .class_ = make_tsfn(
215 env, ContractCallbacks::get(contract_provider, CALLBACK_GET_CONTRACT_CLASS), CALLBACK_GET_CONTRACT_CLASS),
216 .add_contracts =
217 make_tsfn(env, ContractCallbacks::get(contract_provider, CALLBACK_ADD_CONTRACTS), CALLBACK_ADD_CONTRACTS),
218 .bytecode =
219 make_tsfn(env, ContractCallbacks::get(contract_provider, CALLBACK_GET_BYTECODE), CALLBACK_GET_BYTECODE),
220 .debug_name =
221 make_tsfn(env, ContractCallbacks::get(contract_provider, CALLBACK_GET_DEBUG_NAME), CALLBACK_GET_DEBUG_NAME),
222 .create_checkpoint = make_tsfn(
223 env, ContractCallbacks::get(contract_provider, CALLBACK_CREATE_CHECKPOINT), CALLBACK_CREATE_CHECKPOINT),
224 .commit_checkpoint = make_tsfn(
225 env, ContractCallbacks::get(contract_provider, CALLBACK_COMMIT_CHECKPOINT), CALLBACK_COMMIT_CHECKPOINT),
226 .revert_checkpoint = make_tsfn(
227 env, ContractCallbacks::get(contract_provider, CALLBACK_REVERT_CHECKPOINT), CALLBACK_REVERT_CHECKPOINT),
228 };
229
230 /*****************************
231 *** WorldState (required) ***
232 *****************************/
233 if (!cb_info[2].IsExternal()) {
234 throw Napi::TypeError::New(env, "Third argument must be a WorldState handle (External)");
235 }
236 // Extract WorldState handle (3rd argument)
237 auto external = cb_info[2].As<Napi::External<world_state::WorldState>>();
238 world_state::WorldState* ws_ptr = external.Data();
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
284 // Create async operation that will run on a worker thread
285 auto* op = new AsyncOperation(
286 env, deferred, [data, tsfns, logger_tsfn, ws_ptr, cancellation_token](msgpack::sbuffer& result_buffer) {
287 // Collect all thread-safe functions including logger for cleanup
288 auto all_tsfns = tsfns.to_vector();
289 all_tsfns.push_back(logger_tsfn);
290 // Ensure all thread-safe functions are released in all code paths
291 TsfnReleaser releaser = TsfnReleaser(std::move(all_tsfns));
292
293 try {
294 // Deserialize inputs from msgpack
296 msgpack::object_handle obj_handle =
297 msgpack::unpack(reinterpret_cast<const char*>(data->data()), data->size());
298 msgpack::object obj = obj_handle.get();
299 obj.convert(inputs);
300
301 // Create TsCallbackContractDB with TypeScript callbacks
302 TsCallbackContractDB contract_db(*tsfns.instance,
303 *tsfns.class_,
304 *tsfns.add_contracts,
305 *tsfns.bytecode,
306 *tsfns.debug_name,
307 *tsfns.create_checkpoint,
308 *tsfns.commit_checkpoint,
309 *tsfns.revert_checkpoint);
310
311 // Create AVM API and run simulation with the callback-based contracts DB,
312 // WorldState reference, and optional cancellation token
313 avm2::AvmSimAPI avm;
314 avm2::TxSimulationResult result = avm.simulate(inputs, contract_db, *ws_ptr, cancellation_token);
315
316 // Serialize the simulation result with msgpack into the return buffer to TS.
317 msgpack::pack(result_buffer, result);
318 } catch (const avm2::simulation::CancelledException& e) {
319 // Cancellation is an expected condition, rethrow with context
320 throw std::runtime_error("Simulation cancelled");
321 } catch (const std::exception& e) {
322 // Rethrow with context (RAII wrappers will clean up automatically)
323 throw std::runtime_error(std::string("AVM simulation failed: ") + e.what());
324 } catch (...) {
325 throw std::runtime_error("AVM simulation failed with unknown exception");
326 }
327 });
328
329 // Napi is now responsible for destroying this object
330 op->Queue();
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 // Create async operation that will run on a worker thread
372 auto* op = new AsyncOperation(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 // Napi is now responsible for destroying this object
397 op->Queue();
398
399 return deferred->Promise();
400}
401
402Napi::Value AvmSimulateNapi::createCancellationToken(const Napi::CallbackInfo& cb_info)
403{
404 Napi::Env env = cb_info.Env();
405
406 // Create a new CancellationToken. We use a shared_ptr to manage the lifetime,
407 // and the destructor callback in the External will clean it up when GC runs.
408 auto* token = new avm2::simulation::CancellationToken();
409
410 // Create an External with a destructor callback that deletes the token
411 return Napi::External<avm2::simulation::CancellationToken>::New(
412 env, token, [](Napi::Env /*env*/, avm2::simulation::CancellationToken* t) { delete t; });
413}
414
415Napi::Value AvmSimulateNapi::cancelSimulation(const Napi::CallbackInfo& cb_info)
416{
417 Napi::Env env = cb_info.Env();
418
419 if (cb_info.Length() < 1 || !cb_info[0].IsExternal()) {
420 throw Napi::TypeError::New(env, "Expected a CancellationToken External as argument");
421 }
422
423 auto token_external = cb_info[0].As<Napi::External<avm2::simulation::CancellationToken>>();
424 avm2::simulation::CancellationToken* token = token_external.Data();
425
426 // Signal cancellation - this is thread-safe (atomic store)
427 token->cancel();
428
429 return env.Undefined();
430}
431
432} // 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.
#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
const std::vector< MemoryValue > data
uint8_t const size_t length
Definition data_store.hpp:9
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