@nomicfoundation/edr 0.2.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.cargo/config.toml +8 -0
  2. package/.mocharc.json +4 -0
  3. package/Cargo.toml +50 -0
  4. package/LICENSE +1 -0
  5. package/artifacts/bindings-aarch64-apple-darwin/edr.darwin-arm64.node +0 -0
  6. package/artifacts/bindings-aarch64-pc-windows-msvc/edr.win32-arm64-msvc.node +0 -0
  7. package/artifacts/bindings-aarch64-unknown-linux-gnu/edr.linux-arm64-gnu.node +0 -0
  8. package/artifacts/bindings-aarch64-unknown-linux-musl/edr.linux-arm64-musl.node +0 -0
  9. package/artifacts/bindings-i686-pc-windows-msvc/edr.win32-ia32-msvc.node +0 -0
  10. package/artifacts/bindings-x86_64-apple-darwin/edr.darwin-x64.node +0 -0
  11. package/artifacts/bindings-x86_64-pc-windows-msvc/edr.win32-x64-msvc.node +0 -0
  12. package/artifacts/bindings-x86_64-unknown-linux-gnu/edr.linux-x64-gnu.node +0 -0
  13. package/artifacts/bindings-x86_64-unknown-linux-musl/edr.linux-x64-musl.node +0 -0
  14. package/build.rs +3 -0
  15. package/index.d.ts +383 -0
  16. package/index.js +264 -0
  17. package/npm/darwin-arm64/README.md +3 -0
  18. package/npm/darwin-arm64/edr.darwin-arm64.node +0 -0
  19. package/npm/darwin-arm64/package.json +22 -0
  20. package/npm/darwin-x64/README.md +3 -0
  21. package/npm/darwin-x64/edr.darwin-x64.node +0 -0
  22. package/npm/darwin-x64/package.json +22 -0
  23. package/npm/linux-arm64-gnu/README.md +3 -0
  24. package/npm/linux-arm64-gnu/edr.linux-arm64-gnu.node +0 -0
  25. package/npm/linux-arm64-gnu/package.json +25 -0
  26. package/npm/linux-arm64-musl/README.md +3 -0
  27. package/npm/linux-arm64-musl/edr.linux-arm64-musl.node +0 -0
  28. package/npm/linux-arm64-musl/package.json +25 -0
  29. package/npm/linux-x64-gnu/README.md +3 -0
  30. package/npm/linux-x64-gnu/edr.linux-x64-gnu.node +0 -0
  31. package/npm/linux-x64-gnu/package.json +25 -0
  32. package/npm/linux-x64-musl/README.md +3 -0
  33. package/npm/linux-x64-musl/edr.linux-x64-musl.node +0 -0
  34. package/npm/linux-x64-musl/package.json +25 -0
  35. package/npm/win32-arm64-msvc/README.md +3 -0
  36. package/npm/win32-arm64-msvc/edr.win32-arm64-msvc.node +0 -0
  37. package/npm/win32-arm64-msvc/package.json +22 -0
  38. package/npm/win32-ia32-msvc/README.md +3 -0
  39. package/npm/win32-ia32-msvc/edr.win32-ia32-msvc.node +0 -0
  40. package/npm/win32-ia32-msvc/package.json +22 -0
  41. package/npm/win32-x64-msvc/README.md +3 -0
  42. package/npm/win32-x64-msvc/edr.win32-x64-msvc.node +0 -0
  43. package/npm/win32-x64-msvc/package.json +22 -0
  44. package/package.json +61 -0
  45. package/src/account.rs +28 -0
  46. package/src/block.rs +110 -0
  47. package/src/cast.rs +119 -0
  48. package/src/config.rs +70 -0
  49. package/src/context.rs +74 -0
  50. package/src/debug_trace.rs +38 -0
  51. package/src/lib.rs +18 -0
  52. package/src/log.rs +41 -0
  53. package/src/logger.rs +1277 -0
  54. package/src/provider/config.rs +271 -0
  55. package/src/provider.rs +185 -0
  56. package/src/result.rs +254 -0
  57. package/src/subscribe.rs +60 -0
  58. package/src/sync.rs +85 -0
  59. package/src/threadsafe_function.rs +305 -0
  60. package/src/trace.rs +168 -0
  61. package/test/provider.ts +104 -0
  62. package/tsconfig.json +17 -0
