@nomicfoundation/edr 0.3.2 → 0.3.4
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.
- package/Cargo.toml +4 -3
- package/index.d.ts +1 -1
- package/package.json +12 -12
- package/src/call_override.rs +28 -24
- package/src/lib.rs +3 -2
- package/src/log.rs +4 -15
- package/src/logger.rs +92 -134
- package/src/provider/config.rs +19 -2
- package/src/result.rs +49 -71
- package/src/subscribe.rs +15 -12
- package/src/trace.rs +8 -28
- package/src/sync.rs +0 -85
- package/src/threadsafe_function.rs +0 -305
package/Cargo.toml
CHANGED
|
@@ -14,8 +14,8 @@ k256 = { version = "0.13.1", default-features = false, features = ["arithmetic",
|
|
|
14
14
|
log = { version = "0.4.20", default-features = false }
|
|
15
15
|
# when napi is pinned, be sure to pin napi-derive to the same version
|
|
16
16
|
# The `async` feature ensures that a tokio runtime is available
|
|
17
|
-
napi = { version = "2.
|
|
18
|
-
napi-derive = "2.
|
|
17
|
+
napi = { version = "2.16.0", default-features = false, features = ["async", "error_anyhow", "napi8", "serde-json"] }
|
|
18
|
+
napi-derive = "2.16.0"
|
|
19
19
|
edr_defaults = { version = "0.2.0-dev", path = "../edr_defaults" }
|
|
20
20
|
edr_evm = { version = "0.2.0-dev", path = "../edr_evm", features = ["tracing"]}
|
|
21
21
|
edr_eth = { version = "0.2.0-dev", path = "../edr_eth" }
|
|
@@ -29,6 +29,7 @@ parking_lot = { version = "0.12.1", default-features = false }
|
|
|
29
29
|
lazy_static = { version = "1.4.0", features = [] }
|
|
30
30
|
rand = { version = "0.8.4", optional = true }
|
|
31
31
|
serde = { version = "1.0.189", features = ["derive"] }
|
|
32
|
+
mimalloc = { version = "0.1.39", default-features = false }
|
|
32
33
|
|
|
33
34
|
[target.x86_64-unknown-linux-gnu.dependencies]
|
|
34
35
|
openssl-sys = { version = "0.9.93", features = ["vendored"] }
|
|
@@ -46,7 +47,7 @@ openssl-sys = { version = "0.9.93", features = ["vendored"] }
|
|
|
46
47
|
napi-build = "2.0.1"
|
|
47
48
|
|
|
48
49
|
[features]
|
|
49
|
-
tracing = ["edr_evm/tracing"]
|
|
50
|
+
tracing = ["edr_evm/tracing", "edr_provider/tracing"]
|
|
50
51
|
scenarios = ["rand"]
|
|
51
52
|
|
|
52
53
|
[profile.release]
|
package/index.d.ts
CHANGED
|
@@ -313,7 +313,7 @@ export const enum ExceptionalHalt {
|
|
|
313
313
|
/** Error on created contract that begins with EF */
|
|
314
314
|
CreateContractStartingWithEF = 12,
|
|
315
315
|
/** EIP-3860: Limit and meter initcode. Initcode size limit exceeded. */
|
|
316
|
-
|
|
316
|
+
CreateInitCodeSizeLimit = 13
|
|
317
317
|
}
|
|
318
318
|
/** The result when the EVM terminates due to an exceptional halt. */
|
|
319
319
|
export interface HaltResult {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nomicfoundation/edr",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -43,15 +43,15 @@
|
|
|
43
43
|
"node": ">= 18"
|
|
44
44
|
},
|
|
45
45
|
"optionalDependencies": {
|
|
46
|
-
"@nomicfoundation/edr-win32-x64-msvc": "0.3.
|
|
47
|
-
"@nomicfoundation/edr-darwin-x64": "0.3.
|
|
48
|
-
"@nomicfoundation/edr-linux-x64-gnu": "0.3.
|
|
49
|
-
"@nomicfoundation/edr-darwin-arm64": "0.3.
|
|
50
|
-
"@nomicfoundation/edr-win32-arm64-msvc": "0.3.
|
|
51
|
-
"@nomicfoundation/edr-linux-arm64-gnu": "0.3.
|
|
52
|
-
"@nomicfoundation/edr-linux-arm64-musl": "0.3.
|
|
53
|
-
"@nomicfoundation/edr-linux-x64-musl": "0.3.
|
|
54
|
-
"@nomicfoundation/edr-win32-ia32-msvc": "0.3.
|
|
46
|
+
"@nomicfoundation/edr-win32-x64-msvc": "0.3.4",
|
|
47
|
+
"@nomicfoundation/edr-darwin-x64": "0.3.4",
|
|
48
|
+
"@nomicfoundation/edr-linux-x64-gnu": "0.3.4",
|
|
49
|
+
"@nomicfoundation/edr-darwin-arm64": "0.3.4",
|
|
50
|
+
"@nomicfoundation/edr-win32-arm64-msvc": "0.3.4",
|
|
51
|
+
"@nomicfoundation/edr-linux-arm64-gnu": "0.3.4",
|
|
52
|
+
"@nomicfoundation/edr-linux-arm64-musl": "0.3.4",
|
|
53
|
+
"@nomicfoundation/edr-linux-x64-musl": "0.3.4",
|
|
54
|
+
"@nomicfoundation/edr-win32-ia32-msvc": "0.3.4"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"artifacts": "napi artifacts",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"universal": "napi universal",
|
|
63
63
|
"version": "napi version",
|
|
64
64
|
"pretest": "pnpm build",
|
|
65
|
-
"test": "pnpm tsc && mocha --recursive \"test/**/*.ts\"
|
|
66
|
-
"testNoBuild": "pnpm tsc && mocha --recursive \"test/**/*.ts\"
|
|
65
|
+
"test": "pnpm tsc && mocha --recursive \"test/**/*.ts\"",
|
|
66
|
+
"testNoBuild": "pnpm tsc && mocha --recursive \"test/**/*.ts\"",
|
|
67
67
|
"clean": "rm -rf @nomicfoundation/edr.node"
|
|
68
68
|
}
|
|
69
69
|
}
|
package/src/call_override.rs
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
use std::sync::mpsc::
|
|
1
|
+
use std::sync::mpsc::channel;
|
|
2
2
|
|
|
3
3
|
use edr_eth::{Address, Bytes};
|
|
4
|
-
use napi::{
|
|
4
|
+
use napi::{
|
|
5
|
+
bindgen_prelude::Buffer,
|
|
6
|
+
threadsafe_function::{
|
|
7
|
+
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
|
|
8
|
+
},
|
|
9
|
+
Env, JsFunction, Status,
|
|
10
|
+
};
|
|
5
11
|
use napi_derive::napi;
|
|
6
12
|
|
|
7
|
-
use crate::
|
|
8
|
-
cast::TryCast,
|
|
9
|
-
sync::{await_promise, handle_error},
|
|
10
|
-
threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode},
|
|
11
|
-
};
|
|
13
|
+
use crate::cast::TryCast;
|
|
12
14
|
|
|
13
15
|
/// The result of executing a call override.
|
|
14
16
|
#[napi(object)]
|
|
@@ -24,7 +26,7 @@ impl TryCast<Option<edr_provider::CallOverrideResult>> for Option<CallOverrideRe
|
|
|
24
26
|
match self {
|
|
25
27
|
None => Ok(None),
|
|
26
28
|
Some(result) => Ok(Some(edr_provider::CallOverrideResult {
|
|
27
|
-
|
|
29
|
+
output: result.result.try_cast()?,
|
|
28
30
|
should_revert: result.should_revert,
|
|
29
31
|
})),
|
|
30
32
|
}
|
|
@@ -34,20 +36,16 @@ impl TryCast<Option<edr_provider::CallOverrideResult>> for Option<CallOverrideRe
|
|
|
34
36
|
struct CallOverrideCall {
|
|
35
37
|
contract_address: Address,
|
|
36
38
|
data: Bytes,
|
|
37
|
-
sender: Sender<napi::Result<Option<edr_provider::CallOverrideResult>>>,
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
#[derive(Clone)]
|
|
41
42
|
pub struct CallOverrideCallback {
|
|
42
|
-
call_override_callback_fn: ThreadsafeFunction<CallOverrideCall>,
|
|
43
|
+
call_override_callback_fn: ThreadsafeFunction<CallOverrideCall, ErrorStrategy::Fatal>,
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
impl CallOverrideCallback {
|
|
46
47
|
pub fn new(env: &Env, call_override_callback: JsFunction) -> napi::Result<Self> {
|
|
47
|
-
let call_override_callback_fn =
|
|
48
|
-
env.raw(),
|
|
49
|
-
// SAFETY: The callback is guaranteed to be valid for the lifetime of the inspector.
|
|
50
|
-
unsafe { call_override_callback.raw() },
|
|
48
|
+
let mut call_override_callback_fn = call_override_callback.create_threadsafe_function(
|
|
51
49
|
0,
|
|
52
50
|
|ctx: ThreadSafeCallContext<CallOverrideCall>| {
|
|
53
51
|
let address = ctx
|
|
@@ -60,17 +58,14 @@ impl CallOverrideCallback {
|
|
|
60
58
|
.create_buffer_with_data(ctx.value.data.to_vec())?
|
|
61
59
|
.into_raw();
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
let promise = ctx.callback.call(None, &[address, data])?;
|
|
65
|
-
let result = await_promise::<
|
|
66
|
-
Option<CallOverrideResult>,
|
|
67
|
-
Option<edr_provider::CallOverrideResult>,
|
|
68
|
-
>(ctx.env, promise, ctx.value.sender);
|
|
69
|
-
|
|
70
|
-
handle_error(sender, result)
|
|
61
|
+
Ok(vec![address, data])
|
|
71
62
|
},
|
|
72
63
|
)?;
|
|
73
64
|
|
|
65
|
+
// Maintain a weak reference to the function to avoid the event loop from
|
|
66
|
+
// exiting.
|
|
67
|
+
call_override_callback_fn.unref(env)?;
|
|
68
|
+
|
|
74
69
|
Ok(Self {
|
|
75
70
|
call_override_callback_fn,
|
|
76
71
|
})
|
|
@@ -83,13 +78,22 @@ impl CallOverrideCallback {
|
|
|
83
78
|
) -> Option<edr_provider::CallOverrideResult> {
|
|
84
79
|
let (sender, receiver) = channel();
|
|
85
80
|
|
|
86
|
-
let status = self.call_override_callback_fn.
|
|
81
|
+
let status = self.call_override_callback_fn.call_with_return_value(
|
|
87
82
|
CallOverrideCall {
|
|
88
83
|
contract_address,
|
|
89
84
|
data,
|
|
90
|
-
sender,
|
|
91
85
|
},
|
|
92
86
|
ThreadsafeFunctionCallMode::Blocking,
|
|
87
|
+
move |result: Option<CallOverrideResult>| {
|
|
88
|
+
let result = result.try_cast();
|
|
89
|
+
|
|
90
|
+
sender.send(result).map_err(|_error| {
|
|
91
|
+
napi::Error::new(
|
|
92
|
+
Status::GenericFailure,
|
|
93
|
+
"Failed to send result from call_override_callback",
|
|
94
|
+
)
|
|
95
|
+
})
|
|
96
|
+
},
|
|
93
97
|
);
|
|
94
98
|
|
|
95
99
|
assert_eq!(status, Status::Ok, "Call override callback failed");
|
package/src/lib.rs
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
//! NAPI bindings for the EDR EVM
|
|
4
4
|
|
|
5
|
+
#[global_allocator]
|
|
6
|
+
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
|
7
|
+
|
|
5
8
|
mod account;
|
|
6
9
|
mod block;
|
|
7
10
|
mod call_override;
|
|
@@ -16,7 +19,5 @@ mod result;
|
|
|
16
19
|
#[cfg(feature = "scenarios")]
|
|
17
20
|
mod scenarios;
|
|
18
21
|
mod subscribe;
|
|
19
|
-
mod sync;
|
|
20
|
-
mod threadsafe_function;
|
|
21
22
|
mod trace;
|
|
22
23
|
mod withdrawal;
|
package/src/log.rs
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
use std::mem;
|
|
2
|
-
|
|
3
1
|
use napi::{bindgen_prelude::Buffer, Env, JsBuffer, JsBufferValue};
|
|
4
2
|
use napi_derive::napi;
|
|
5
3
|
|
|
@@ -14,23 +12,14 @@ pub struct ExecutionLog {
|
|
|
14
12
|
impl ExecutionLog {
|
|
15
13
|
pub fn new(env: &Env, log: &edr_evm::Log) -> napi::Result<Self> {
|
|
16
14
|
let topics = log
|
|
17
|
-
.topics
|
|
15
|
+
.topics()
|
|
18
16
|
.iter()
|
|
19
17
|
.map(|topic| Buffer::from(topic.as_slice()))
|
|
20
18
|
.collect();
|
|
21
19
|
|
|
22
|
-
let data =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
data.as_ptr(),
|
|
26
|
-
data.len(),
|
|
27
|
-
data,
|
|
28
|
-
|data: edr_eth::Bytes, _env| {
|
|
29
|
-
mem::drop(data);
|
|
30
|
-
},
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
.map(JsBufferValue::into_raw)?;
|
|
20
|
+
let data = env
|
|
21
|
+
.create_buffer_with_data(log.data.data.to_vec())
|
|
22
|
+
.map(JsBufferValue::into_raw)?;
|
|
34
23
|
|
|
35
24
|
Ok(Self {
|
|
36
25
|
address: Buffer::from(log.address.as_slice()),
|
package/src/logger.rs
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
use std::{
|
|
2
|
-
fmt::Display,
|
|
3
|
-
sync::mpsc::{channel, Sender},
|
|
4
|
-
};
|
|
1
|
+
use std::{fmt::Display, sync::mpsc::channel};
|
|
5
2
|
|
|
6
3
|
use ansi_term::{Color, Style};
|
|
7
4
|
use edr_eth::{Bytes, B256, U256};
|
|
@@ -13,14 +10,15 @@ use edr_evm::{
|
|
|
13
10
|
};
|
|
14
11
|
use edr_provider::{ProviderError, TransactionFailure};
|
|
15
12
|
use itertools::izip;
|
|
16
|
-
use napi::{
|
|
13
|
+
use napi::{
|
|
14
|
+
threadsafe_function::{
|
|
15
|
+
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
|
|
16
|
+
},
|
|
17
|
+
Env, JsFunction, Status,
|
|
18
|
+
};
|
|
17
19
|
use napi_derive::napi;
|
|
18
20
|
|
|
19
|
-
use crate::
|
|
20
|
-
cast::TryCast,
|
|
21
|
-
sync::{await_promise, handle_error},
|
|
22
|
-
threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode},
|
|
23
|
-
};
|
|
21
|
+
use crate::cast::TryCast;
|
|
24
22
|
|
|
25
23
|
#[napi(object)]
|
|
26
24
|
pub struct ContractAndFunctionName {
|
|
@@ -38,16 +36,10 @@ impl TryCast<(String, Option<String>)> for ContractAndFunctionName {
|
|
|
38
36
|
}
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
struct DecodeConsoleLogInputsCall {
|
|
42
|
-
inputs: Vec<Bytes>,
|
|
43
|
-
sender: Sender<napi::Result<Vec<String>>>,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
39
|
struct ContractAndFunctionNameCall {
|
|
47
40
|
code: Bytes,
|
|
48
41
|
/// Only present for calls.
|
|
49
42
|
calldata: Option<Bytes>,
|
|
50
|
-
sender: Sender<napi::Result<(String, Option<String>)>>,
|
|
51
43
|
}
|
|
52
44
|
|
|
53
45
|
#[napi(object)]
|
|
@@ -247,116 +239,71 @@ pub struct CollapsedMethod {
|
|
|
247
239
|
|
|
248
240
|
#[derive(Clone)]
|
|
249
241
|
struct LogCollector {
|
|
250
|
-
decode_console_log_inputs_fn: ThreadsafeFunction<
|
|
251
|
-
get_contract_and_function_name_fn:
|
|
242
|
+
decode_console_log_inputs_fn: ThreadsafeFunction<Vec<Bytes>, ErrorStrategy::Fatal>,
|
|
243
|
+
get_contract_and_function_name_fn:
|
|
244
|
+
ThreadsafeFunction<ContractAndFunctionNameCall, ErrorStrategy::Fatal>,
|
|
252
245
|
indentation: usize,
|
|
253
246
|
is_enabled: bool,
|
|
254
247
|
logs: Vec<LogLine>,
|
|
255
|
-
print_line_fn: ThreadsafeFunction<(String, bool)>,
|
|
248
|
+
print_line_fn: ThreadsafeFunction<(String, bool), ErrorStrategy::Fatal>,
|
|
256
249
|
state: LoggingState,
|
|
257
250
|
title_length: usize,
|
|
258
251
|
}
|
|
259
252
|
|
|
260
253
|
impl LogCollector {
|
|
261
254
|
pub fn new(env: &Env, config: LoggerConfig) -> napi::Result<Self> {
|
|
262
|
-
let decode_console_log_inputs_fn =
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
for (idx, input) in ctx.value.inputs.into_iter().enumerate() {
|
|
274
|
-
// SAFETY: The input is guaranteed to be valid for the lifetime of the
|
|
275
|
-
// JS buffer.
|
|
276
|
-
unsafe {
|
|
277
|
-
ctx.env.create_buffer_with_borrowed_data(
|
|
278
|
-
input.as_ptr(),
|
|
279
|
-
input.len(),
|
|
280
|
-
input,
|
|
281
|
-
|input: Bytes, _env| {
|
|
282
|
-
std::mem::drop(input);
|
|
283
|
-
},
|
|
284
|
-
)
|
|
255
|
+
let mut decode_console_log_inputs_fn = config
|
|
256
|
+
.decode_console_log_inputs_callback
|
|
257
|
+
.create_threadsafe_function(0, |ctx: ThreadSafeCallContext<Vec<Bytes>>| {
|
|
258
|
+
let inputs =
|
|
259
|
+
ctx.env
|
|
260
|
+
.create_array_with_length(ctx.value.len())
|
|
261
|
+
.and_then(|mut inputs| {
|
|
262
|
+
for (idx, input) in ctx.value.into_iter().enumerate() {
|
|
263
|
+
ctx.env.create_buffer_with_data(input.to_vec()).and_then(
|
|
264
|
+
|input| inputs.set_element(idx as u32, input.into_raw()),
|
|
265
|
+
)?;
|
|
285
266
|
}
|
|
286
|
-
.and_then(|input| inputs.set_element(idx as u32, input.into_raw()))?;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
Ok(inputs)
|
|
290
|
-
})?;
|
|
291
|
-
|
|
292
|
-
let sender = ctx.value.sender.clone();
|
|
293
267
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
await_promise::<Vec<String>, Vec<String>>(ctx.env, promise, ctx.value.sender);
|
|
297
|
-
|
|
298
|
-
handle_error(sender, result)
|
|
299
|
-
},
|
|
300
|
-
)?;
|
|
301
|
-
|
|
302
|
-
let get_contract_and_function_name_fn = ThreadsafeFunction::create(
|
|
303
|
-
env.raw(),
|
|
304
|
-
// SAFETY: The callback is guaranteed to be valid for the lifetime of the tracer.
|
|
305
|
-
unsafe { config.get_contract_and_function_name_callback.raw() },
|
|
306
|
-
0,
|
|
307
|
-
|ctx: ThreadSafeCallContext<ContractAndFunctionNameCall>| {
|
|
308
|
-
// Buffer
|
|
309
|
-
let code = ctx.value.code;
|
|
310
|
-
// SAFETY: The code is guaranteed to be valid for the lifetime of the
|
|
311
|
-
// JS buffer.
|
|
312
|
-
let code = unsafe {
|
|
313
|
-
ctx.env.create_buffer_with_borrowed_data(
|
|
314
|
-
code.as_ptr(),
|
|
315
|
-
code.len(),
|
|
316
|
-
code,
|
|
317
|
-
|code: Bytes, _env| {
|
|
318
|
-
std::mem::drop(code);
|
|
319
|
-
},
|
|
320
|
-
)
|
|
321
|
-
}?
|
|
322
|
-
.into_unknown();
|
|
323
|
-
|
|
324
|
-
// Option<Buffer>
|
|
325
|
-
let calldata = if let Some(calldata) = ctx.value.calldata {
|
|
326
|
-
// SAFETY: The calldata is guaranteed to be valid for the lifetime of the
|
|
327
|
-
// JS buffer.
|
|
328
|
-
unsafe {
|
|
329
|
-
ctx.env.create_buffer_with_borrowed_data(
|
|
330
|
-
calldata.as_ptr(),
|
|
331
|
-
calldata.len(),
|
|
332
|
-
calldata,
|
|
333
|
-
|calldata: Bytes, _env| {
|
|
334
|
-
std::mem::drop(calldata);
|
|
335
|
-
},
|
|
336
|
-
)
|
|
337
|
-
}?
|
|
338
|
-
.into_unknown()
|
|
339
|
-
} else {
|
|
340
|
-
ctx.env.get_undefined()?.into_unknown()
|
|
341
|
-
};
|
|
268
|
+
Ok(inputs)
|
|
269
|
+
})?;
|
|
342
270
|
|
|
343
|
-
|
|
271
|
+
Ok(vec![inputs])
|
|
272
|
+
})?;
|
|
273
|
+
|
|
274
|
+
// Maintain a weak reference to the function to avoid the event loop from
|
|
275
|
+
// exiting.
|
|
276
|
+
decode_console_log_inputs_fn.unref(env)?;
|
|
277
|
+
|
|
278
|
+
let mut get_contract_and_function_name_fn = config
|
|
279
|
+
.get_contract_and_function_name_callback
|
|
280
|
+
.create_threadsafe_function(
|
|
281
|
+
0,
|
|
282
|
+
|ctx: ThreadSafeCallContext<ContractAndFunctionNameCall>| {
|
|
283
|
+
// Buffer
|
|
284
|
+
let code = ctx
|
|
285
|
+
.env
|
|
286
|
+
.create_buffer_with_data(ctx.value.code.to_vec())?
|
|
287
|
+
.into_unknown();
|
|
288
|
+
|
|
289
|
+
// Option<Buffer>
|
|
290
|
+
let calldata = if let Some(calldata) = ctx.value.calldata {
|
|
291
|
+
ctx.env
|
|
292
|
+
.create_buffer_with_data(calldata.to_vec())?
|
|
293
|
+
.into_unknown()
|
|
294
|
+
} else {
|
|
295
|
+
ctx.env.get_undefined()?.into_unknown()
|
|
296
|
+
};
|
|
344
297
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
promise,
|
|
349
|
-
ctx.value.sender,
|
|
350
|
-
);
|
|
298
|
+
Ok(vec![code, calldata])
|
|
299
|
+
},
|
|
300
|
+
)?;
|
|
351
301
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
)?;
|
|
302
|
+
// Maintain a weak reference to the function to avoid the event loop from
|
|
303
|
+
// exiting.
|
|
304
|
+
get_contract_and_function_name_fn.unref(env)?;
|
|
355
305
|
|
|
356
|
-
let print_line_fn =
|
|
357
|
-
env.raw(),
|
|
358
|
-
// SAFETY: The callback is guaranteed to be valid for the lifetime of the tracer.
|
|
359
|
-
unsafe { config.print_line_callback.raw() },
|
|
306
|
+
let mut print_line_fn = config.print_line_callback.create_threadsafe_function(
|
|
360
307
|
0,
|
|
361
308
|
|ctx: ThreadSafeCallContext<(String, bool)>| {
|
|
362
309
|
// String
|
|
@@ -365,12 +312,14 @@ impl LogCollector {
|
|
|
365
312
|
// bool
|
|
366
313
|
let replace = ctx.env.get_boolean(ctx.value.1)?;
|
|
367
314
|
|
|
368
|
-
|
|
369
|
-
.call(None, &[message.into_unknown(), replace.into_unknown()])?;
|
|
370
|
-
Ok(())
|
|
315
|
+
Ok(vec![message.into_unknown(), replace.into_unknown()])
|
|
371
316
|
},
|
|
372
317
|
)?;
|
|
373
318
|
|
|
319
|
+
// Maintain a weak reference to the function to avoid the event loop from
|
|
320
|
+
// exiting.
|
|
321
|
+
print_line_fn.unref(env)?;
|
|
322
|
+
|
|
374
323
|
Ok(Self {
|
|
375
324
|
decode_console_log_inputs_fn,
|
|
376
325
|
get_contract_and_function_name_fn,
|
|
@@ -599,14 +548,21 @@ impl LogCollector {
|
|
|
599
548
|
) -> (String, Option<String>) {
|
|
600
549
|
let (sender, receiver) = channel();
|
|
601
550
|
|
|
602
|
-
let status = self
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
calldata,
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
551
|
+
let status = self
|
|
552
|
+
.get_contract_and_function_name_fn
|
|
553
|
+
.call_with_return_value(
|
|
554
|
+
ContractAndFunctionNameCall { code, calldata },
|
|
555
|
+
ThreadsafeFunctionCallMode::Blocking,
|
|
556
|
+
move |result: ContractAndFunctionName| {
|
|
557
|
+
let contract_and_function_name = result.try_cast();
|
|
558
|
+
sender.send(contract_and_function_name).map_err(|_error| {
|
|
559
|
+
napi::Error::new(
|
|
560
|
+
Status::GenericFailure,
|
|
561
|
+
"Failed to send result from get_contract_and_function_name",
|
|
562
|
+
)
|
|
563
|
+
})
|
|
564
|
+
},
|
|
565
|
+
);
|
|
610
566
|
assert_eq!(status, Status::Ok);
|
|
611
567
|
|
|
612
568
|
receiver
|
|
@@ -793,19 +749,21 @@ impl LogCollector {
|
|
|
793
749
|
fn log_console_log_messages(&mut self, console_log_inputs: &[Bytes]) {
|
|
794
750
|
let (sender, receiver) = channel();
|
|
795
751
|
|
|
796
|
-
let status = self.decode_console_log_inputs_fn.
|
|
797
|
-
|
|
798
|
-
inputs: console_log_inputs.to_vec(),
|
|
799
|
-
sender,
|
|
800
|
-
},
|
|
752
|
+
let status = self.decode_console_log_inputs_fn.call_with_return_value(
|
|
753
|
+
console_log_inputs.to_vec(),
|
|
801
754
|
ThreadsafeFunctionCallMode::Blocking,
|
|
755
|
+
move |decoded_inputs: Vec<String>| {
|
|
756
|
+
sender.send(decoded_inputs).map_err(|_error| {
|
|
757
|
+
napi::Error::new(
|
|
758
|
+
Status::GenericFailure,
|
|
759
|
+
"Failed to send result from decode_console_log_inputs",
|
|
760
|
+
)
|
|
761
|
+
})
|
|
762
|
+
},
|
|
802
763
|
);
|
|
803
764
|
assert_eq!(status, Status::Ok);
|
|
804
765
|
|
|
805
|
-
let console_log_inputs = receiver
|
|
806
|
-
.recv()
|
|
807
|
-
.unwrap()
|
|
808
|
-
.expect("Failed call to decode_console_log_inputs");
|
|
766
|
+
let console_log_inputs = receiver.recv().unwrap();
|
|
809
767
|
// This is a special case, as we always want to print the console.log messages.
|
|
810
768
|
// The difference is how. If we have a logger, we should use that, so that logs
|
|
811
769
|
// are printed in order. If we don't, we just print the messages here.
|
|
@@ -840,9 +798,9 @@ impl LogCollector {
|
|
|
840
798
|
if let Some(to) = before_message.to {
|
|
841
799
|
// Call
|
|
842
800
|
let is_precompile = {
|
|
843
|
-
let
|
|
844
|
-
Precompiles::new(precompile::
|
|
845
|
-
|
|
801
|
+
let precompiles =
|
|
802
|
+
Precompiles::new(precompile::PrecompileSpecId::from_spec_id(spec_id));
|
|
803
|
+
precompiles.contains(&to)
|
|
846
804
|
};
|
|
847
805
|
|
|
848
806
|
if is_precompile {
|
package/src/provider/config.rs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
use std::{
|
|
2
|
+
num::NonZeroU64,
|
|
2
3
|
path::PathBuf,
|
|
3
4
|
time::{Duration, SystemTime},
|
|
4
5
|
};
|
|
@@ -172,7 +173,15 @@ impl TryFrom<MiningConfig> for edr_provider::MiningConfig {
|
|
|
172
173
|
.map(|interval| {
|
|
173
174
|
let interval = match interval {
|
|
174
175
|
Either::A(interval) => {
|
|
175
|
-
|
|
176
|
+
let interval = interval.try_cast()?;
|
|
177
|
+
let interval = NonZeroU64::new(interval).ok_or_else(|| {
|
|
178
|
+
napi::Error::new(
|
|
179
|
+
napi::Status::GenericFailure,
|
|
180
|
+
"Interval must be greater than 0",
|
|
181
|
+
)
|
|
182
|
+
})?;
|
|
183
|
+
|
|
184
|
+
edr_provider::IntervalConfig::Fixed(interval)
|
|
176
185
|
}
|
|
177
186
|
Either::B(IntervalRange { min, max }) => edr_provider::IntervalConfig::Range {
|
|
178
187
|
min: min.try_cast()?,
|
|
@@ -225,6 +234,14 @@ impl TryFrom<ProviderConfig> for edr_provider::ProviderConfig {
|
|
|
225
234
|
)
|
|
226
235
|
.collect::<napi::Result<_>>()?;
|
|
227
236
|
|
|
237
|
+
let block_gas_limit =
|
|
238
|
+
NonZeroU64::new(value.block_gas_limit.try_cast()?).ok_or_else(|| {
|
|
239
|
+
napi::Error::new(
|
|
240
|
+
napi::Status::GenericFailure,
|
|
241
|
+
"Block gas limit must be greater than 0",
|
|
242
|
+
)
|
|
243
|
+
})?;
|
|
244
|
+
|
|
228
245
|
Ok(Self {
|
|
229
246
|
accounts: value
|
|
230
247
|
.genesis_accounts
|
|
@@ -235,7 +252,7 @@ impl TryFrom<ProviderConfig> for edr_provider::ProviderConfig {
|
|
|
235
252
|
allow_unlimited_contract_size: value.allow_unlimited_contract_size,
|
|
236
253
|
bail_on_call_failure: value.bail_on_call_failure,
|
|
237
254
|
bail_on_transaction_failure: value.bail_on_transaction_failure,
|
|
238
|
-
block_gas_limit
|
|
255
|
+
block_gas_limit,
|
|
239
256
|
cache_dir: PathBuf::from(
|
|
240
257
|
value
|
|
241
258
|
.cache_dir
|
package/src/result.rs
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
use std::mem;
|
|
2
|
-
|
|
3
1
|
use napi::{
|
|
4
2
|
bindgen_prelude::{BigInt, Buffer, Either3},
|
|
5
3
|
Either, Env, JsBuffer, JsBufferValue,
|
|
@@ -19,17 +17,17 @@ pub enum SuccessReason {
|
|
|
19
17
|
SelfDestruct,
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
impl From<edr_evm::
|
|
23
|
-
fn from(eval: edr_evm::
|
|
20
|
+
impl From<edr_evm::SuccessReason> for SuccessReason {
|
|
21
|
+
fn from(eval: edr_evm::SuccessReason) -> Self {
|
|
24
22
|
match eval {
|
|
25
|
-
edr_evm::
|
|
26
|
-
edr_evm::
|
|
27
|
-
edr_evm::
|
|
23
|
+
edr_evm::SuccessReason::Stop => Self::Stop,
|
|
24
|
+
edr_evm::SuccessReason::Return => Self::Return,
|
|
25
|
+
edr_evm::SuccessReason::SelfDestruct => Self::SelfDestruct,
|
|
28
26
|
}
|
|
29
27
|
}
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
impl From<SuccessReason> for edr_evm::
|
|
30
|
+
impl From<SuccessReason> for edr_evm::SuccessReason {
|
|
33
31
|
fn from(value: SuccessReason) -> Self {
|
|
34
32
|
match value {
|
|
35
33
|
SuccessReason::Stop => Self::Stop,
|
|
@@ -97,43 +95,47 @@ pub enum ExceptionalHalt {
|
|
|
97
95
|
/// Error on created contract that begins with EF
|
|
98
96
|
CreateContractStartingWithEF,
|
|
99
97
|
/// EIP-3860: Limit and meter initcode. Initcode size limit exceeded.
|
|
100
|
-
|
|
98
|
+
CreateInitCodeSizeLimit,
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
impl From<edr_evm::
|
|
104
|
-
fn from(halt: edr_evm::
|
|
101
|
+
impl From<edr_evm::HaltReason> for ExceptionalHalt {
|
|
102
|
+
fn from(halt: edr_evm::HaltReason) -> Self {
|
|
105
103
|
match halt {
|
|
106
|
-
edr_evm::
|
|
107
|
-
edr_evm::
|
|
108
|
-
edr_evm::
|
|
109
|
-
edr_evm::
|
|
110
|
-
edr_evm::
|
|
111
|
-
edr_evm::
|
|
112
|
-
edr_evm::
|
|
113
|
-
edr_evm::
|
|
114
|
-
edr_evm::
|
|
115
|
-
edr_evm::
|
|
116
|
-
edr_evm::
|
|
117
|
-
edr_evm::
|
|
118
|
-
|
|
104
|
+
edr_evm::HaltReason::OutOfGas(..) => ExceptionalHalt::OutOfGas,
|
|
105
|
+
edr_evm::HaltReason::OpcodeNotFound => ExceptionalHalt::OpcodeNotFound,
|
|
106
|
+
edr_evm::HaltReason::InvalidFEOpcode => ExceptionalHalt::InvalidFEOpcode,
|
|
107
|
+
edr_evm::HaltReason::InvalidJump => ExceptionalHalt::InvalidJump,
|
|
108
|
+
edr_evm::HaltReason::NotActivated => ExceptionalHalt::NotActivated,
|
|
109
|
+
edr_evm::HaltReason::StackUnderflow => ExceptionalHalt::StackUnderflow,
|
|
110
|
+
edr_evm::HaltReason::StackOverflow => ExceptionalHalt::StackOverflow,
|
|
111
|
+
edr_evm::HaltReason::OutOfOffset => ExceptionalHalt::OutOfOffset,
|
|
112
|
+
edr_evm::HaltReason::CreateCollision => ExceptionalHalt::CreateCollision,
|
|
113
|
+
edr_evm::HaltReason::PrecompileError => ExceptionalHalt::PrecompileError,
|
|
114
|
+
edr_evm::HaltReason::NonceOverflow => ExceptionalHalt::NonceOverflow,
|
|
115
|
+
edr_evm::HaltReason::CreateContractSizeLimit => {
|
|
116
|
+
ExceptionalHalt::CreateContractSizeLimit
|
|
117
|
+
}
|
|
118
|
+
edr_evm::HaltReason::CreateContractStartingWithEF => {
|
|
119
119
|
ExceptionalHalt::CreateContractStartingWithEF
|
|
120
120
|
}
|
|
121
|
-
edr_evm::
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
| edr_evm::
|
|
126
|
-
| edr_evm::
|
|
121
|
+
edr_evm::HaltReason::CreateInitCodeSizeLimit => {
|
|
122
|
+
ExceptionalHalt::CreateInitCodeSizeLimit
|
|
123
|
+
}
|
|
124
|
+
edr_evm::HaltReason::OverflowPayment
|
|
125
|
+
| edr_evm::HaltReason::StateChangeDuringStaticCall
|
|
126
|
+
| edr_evm::HaltReason::CallNotAllowedInsideStatic
|
|
127
|
+
| edr_evm::HaltReason::OutOfFunds
|
|
128
|
+
| edr_evm::HaltReason::CallTooDeep => {
|
|
127
129
|
unreachable!("Internal halts that can be only found inside Inspector: {halt:?}")
|
|
128
130
|
}
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
impl From<ExceptionalHalt> for edr_evm::
|
|
135
|
+
impl From<ExceptionalHalt> for edr_evm::HaltReason {
|
|
134
136
|
fn from(value: ExceptionalHalt) -> Self {
|
|
135
137
|
match value {
|
|
136
|
-
ExceptionalHalt::OutOfGas => Self::OutOfGas(edr_evm::OutOfGasError::
|
|
138
|
+
ExceptionalHalt::OutOfGas => Self::OutOfGas(edr_evm::OutOfGasError::Basic),
|
|
137
139
|
ExceptionalHalt::OpcodeNotFound => Self::OpcodeNotFound,
|
|
138
140
|
ExceptionalHalt::InvalidFEOpcode => Self::InvalidFEOpcode,
|
|
139
141
|
ExceptionalHalt::InvalidJump => Self::InvalidJump,
|
|
@@ -146,7 +148,7 @@ impl From<ExceptionalHalt> for edr_evm::Halt {
|
|
|
146
148
|
ExceptionalHalt::NonceOverflow => Self::NonceOverflow,
|
|
147
149
|
ExceptionalHalt::CreateContractSizeLimit => Self::CreateContractSizeLimit,
|
|
148
150
|
ExceptionalHalt::CreateContractStartingWithEF => Self::CreateContractStartingWithEF,
|
|
149
|
-
ExceptionalHalt::
|
|
151
|
+
ExceptionalHalt::CreateInitCodeSizeLimit => Self::CreateInitCodeSizeLimit,
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
154
|
}
|
|
@@ -190,36 +192,19 @@ impl ExecutionResult {
|
|
|
190
192
|
logs,
|
|
191
193
|
output: match output {
|
|
192
194
|
edr_evm::Output::Call(return_value) => {
|
|
193
|
-
let return_value =
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return_value.len(),
|
|
199
|
-
return_value,
|
|
200
|
-
|return_value: edr_eth::Bytes, _env| {
|
|
201
|
-
mem::drop(return_value);
|
|
202
|
-
},
|
|
203
|
-
)
|
|
204
|
-
}
|
|
205
|
-
.map(JsBufferValue::into_raw)?,
|
|
206
|
-
})
|
|
195
|
+
let return_value = env
|
|
196
|
+
.create_buffer_with_data(return_value.to_vec())
|
|
197
|
+
.map(JsBufferValue::into_raw)?;
|
|
198
|
+
|
|
199
|
+
Either::A(CallOutput { return_value })
|
|
207
200
|
}
|
|
208
201
|
edr_evm::Output::Create(return_value, address) => {
|
|
209
|
-
let return_value =
|
|
202
|
+
let return_value = env
|
|
203
|
+
.create_buffer_with_data(return_value.to_vec())
|
|
204
|
+
.map(JsBufferValue::into_raw)?;
|
|
210
205
|
|
|
211
206
|
Either::B(CreateOutput {
|
|
212
|
-
return_value
|
|
213
|
-
env.create_buffer_with_borrowed_data(
|
|
214
|
-
return_value.as_ptr(),
|
|
215
|
-
return_value.len(),
|
|
216
|
-
return_value,
|
|
217
|
-
|return_value: edr_eth::Bytes, _env| {
|
|
218
|
-
mem::drop(return_value);
|
|
219
|
-
},
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
.map(JsBufferValue::into_raw)?,
|
|
207
|
+
return_value,
|
|
223
208
|
address: address.map(|address| Buffer::from(address.as_slice())),
|
|
224
209
|
})
|
|
225
210
|
}
|
|
@@ -227,20 +212,13 @@ impl ExecutionResult {
|
|
|
227
212
|
})
|
|
228
213
|
}
|
|
229
214
|
edr_evm::ExecutionResult::Revert { gas_used, output } => {
|
|
230
|
-
let output =
|
|
215
|
+
let output = env
|
|
216
|
+
.create_buffer_with_data(output.to_vec())
|
|
217
|
+
.map(JsBufferValue::into_raw)?;
|
|
218
|
+
|
|
231
219
|
Either3::B(RevertResult {
|
|
232
220
|
gas_used: BigInt::from(*gas_used),
|
|
233
|
-
output
|
|
234
|
-
env.create_buffer_with_borrowed_data(
|
|
235
|
-
output.as_ptr(),
|
|
236
|
-
output.len(),
|
|
237
|
-
output,
|
|
238
|
-
|output: edr_eth::Bytes, _env| {
|
|
239
|
-
mem::drop(output);
|
|
240
|
-
},
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
.map(JsBufferValue::into_raw)?,
|
|
221
|
+
output,
|
|
244
222
|
})
|
|
245
223
|
}
|
|
246
224
|
edr_evm::ExecutionResult::Halt { reason, gas_used } => Either3::C(HaltResult {
|
package/src/subscribe.rs
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
use edr_eth::{remote::eth, B256};
|
|
2
|
-
use napi::{
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
use napi::{
|
|
3
|
+
bindgen_prelude::BigInt,
|
|
4
|
+
threadsafe_function::{
|
|
5
|
+
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
|
|
6
|
+
},
|
|
7
|
+
Env, JsFunction,
|
|
7
8
|
};
|
|
9
|
+
use napi_derive::napi;
|
|
8
10
|
|
|
9
11
|
#[derive(Clone)]
|
|
10
12
|
pub struct SubscriberCallback {
|
|
11
|
-
inner: ThreadsafeFunction<edr_provider::SubscriptionEvent>,
|
|
13
|
+
inner: ThreadsafeFunction<edr_provider::SubscriptionEvent, ErrorStrategy::Fatal>,
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
impl SubscriberCallback {
|
|
15
17
|
pub fn new(env: &Env, subscription_event_callback: JsFunction) -> napi::Result<Self> {
|
|
16
|
-
let callback =
|
|
17
|
-
env.raw(),
|
|
18
|
-
// SAFETY: The callback is guaranteed to be valid for the lifetime of the inspector.
|
|
19
|
-
unsafe { subscription_event_callback.raw() },
|
|
18
|
+
let mut callback = subscription_event_callback.create_threadsafe_function(
|
|
20
19
|
0,
|
|
21
20
|
|ctx: ThreadSafeCallContext<edr_provider::SubscriptionEvent>| {
|
|
22
21
|
// SubscriptionEvent
|
|
@@ -39,10 +38,14 @@ impl SubscriberCallback {
|
|
|
39
38
|
|
|
40
39
|
event.set_named_property("result", result)?;
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
Ok(())
|
|
41
|
+
Ok(vec![event])
|
|
44
42
|
},
|
|
45
43
|
)?;
|
|
44
|
+
|
|
45
|
+
// Maintain a weak reference to the function to avoid the event loop from
|
|
46
|
+
// exiting.
|
|
47
|
+
callback.unref(env)?;
|
|
48
|
+
|
|
46
49
|
Ok(Self { inner: callback })
|
|
47
50
|
}
|
|
48
51
|
|
package/src/trace.rs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
use std::
|
|
1
|
+
use std::sync::Arc;
|
|
2
2
|
|
|
3
|
-
use edr_evm::{trace::BeforeMessage
|
|
3
|
+
use edr_evm::{interpreter::OPCODE_JUMPMAP, trace::BeforeMessage};
|
|
4
4
|
use napi::{
|
|
5
5
|
bindgen_prelude::{BigInt, Buffer, Either3},
|
|
6
6
|
Env, JsBuffer, JsBufferValue,
|
|
@@ -47,34 +47,14 @@ pub struct TracingMessage {
|
|
|
47
47
|
|
|
48
48
|
impl TracingMessage {
|
|
49
49
|
pub fn new(env: &Env, message: &BeforeMessage) -> napi::Result<Self> {
|
|
50
|
-
let data =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
data.as_ptr(),
|
|
54
|
-
data.len(),
|
|
55
|
-
data,
|
|
56
|
-
|data: edr_eth::Bytes, _env| {
|
|
57
|
-
mem::drop(data);
|
|
58
|
-
},
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
.map(JsBufferValue::into_raw)?;
|
|
50
|
+
let data = env
|
|
51
|
+
.create_buffer_with_data(message.data.to_vec())
|
|
52
|
+
.map(JsBufferValue::into_raw)?;
|
|
62
53
|
|
|
63
54
|
let code = message.code.as_ref().map_or(Ok(None), |code| {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
env.create_buffer_with_borrowed_data(
|
|
68
|
-
code.as_ptr(),
|
|
69
|
-
code.len(),
|
|
70
|
-
code,
|
|
71
|
-
|code: edr_eth::Bytes, _env| {
|
|
72
|
-
mem::drop(code);
|
|
73
|
-
},
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
.map(JsBufferValue::into_raw)
|
|
77
|
-
.map(Some)
|
|
55
|
+
env.create_buffer_with_data(code.original_bytes().to_vec())
|
|
56
|
+
.map(JsBufferValue::into_raw)
|
|
57
|
+
.map(Some)
|
|
78
58
|
})?;
|
|
79
59
|
|
|
80
60
|
Ok(TracingMessage {
|
package/src/sync.rs
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
use std::{fmt::Debug, sync::mpsc::Sender};
|
|
2
|
-
|
|
3
|
-
use napi::{bindgen_prelude::FromNapiValue, Env, JsFunction, JsObject, JsUnknown, NapiRaw, Status};
|
|
4
|
-
|
|
5
|
-
use crate::cast::TryCast;
|
|
6
|
-
|
|
7
|
-
pub fn await_promise<I, O>(
|
|
8
|
-
env: Env,
|
|
9
|
-
result: JsUnknown,
|
|
10
|
-
tx: Sender<napi::Result<O>>,
|
|
11
|
-
) -> napi::Result<()>
|
|
12
|
-
where
|
|
13
|
-
I: FromNapiValue + TryCast<O, Error = napi::Error>,
|
|
14
|
-
O: 'static,
|
|
15
|
-
{
|
|
16
|
-
// If the result is a promise, wait for it to resolve, and send the result to
|
|
17
|
-
// the channel. Otherwise, send the result immediately.
|
|
18
|
-
if result.is_promise()? {
|
|
19
|
-
let result: JsObject = result.try_into()?;
|
|
20
|
-
let then: JsFunction = result.get_named_property("then")?;
|
|
21
|
-
let tx2 = tx.clone();
|
|
22
|
-
let cb = env.create_function_from_closure("callback", move |ctx| {
|
|
23
|
-
let result = ctx.get::<I>(0)?;
|
|
24
|
-
tx.send(Ok(result.try_cast()?)).unwrap();
|
|
25
|
-
ctx.env.get_undefined()
|
|
26
|
-
})?;
|
|
27
|
-
let eb = env.create_function_from_closure("error_callback", move |ctx| {
|
|
28
|
-
// TODO: need a way to convert a JsUnknown to an Error
|
|
29
|
-
tx2.send(Err(napi::Error::from_reason("Promise rejected")))
|
|
30
|
-
.unwrap();
|
|
31
|
-
ctx.env.get_undefined()
|
|
32
|
-
})?;
|
|
33
|
-
then.call(Some(&result), &[cb, eb])?;
|
|
34
|
-
} else {
|
|
35
|
-
let result = unsafe { I::from_napi_value(env.raw(), result.raw())? };
|
|
36
|
-
tx.send(Ok(result.try_cast()?)).unwrap();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
Ok(())
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
#[allow(dead_code)]
|
|
43
|
-
pub fn await_void_promise(
|
|
44
|
-
env: Env,
|
|
45
|
-
result: JsUnknown,
|
|
46
|
-
tx: Sender<napi::Result<()>>,
|
|
47
|
-
) -> napi::Result<()> {
|
|
48
|
-
// If the result is a promise, wait for it to resolve, and send the result to
|
|
49
|
-
// the channel. Otherwise, send the result immediately.
|
|
50
|
-
if result.is_promise()? {
|
|
51
|
-
let result: JsObject = result.try_into()?;
|
|
52
|
-
let then: JsFunction = result.get_named_property("then")?;
|
|
53
|
-
let tx2 = tx.clone();
|
|
54
|
-
let cb = env.create_function_from_closure("callback", move |ctx| {
|
|
55
|
-
tx.send(Ok(())).unwrap();
|
|
56
|
-
ctx.env.get_undefined()
|
|
57
|
-
})?;
|
|
58
|
-
let eb = env.create_function_from_closure("error_callback", move |ctx| {
|
|
59
|
-
// TODO: need a way to convert a JsUnknown to an Error
|
|
60
|
-
tx2.send(Err(napi::Error::from_reason("Promise rejected")))
|
|
61
|
-
.unwrap();
|
|
62
|
-
ctx.env.get_undefined()
|
|
63
|
-
})?;
|
|
64
|
-
then.call(Some(&result), &[cb, eb])?;
|
|
65
|
-
Ok(())
|
|
66
|
-
} else {
|
|
67
|
-
Err(napi::Error::new(
|
|
68
|
-
Status::ObjectExpected,
|
|
69
|
-
"Expected promise".to_owned(),
|
|
70
|
-
))
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
pub fn handle_error<T: Debug>(
|
|
75
|
-
tx: Sender<napi::Result<T>>,
|
|
76
|
-
res: napi::Result<()>,
|
|
77
|
-
) -> napi::Result<()> {
|
|
78
|
-
match res {
|
|
79
|
-
Ok(_) => Ok(()),
|
|
80
|
-
Err(e) => {
|
|
81
|
-
tx.send(Err(e)).expect("send error");
|
|
82
|
-
Ok(())
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
// Fork of threadsafe_function from napi-rs that allows calling JS function
|
|
2
|
-
// manually rather than only returning args. This enables us to use the return
|
|
3
|
-
// value of the function.
|
|
4
|
-
|
|
5
|
-
#![allow(clippy::single_component_path_imports)]
|
|
6
|
-
|
|
7
|
-
use std::{
|
|
8
|
-
convert::Into,
|
|
9
|
-
ffi::CString,
|
|
10
|
-
marker::PhantomData,
|
|
11
|
-
os::raw::c_void,
|
|
12
|
-
ptr,
|
|
13
|
-
sync::{
|
|
14
|
-
atomic::{AtomicBool, AtomicUsize, Ordering},
|
|
15
|
-
Arc,
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
use napi::{check_status, sys, Env, JsError, JsFunction, NapiValue, Result, Status};
|
|
20
|
-
|
|
21
|
-
/// `ThreadSafeFunction` context object
|
|
22
|
-
/// the `value` is the value passed to `call` method
|
|
23
|
-
pub struct ThreadSafeCallContext<T: 'static> {
|
|
24
|
-
pub env: Env,
|
|
25
|
-
pub value: T,
|
|
26
|
-
pub callback: JsFunction,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
#[repr(u8)]
|
|
30
|
-
pub enum ThreadsafeFunctionCallMode {
|
|
31
|
-
NonBlocking,
|
|
32
|
-
Blocking,
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
impl From<ThreadsafeFunctionCallMode> for sys::napi_threadsafe_function_call_mode {
|
|
36
|
-
fn from(value: ThreadsafeFunctionCallMode) -> Self {
|
|
37
|
-
match value {
|
|
38
|
-
ThreadsafeFunctionCallMode::Blocking => sys::ThreadsafeFunctionCallMode::blocking,
|
|
39
|
-
ThreadsafeFunctionCallMode::NonBlocking => sys::ThreadsafeFunctionCallMode::nonblocking,
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/// Communicate with the addon's main thread by invoking a JavaScript function
|
|
45
|
-
/// from other threads.
|
|
46
|
-
///
|
|
47
|
-
/// ## Example
|
|
48
|
-
/// An example of using `ThreadsafeFunction`:
|
|
49
|
-
///
|
|
50
|
-
/// ```rust
|
|
51
|
-
/// #[macro_use]
|
|
52
|
-
/// extern crate napi_derive;
|
|
53
|
-
///
|
|
54
|
-
/// use std::thread;
|
|
55
|
-
///
|
|
56
|
-
/// use napi::{
|
|
57
|
-
/// threadsafe_function::{
|
|
58
|
-
/// ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode,
|
|
59
|
-
/// },
|
|
60
|
-
/// CallContext, Error, JsFunction, JsNumber, JsUndefined, Result, Status,
|
|
61
|
-
/// };
|
|
62
|
-
///
|
|
63
|
-
/// #[js_function(1)]
|
|
64
|
-
/// pub fn test_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> {
|
|
65
|
-
/// let func = ctx.get::<JsFunction>(0)?;
|
|
66
|
-
///
|
|
67
|
-
/// let tsfn =
|
|
68
|
-
/// ctx
|
|
69
|
-
/// .env
|
|
70
|
-
/// .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext<Vec<u32>>| {
|
|
71
|
-
/// ctx.value
|
|
72
|
-
/// .iter()
|
|
73
|
-
/// .map(|v| ctx.env.create_uint32(*v))
|
|
74
|
-
/// .collect::<Result<Vec<JsNumber>>>()
|
|
75
|
-
/// })?;
|
|
76
|
-
///
|
|
77
|
-
/// let tsfn_cloned = tsfn.clone();
|
|
78
|
-
///
|
|
79
|
-
/// thread::spawn(move || {
|
|
80
|
-
/// let output: Vec<u32> = vec![0, 1, 2, 3];
|
|
81
|
-
/// // It's okay to call a threadsafe function multiple times.
|
|
82
|
-
/// tsfn.call(Ok(output.clone()), ThreadsafeFunctionCallMode::Blocking);
|
|
83
|
-
/// });
|
|
84
|
-
///
|
|
85
|
-
/// thread::spawn(move || {
|
|
86
|
-
/// let output: Vec<u32> = vec![3, 2, 1, 0];
|
|
87
|
-
/// // It's okay to call a threadsafe function multiple times.
|
|
88
|
-
/// tsfn_cloned.call(Ok(output.clone()), ThreadsafeFunctionCallMode::NonBlocking);
|
|
89
|
-
/// });
|
|
90
|
-
///
|
|
91
|
-
/// ctx.env.get_undefined()
|
|
92
|
-
/// }
|
|
93
|
-
/// ```
|
|
94
|
-
#[derive(Debug)]
|
|
95
|
-
pub struct ThreadsafeFunction<T: 'static> {
|
|
96
|
-
raw_tsfn: sys::napi_threadsafe_function,
|
|
97
|
-
aborted: Arc<AtomicBool>,
|
|
98
|
-
ref_count: Arc<AtomicUsize>,
|
|
99
|
-
_phantom: PhantomData<T>,
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
impl<T: 'static> Clone for ThreadsafeFunction<T> {
|
|
103
|
-
fn clone(&self) -> Self {
|
|
104
|
-
if !self.aborted.load(Ordering::Acquire) {
|
|
105
|
-
let acquire_status = unsafe { sys::napi_acquire_threadsafe_function(self.raw_tsfn) };
|
|
106
|
-
debug_assert!(
|
|
107
|
-
acquire_status == sys::Status::napi_ok,
|
|
108
|
-
"Acquire threadsafe function failed in clone"
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
Self {
|
|
113
|
-
raw_tsfn: self.raw_tsfn,
|
|
114
|
-
aborted: Arc::clone(&self.aborted),
|
|
115
|
-
ref_count: Arc::clone(&self.ref_count),
|
|
116
|
-
_phantom: PhantomData,
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
unsafe impl<T> Send for ThreadsafeFunction<T> {}
|
|
122
|
-
unsafe impl<T> Sync for ThreadsafeFunction<T> {}
|
|
123
|
-
|
|
124
|
-
impl<T: 'static> ThreadsafeFunction<T> {
|
|
125
|
-
/// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function)
|
|
126
|
-
/// for more information.
|
|
127
|
-
pub(crate) fn create<R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<()>>(
|
|
128
|
-
env: sys::napi_env,
|
|
129
|
-
func: sys::napi_value,
|
|
130
|
-
max_queue_size: usize,
|
|
131
|
-
callback: R,
|
|
132
|
-
) -> Result<Self> {
|
|
133
|
-
let mut async_resource_name = ptr::null_mut();
|
|
134
|
-
let s = "napi_rs_threadsafe_function";
|
|
135
|
-
let len = s.len();
|
|
136
|
-
let s = CString::new(s)?;
|
|
137
|
-
check_status!(unsafe {
|
|
138
|
-
sys::napi_create_string_utf8(env, s.as_ptr(), len, &mut async_resource_name)
|
|
139
|
-
})?;
|
|
140
|
-
|
|
141
|
-
let initial_thread_count = 1usize;
|
|
142
|
-
let mut raw_tsfn = ptr::null_mut();
|
|
143
|
-
let ptr = Box::into_raw(Box::new(callback)).cast::<c_void>();
|
|
144
|
-
check_status!(unsafe {
|
|
145
|
-
sys::napi_create_threadsafe_function(
|
|
146
|
-
env,
|
|
147
|
-
func,
|
|
148
|
-
ptr::null_mut(),
|
|
149
|
-
async_resource_name,
|
|
150
|
-
max_queue_size,
|
|
151
|
-
initial_thread_count,
|
|
152
|
-
ptr,
|
|
153
|
-
Some(thread_finalize_cb::<T, R>),
|
|
154
|
-
ptr,
|
|
155
|
-
Some(call_js_cb::<T, R>),
|
|
156
|
-
&mut raw_tsfn,
|
|
157
|
-
)
|
|
158
|
-
})?;
|
|
159
|
-
|
|
160
|
-
let aborted = Arc::new(AtomicBool::new(false));
|
|
161
|
-
let aborted_ptr = Arc::into_raw(aborted.clone()) as *mut c_void;
|
|
162
|
-
check_status!(unsafe {
|
|
163
|
-
sys::napi_add_env_cleanup_hook(env, Some(cleanup_cb), aborted_ptr)
|
|
164
|
-
})?;
|
|
165
|
-
|
|
166
|
-
Ok(ThreadsafeFunction {
|
|
167
|
-
raw_tsfn,
|
|
168
|
-
aborted,
|
|
169
|
-
ref_count: Arc::new(AtomicUsize::new(initial_thread_count)),
|
|
170
|
-
_phantom: PhantomData,
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
impl<T: 'static> ThreadsafeFunction<T> {
|
|
176
|
-
/// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function)
|
|
177
|
-
/// for more information.
|
|
178
|
-
pub fn call(&self, value: T, mode: ThreadsafeFunctionCallMode) -> Status {
|
|
179
|
-
if self.aborted.load(Ordering::Acquire) {
|
|
180
|
-
return Status::Closing;
|
|
181
|
-
}
|
|
182
|
-
unsafe {
|
|
183
|
-
sys::napi_call_threadsafe_function(
|
|
184
|
-
self.raw_tsfn,
|
|
185
|
-
Box::into_raw(Box::new(value)).cast(),
|
|
186
|
-
mode.into(),
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
.into()
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
impl<T: 'static> Drop for ThreadsafeFunction<T> {
|
|
194
|
-
fn drop(&mut self) {
|
|
195
|
-
if !self.aborted.load(Ordering::Acquire) && self.ref_count.load(Ordering::Acquire) > 0usize
|
|
196
|
-
{
|
|
197
|
-
let release_status = unsafe {
|
|
198
|
-
sys::napi_release_threadsafe_function(
|
|
199
|
-
self.raw_tsfn,
|
|
200
|
-
sys::ThreadsafeFunctionReleaseMode::release,
|
|
201
|
-
)
|
|
202
|
-
};
|
|
203
|
-
assert!(
|
|
204
|
-
release_status == sys::Status::napi_ok,
|
|
205
|
-
"Threadsafe Function release failed"
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
unsafe extern "C" fn cleanup_cb(cleanup_data: *mut c_void) {
|
|
212
|
-
let aborted = Arc::<AtomicBool>::from_raw(cleanup_data.cast());
|
|
213
|
-
aborted.store(true, Ordering::SeqCst);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
unsafe extern "C" fn thread_finalize_cb<T: 'static, R>(
|
|
217
|
-
_raw_env: sys::napi_env,
|
|
218
|
-
finalize_data: *mut c_void,
|
|
219
|
-
_finalize_hint: *mut c_void,
|
|
220
|
-
) where
|
|
221
|
-
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<()>,
|
|
222
|
-
{
|
|
223
|
-
// cleanup
|
|
224
|
-
drop(Box::<R>::from_raw(finalize_data.cast()));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
unsafe extern "C" fn call_js_cb<T: 'static, R>(
|
|
228
|
-
raw_env: sys::napi_env,
|
|
229
|
-
js_callback: sys::napi_value,
|
|
230
|
-
context: *mut c_void,
|
|
231
|
-
data: *mut c_void,
|
|
232
|
-
) where
|
|
233
|
-
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<()>,
|
|
234
|
-
{
|
|
235
|
-
// env and/or callback can be null when shutting down
|
|
236
|
-
if raw_env.is_null() || js_callback.is_null() {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
let ctx: &mut R = &mut *context.cast::<R>();
|
|
241
|
-
let val: Result<T> = Ok(*Box::<T>::from_raw(data.cast()));
|
|
242
|
-
|
|
243
|
-
let mut recv = ptr::null_mut();
|
|
244
|
-
sys::napi_get_undefined(raw_env, &mut recv);
|
|
245
|
-
|
|
246
|
-
let ret = val.and_then(|v| {
|
|
247
|
-
(ctx)(ThreadSafeCallContext {
|
|
248
|
-
env: Env::from_raw(raw_env),
|
|
249
|
-
value: v,
|
|
250
|
-
callback: JsFunction::from_raw(raw_env, js_callback).unwrap(), // TODO: unwrap
|
|
251
|
-
})
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
let status = match ret {
|
|
255
|
-
Ok(()) => sys::Status::napi_ok,
|
|
256
|
-
Err(e) => sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env)),
|
|
257
|
-
};
|
|
258
|
-
if status == sys::Status::napi_ok {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
if status == sys::Status::napi_pending_exception {
|
|
262
|
-
let mut error_result = ptr::null_mut();
|
|
263
|
-
assert_eq!(
|
|
264
|
-
sys::napi_get_and_clear_last_exception(raw_env, &mut error_result),
|
|
265
|
-
sys::Status::napi_ok
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
// When shutting down, napi_fatal_exception sometimes returns another exception
|
|
269
|
-
let stat = sys::napi_fatal_exception(raw_env, error_result);
|
|
270
|
-
assert!(stat == sys::Status::napi_ok || stat == sys::Status::napi_pending_exception);
|
|
271
|
-
} else {
|
|
272
|
-
let error_code: Status = status.into();
|
|
273
|
-
let error_code_string = format!("{error_code:?}");
|
|
274
|
-
let mut error_code_value = ptr::null_mut();
|
|
275
|
-
assert_eq!(
|
|
276
|
-
sys::napi_create_string_utf8(
|
|
277
|
-
raw_env,
|
|
278
|
-
error_code_string.as_ptr().cast(),
|
|
279
|
-
error_code_string.len(),
|
|
280
|
-
&mut error_code_value,
|
|
281
|
-
),
|
|
282
|
-
sys::Status::napi_ok,
|
|
283
|
-
);
|
|
284
|
-
let error_msg = "Call JavaScript callback failed in thread safe function";
|
|
285
|
-
let mut error_msg_value = ptr::null_mut();
|
|
286
|
-
assert_eq!(
|
|
287
|
-
sys::napi_create_string_utf8(
|
|
288
|
-
raw_env,
|
|
289
|
-
error_msg.as_ptr().cast(),
|
|
290
|
-
error_msg.len(),
|
|
291
|
-
&mut error_msg_value,
|
|
292
|
-
),
|
|
293
|
-
sys::Status::napi_ok,
|
|
294
|
-
);
|
|
295
|
-
let mut error_value = ptr::null_mut();
|
|
296
|
-
assert_eq!(
|
|
297
|
-
sys::napi_create_error(raw_env, error_code_value, error_msg_value, &mut error_value),
|
|
298
|
-
sys::Status::napi_ok,
|
|
299
|
-
);
|
|
300
|
-
assert_eq!(
|
|
301
|
-
sys::napi_fatal_exception(raw_env, error_value),
|
|
302
|
-
sys::Status::napi_ok
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
}
|