@@ -0,0 +1,271 @@
1
+ use std::{
2
+ path::PathBuf,
3
+ time::{Duration, SystemTime},
4
+ };
5
+
6
+ use edr_eth::HashMap;
7
+ use edr_provider::AccountConfig;
8
+ use napi::{
9
+ bindgen_prelude::{BigInt, Buffer},
10
+ Either,
11
+ };
12
+ use napi_derive::napi;
13
+
14
+ use crate::{account::GenesisAccount, block::BlobGas, cast::TryCast, config::SpecId};
15
+
16
+ /// Configuration for a chain
17
+ #[napi(object)]
18
+ pub struct ChainConfig {
19
+ /// The chain ID
20
+ pub chain_id: BigInt,
21
+ /// The chain's supported hardforks
22
+ pub hardforks: Vec<HardforkActivation>,
23
+ }
24
+
25
+ /// Configuration for forking a blockchain
26
+ #[napi(object)]
27
+ pub struct ForkConfig {
28
+ /// The URL of the JSON-RPC endpoint to fork from
29
+ pub json_rpc_url: String,
30
+ /// The block number to fork from. If not provided, the latest safe block is
31
+ /// used.
32
+ pub block_number: Option<BigInt>,
33
+ /// The HTTP headers to use when making requests to the JSON-RPC endpoint
34
+ pub http_headers: Option<Vec<HttpHeader>>,
35
+ }
36
+
37
+ #[napi(object)]
38
+ pub struct HttpHeader {
39
+ pub name: String,
40
+ pub value: String,
41
+ }
42
+
43
+ /// Configuration for a hardfork activation
44
+ #[napi(object)]
45
+ pub struct HardforkActivation {
46
+ /// The block number at which the hardfork is activated
47
+ pub block_number: BigInt,
48
+ /// The activated hardfork
49
+ pub spec_id: SpecId,
50
+ }
51
+
52
+ #[napi(string_enum)]
53
+ #[doc = "The type of ordering to use when selecting blocks to mine."]
54
+ pub enum MineOrdering {
55
+ #[doc = "Insertion order"]
56
+ Fifo,
57
+ #[doc = "Effective miner fee"]
58
+ Priority,
59
+ }
60
+
61
+ /// Configuration for the provider's mempool.
62
+ #[napi(object)]
63
+ pub struct MemPoolConfig {
64
+ pub order: MineOrdering,
65
+ }
66
+
67
+ #[napi(object)]
68
+ pub struct IntervalRange {
69
+ pub min: BigInt,
70
+ pub max: BigInt,
71
+ }
72
+
73
+ /// Configuration for the provider's miner.
74
+ #[napi(object)]
75
+ pub struct MiningConfig {
76
+ pub auto_mine: bool,
77
+ pub interval: Option<Either<BigInt, IntervalRange>>,
78
+ pub mem_pool: MemPoolConfig,
79
+ }
80
+
81
+ /// Configuration for a provider
82
+ #[napi(object)]
83
+ pub struct ProviderConfig {
84
+ /// Whether to allow blocks with the same timestamp
85
+ pub allow_blocks_with_same_timestamp: bool,
86
+ /// Whether to allow unlimited contract size
87
+ pub allow_unlimited_contract_size: bool,
88
+ /// Whether to return an `Err` when `eth_call` fails
89
+ pub bail_on_call_failure: bool,
90
+ /// Whether to return an `Err` when a `eth_sendTransaction` fails
91
+ pub bail_on_transaction_failure: bool,
92
+ /// The gas limit of each block
93
+ pub block_gas_limit: BigInt,
94
+ /// The directory to cache remote JSON-RPC responses
95
+ pub cache_dir: Option<String>,
96
+ /// The chain ID of the blockchain
97
+ pub chain_id: BigInt,
98
+ /// The configuration for chains
99
+ pub chains: Vec<ChainConfig>,
100
+ /// The address of the coinbase
101
+ pub coinbase: Buffer,
102
+ /// The configuration for forking a blockchain. If not provided, a local
103
+ /// blockchain will be created
104
+ pub fork: Option<ForkConfig>,
105
+ /// The genesis accounts of the blockchain
106
+ pub genesis_accounts: Vec<GenesisAccount>,
107
+ /// The hardfork of the blockchain
108
+ pub hardfork: SpecId,
109
+ /// The initial base fee per gas of the blockchain. Required for EIP-1559
110
+ /// transactions and later
111
+ pub initial_base_fee_per_gas: Option<BigInt>,
112
+ /// The initial blob gas of the blockchain. Required for EIP-4844
113
+ pub initial_blob_gas: Option<BlobGas>,
114
+ /// The initial date of the blockchain, in seconds since the Unix epoch
115
+ pub initial_date: Option<BigInt>,
116
+ /// The initial parent beacon block root of the blockchain. Required for
117
+ /// EIP-4788
118
+ pub initial_parent_beacon_block_root: Option<Buffer>,
119
+ /// The minimum gas price of the next block.
120
+ pub min_gas_price: BigInt,
121
+ /// The configuration for the miner
122
+ pub mining: MiningConfig,
123
+ /// The network ID of the blockchain
124
+ pub network_id: BigInt,
125
+ }
126
+
127
+ impl TryFrom<ForkConfig> for edr_provider::hardhat_rpc_types::ForkConfig {
128
+ type Error = napi::Error;
129
+
130
+ fn try_from(value: ForkConfig) -> Result<Self, Self::Error> {
131
+ let block_number: Option<u64> = value.block_number.map(TryCast::try_cast).transpose()?;
132
+ let http_headers = value.http_headers.map(|http_headers| {
133
+ http_headers
134
+ .into_iter()
135
+ .map(|HttpHeader { name, value }| (name, value))
136
+ .collect()
137
+ });
138
+
139
+ Ok(Self {
140
+ json_rpc_url: value.json_rpc_url,
141
+ block_number,
142
+ http_headers,
143
+ })
144
+ }
145
+ }
146
+
147
+ impl From<MemPoolConfig> for edr_provider::MemPoolConfig {
148
+ fn from(value: MemPoolConfig) -> Self {
149
+ Self {
150
+ order: value.order.into(),
151
+ }
152
+ }
153
+ }
154
+
155
+ impl From<MineOrdering> for edr_evm::MineOrdering {
156
+ fn from(value: MineOrdering) -> Self {
157
+ match value {
158
+ MineOrdering::Fifo => Self::Fifo,
159
+ MineOrdering::Priority => Self::Priority,
160
+ }
161
+ }
162
+ }
163
+
164
+ impl TryFrom<MiningConfig> for edr_provider::MiningConfig {
165
+ type Error = napi::Error;
166
+
167
+ fn try_from(value: MiningConfig) -> Result<Self, Self::Error> {
168
+ let mem_pool = value.mem_pool.into();
169
+
170
+ let interval = value
171
+ .interval
172
+ .map(|interval| {
173
+ let interval = match interval {
174
+ Either::A(interval) => {
175
+ edr_provider::IntervalConfig::Fixed(interval.try_cast()?)
176
+ }
177
+ Either::B(IntervalRange { min, max }) => edr_provider::IntervalConfig::Range {
178
+ min: min.try_cast()?,
179
+ max: max.try_cast()?,
180
+ },
181
+ };
182
+
183
+ napi::Result::Ok(interval)
184
+ })
185
+ .transpose()?;
186
+
187
+ Ok(Self {
188
+ auto_mine: value.auto_mine,
189
+ interval,
190
+ mem_pool,
191
+ })
192
+ }
193
+ }
194
+
195
+ impl TryFrom<ProviderConfig> for edr_provider::ProviderConfig {
196
+ type Error = napi::Error;
197
+
198
+ fn try_from(value: ProviderConfig) -> Result<Self, Self::Error> {
199
+ let chains = value
200
+ .chains
201
+ .into_iter()
202
+ .map(
203
+ |ChainConfig {
204
+ chain_id,
205
+ hardforks,
206
+ }| {
207
+ let hardforks = hardforks
208
+ .into_iter()
209
+ .map(
210
+ |HardforkActivation {
211
+ block_number,
212
+ spec_id,
213
+ }| {
214
+ let block_number = block_number.try_cast()?;
215
+ let spec_id = spec_id.try_into()?;
216
+
217
+ Ok((block_number, spec_id))
218
+ },
219
+ )
220
+ .collect::<napi::Result<Vec<_>>>()?;
221
+
222
+ let chain_id = chain_id.try_cast()?;
223
+ Ok((chain_id, edr_eth::spec::HardforkActivations::new(hardforks)))
224
+ },
225
+ )
226
+ .collect::<napi::Result<_>>()?;
227
+
228
+ Ok(Self {
229
+ accounts: value
230
+ .genesis_accounts
231
+ .into_iter()
232
+ .map(AccountConfig::try_from)
233
+ .collect::<napi::Result<Vec<_>>>()?,
234
+ allow_blocks_with_same_timestamp: value.allow_blocks_with_same_timestamp,
235
+ allow_unlimited_contract_size: value.allow_unlimited_contract_size,
236
+ bail_on_call_failure: value.bail_on_call_failure,
237
+ bail_on_transaction_failure: value.bail_on_transaction_failure,
238
+ block_gas_limit: value.block_gas_limit.try_cast()?,
239
+ cache_dir: PathBuf::from(
240
+ value
241
+ .cache_dir
242
+ .unwrap_or(String::from(edr_defaults::CACHE_DIR)),
243
+ ),
244
+ chain_id: value.chain_id.try_cast()?,
245
+ chains,
246
+ coinbase: value.coinbase.try_cast()?,
247
+ fork: value.fork.map(TryInto::try_into).transpose()?,
248
+ genesis_accounts: HashMap::new(),
249
+ hardfork: value.hardfork.try_into()?,
250
+ initial_base_fee_per_gas: value
251
+ .initial_base_fee_per_gas
252
+ .map(TryCast::try_cast)
253
+ .transpose()?,
254
+ initial_blob_gas: value.initial_blob_gas.map(TryInto::try_into).transpose()?,
255
+ initial_date: value
256
+ .initial_date
257
+ .map(|date| {
258
+ let elapsed_since_epoch = Duration::from_secs(date.try_cast()?);
259
+ napi::Result::Ok(SystemTime::UNIX_EPOCH + elapsed_since_epoch)
260
+ })
261
+ .transpose()?,
262
+ initial_parent_beacon_block_root: value
263
+ .initial_parent_beacon_block_root
264
+ .map(TryCast::try_cast)
265
+ .transpose()?,
266
+ mining: value.mining.try_into()?,
267
+ min_gas_price: value.min_gas_price.try_cast()?,
268
+ network_id: value.network_id.try_cast()?,
269
+ })
270
+ }
271
+ }
@@ -0,0 +1,185 @@
1
+ mod config;
2
+
3
+ use std::sync::Arc;
4
+
5
+ use edr_eth::remote::jsonrpc;
6
+ use edr_provider::InvalidRequestReason;
7
+ use napi::{tokio::runtime, Env, JsFunction, JsObject, Status};
8
+ use napi_derive::napi;
9
+
10
+ use self::config::ProviderConfig;
11
+ use crate::{
12
+ context::EdrContext,
13
+ logger::{Logger, LoggerConfig, LoggerError},
14
+ subscribe::SubscriberCallback,
15
+ trace::RawTrace,
16
+ };
17
+
18
+ /// A JSON-RPC provider for Ethereum.
19
+ #[napi]
20
+ pub struct Provider {
21
+ provider: Arc<edr_provider::Provider<LoggerError>>,
22
+ }
23
+
24
+ #[napi]
25
+ impl Provider {
26
+ #[doc = "Constructs a new provider with the provided configuration."]
27
+ #[napi(ts_return_type = "Promise<Provider>")]
28
+ pub fn with_config(
29
+ env: Env,
30
+ // We take the context as argument to ensure that tracing is initialized properly.
31
+ _context: &EdrContext,
32
+ config: ProviderConfig,
33
+ logger_config: LoggerConfig,
34
+ #[napi(ts_arg_type = "(event: SubscriptionEvent) => void")] subscriber_callback: JsFunction,
35
+ ) -> napi::Result<JsObject> {
36
+ let config = edr_provider::ProviderConfig::try_from(config)?;
37
+ let runtime = runtime::Handle::current();
38
+
39
+ let logger = Box::new(Logger::new(&env, logger_config)?);
40
+ let subscriber_callback = SubscriberCallback::new(&env, subscriber_callback)?;
41
+ let subscriber_callback = Box::new(move |event| subscriber_callback.call(event));
42
+
43
+ let (deferred, promise) = env.create_deferred()?;
44
+ runtime.clone().spawn_blocking(move || {
45
+ let result = edr_provider::Provider::new(runtime, logger, subscriber_callback, config)
46
+ .map_or_else(
47
+ |error| Err(napi::Error::new(Status::GenericFailure, error.to_string())),
48
+ |provider| {
49
+ Ok(Provider {
50
+ provider: Arc::new(provider),
51
+ })
52
+ },
53
+ );
54
+
55
+ deferred.resolve(|_env| result);
56
+ });
57
+
58
+ Ok(promise)
59
+ }
60
+
61
+ #[doc = "Handles a JSON-RPC request and returns a JSON-RPC response."]
62
+ #[napi]
63
+ pub async fn handle_request(&self, json_request: String) -> napi::Result<Response> {
64
+ let provider = self.provider.clone();
65
+ let request = match serde_json::from_str(&json_request) {
66
+ Ok(request) => request,
67
+ Err(error) => {
68
+ let message = error.to_string();
69
+ let reason = InvalidRequestReason::new(&json_request, &message);
70
+
71
+ // HACK: We need to log failed deserialization attempts when they concern input
72
+ // validation.
73
+ if let Some((method_name, provider_error)) = reason.provider_error() {
74
+ // Ignore potential failure of logging, as returning the original error is more
75
+ // important
76
+ let _result = runtime::Handle::current()
77
+ .spawn_blocking(move || {
78
+ provider.log_failed_deserialization(&method_name, &provider_error)
79
+ })
80
+ .await
81
+ .map_err(|error| {
82
+ napi::Error::new(Status::GenericFailure, error.to_string())
83
+ })?;
84
+ }
85
+
86
+ let data = serde_json::from_str(&json_request).ok();
87
+ let response = jsonrpc::ResponseData::<()>::Error {
88
+ error: jsonrpc::Error {
89
+ code: reason.error_code(),
90
+ message: reason.error_message(),
91
+ data,
92
+ },
93
+ };
94
+
95
+ return serde_json::to_string(&response)
96
+ .map_err(|error| {
97
+ napi::Error::new(
98
+ Status::InvalidArg,
99
+ format!("Invalid JSON `{json_request}` due to: {error}"),
100
+ )
101
+ })
102
+ .map(|json_response| Response {
103
+ solidity_trace: None,
104
+ json: json_response,
105
+ traces: Vec::new(),
106
+ });
107
+ }
108
+ };
109
+
110
+ let mut response = runtime::Handle::current()
111
+ .spawn_blocking(move || provider.handle_request(request))
112
+ .await
113
+ .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))?;
114
+
115
+ // We can take the solidity trace as it won't be used for anything else
116
+ let solidity_trace = response.as_mut().err().and_then(|error| {
117
+ if let edr_provider::ProviderError::TransactionFailed(failure) = error {
118
+ if matches!(
119
+ failure.failure.reason,
120
+ edr_provider::TransactionFailureReason::OutOfGas(_)
121
+ ) {
122
+ None
123
+ } else {
124
+ Some(Arc::new(std::mem::take(
125
+ &mut failure.failure.solidity_trace,
126
+ )))
127
+ }
128
+ } else {
129
+ None
130
+ }
131
+ });
132
+
133
+ // We can take the traces as they won't be used for anything else
134
+ let traces = match &mut response {
135
+ Ok(response) => std::mem::take(&mut response.traces),
136
+ Err(edr_provider::ProviderError::TransactionFailed(failure)) => {
137
+ std::mem::take(&mut failure.traces)
138
+ }
139
+ Err(_) => Vec::new(),
140
+ };
141
+
142
+ let response = jsonrpc::ResponseData::from(response.map(|response| response.result));
143
+
144
+ serde_json::to_string(&response)
145
+ .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))
146
+ .map(|json_response| Response {
147
+ solidity_trace,
148
+ json: json_response,
149
+ traces: traces.into_iter().map(Arc::new).collect(),
150
+ })
151
+ }
152
+ }
153
+
154
+ #[napi]
155
+ pub struct Response {
156
+ json: String,
157
+ /// When a transaction fails to execute, the provider returns a trace of the
158
+ /// transaction.
159
+ solidity_trace: Option<Arc<edr_evm::trace::Trace>>,
160
+ /// This may contain zero or more traces, depending on the (batch) request
161
+ traces: Vec<Arc<edr_evm::trace::Trace>>,
162
+ }
163
+
164
+ #[napi]
165
+ impl Response {
166
+ #[napi(getter)]
167
+ pub fn json(&self) -> String {
168
+ self.json.clone()
169
+ }
170
+
171
+ #[napi(getter)]
172
+ pub fn solidity_trace(&self) -> Option<RawTrace> {
173
+ self.solidity_trace
174
+ .as_ref()
175
+ .map(|trace| RawTrace::new(trace.clone()))
176
+ }
177
+
178
+ #[napi(getter)]
179
+ pub fn traces(&self) -> Vec<RawTrace> {
180
+ self.traces
181
+ .iter()
182
+ .map(|trace| RawTrace::new(trace.clone()))
183
+ .collect()
184
+ }
185
+ }