Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: pass build info as buffer #780

Merged
merged 10 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/honest-crews-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/edr": patch
---

Improved provider initialization performance by passing build info as buffer which avoids FFI copy overhead
9 changes: 8 additions & 1 deletion crates/edr_napi/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,13 @@ export interface ProviderConfig {
/** The network ID of the blockchain */
networkId: bigint
}
/** Tracing config for Solidity stack trace generation. */
export interface TracingConfigWithBuffers {
/** Build information to use for decoding contracts. */
buildInfos?: Array<Uint8Array>
/** Whether to ignore contracts whose name starts with "Ignored". */
ignoreContracts?: boolean
}
/** The possible reasons for successful termination of the EVM. */
export const enum SuccessReason {
/** The opcode `STOP` was called */
Expand Down Expand Up @@ -595,7 +602,7 @@ export declare class EdrContext {
/** A JSON-RPC provider for Ethereum. */
export declare class Provider {
/**Constructs a new provider with the provided configuration. */
static withConfig(context: EdrContext, config: ProviderConfig, loggerConfig: LoggerConfig, tracingConfig: any, subscriberCallback: (event: SubscriptionEvent) => void): Promise<Provider>
static withConfig(context: EdrContext, config: ProviderConfig, loggerConfig: LoggerConfig, tracingConfig: TracingConfigWithBuffers, subscriberCallback: (event: SubscriptionEvent) => void): Promise<Provider>
/**Handles a JSON-RPC request and returns a JSON-RPC response. */
handleRequest(jsonRequest: string): Promise<Response>
setCallOverrideCallback(callOverrideCallback: (contract_address: Buffer, data: Buffer) => Promise<CallOverrideResult | undefined>): void
Expand Down
42 changes: 37 additions & 5 deletions crates/edr_napi/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ use std::sync::Arc;

use edr_provider::{time::CurrentTime, InvalidRequestReason};
use edr_rpc_eth::jsonrpc;
use edr_solidity::contract_decoder::ContractDecoder;
use napi::{tokio::runtime, Either, Env, JsFunction, JsObject, Status};
use edr_solidity::{artifacts::BuildInfoConfig, contract_decoder::ContractDecoder};
use napi::{
bindgen_prelude::Uint8Array, tokio::runtime, Either, Env, JsFunction, JsObject, Status,
};
use napi_derive::napi;

use self::config::ProviderConfig;
Expand Down Expand Up @@ -37,16 +39,16 @@ impl Provider {
_context: &EdrContext,
config: ProviderConfig,
logger_config: LoggerConfig,
tracing_config: serde_json::Value,
tracing_config: TracingConfigWithBuffers,
#[napi(ts_arg_type = "(event: SubscriptionEvent) => void")] subscriber_callback: JsFunction,
) -> napi::Result<JsObject> {
let runtime = runtime::Handle::current();

let config = edr_provider::ProviderConfig::try_from(config)?;

// TODO https://github.com/NomicFoundation/edr/issues/760
let build_info_config: edr_solidity::contract_decoder::BuildInfoConfig =
serde_json::from_value(tracing_config)?;
let build_info_config = BuildInfoConfig::parse_from_buffers((&tracing_config).into())
.map_err(|err| napi::Error::from_reason(err.to_string()))?;
let contract_decoder = ContractDecoder::new(&build_info_config)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
let contract_decoder = Arc::new(contract_decoder);
Expand Down Expand Up @@ -245,6 +247,36 @@ impl Provider {
}
}

/// Tracing config for Solidity stack trace generation.
#[napi(object)]
pub struct TracingConfigWithBuffers {
/// Build information to use for decoding contracts.
pub build_infos: Option<Vec<Uint8Array>>,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapping in an Option is a change from the design doc, but it maintains backwards compatibility as the TracingConfig in Hardhat has the buildInfo as an optional parameter.

/// Whether to ignore contracts whose name starts with "Ignored".
pub ignore_contracts: Option<bool>,
}

impl<'a> From<&'a TracingConfigWithBuffers>
for edr_solidity::artifacts::BuildInfoConfigWithBuffers<'a>
{
fn from(value: &'a TracingConfigWithBuffers) -> Self {
let build_infos = value
.build_infos
.as_ref()
.map(|infos| {
infos
.iter()
.map(std::convert::AsRef::as_ref)
.collect::<Vec<_>>()
})
.unwrap_or_default();
Self {
build_infos,
ignore_contracts: value.ignore_contracts,
}
}
}

#[derive(Debug)]
struct SolidityTraceData {
trace: Arc<edr_evm::trace::Trace>,
Expand Down
8 changes: 3 additions & 5 deletions crates/edr_solidity/benches/contracts_identifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use std::{fs, path::PathBuf, time::Duration};

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use edr_solidity::{
artifacts::BuildInfo,
contract_decoder::{BuildInfoConfig, ContractDecoder},
artifacts::{BuildInfo, BuildInfoConfig},
contract_decoder::ContractDecoder,
};

const FORGE_STD_ARTIFACTS_DIR: &str = "EDR_FORGE_STD_ARTIFACTS_DIR";
Expand All @@ -39,7 +39,7 @@ fn load_build_info_config() -> anyhow::Result<Option<BuildInfoConfig>> {
}

Ok(Some(BuildInfoConfig {
build_infos: Some(build_infos),
build_infos,
ignore_contracts: None,
}))
}
Expand All @@ -51,8 +51,6 @@ pub fn criterion_benchmark(c: &mut Criterion) {

let contracts = &build_info_config
.build_infos
.as_ref()
.expect("loaded build info")
.first()
.expect("there is at least one build info")
.output
Expand Down
65 changes: 65 additions & 0 deletions crates/edr_solidity/src/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,71 @@ use std::collections::HashMap;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};

/// Error in the build info config
#[derive(Debug, thiserror::Error)]
pub enum BuildInfoConfigError {
/// JSON deserialization error
#[error(transparent)]
Json(#[from] serde_json::Error),
/// Invalid semver in the build info
#[error(transparent)]
Semver(#[from] semver::Error),
}

/// Configuration for the [`crate::contract_decoder::ContractDecoder`].
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildInfoConfig {
/// Build information to use for decoding contracts.
pub build_infos: Vec<BuildInfo>,
/// Whether to ignore contracts whose name starts with "Ignored".
pub ignore_contracts: Option<bool>,
}

impl BuildInfoConfig {
/// Parse the config from bytes. This is a performance intensive operation
/// which is why it's not a `TryFrom` implementation.
pub fn parse_from_buffers(
config: BuildInfoConfigWithBuffers<'_>,
) -> Result<Self, BuildInfoConfigError> {
let BuildInfoConfigWithBuffers {
build_infos,
ignore_contracts,
} = config;

let build_infos = build_infos
.into_iter()
.filter_map(|array| parse_build_info(array).transpose())
.collect::<Result<Vec<_>, _>>()?;

Ok(Self {
build_infos,
ignore_contracts,
})
}
}

fn parse_build_info(array: &[u8]) -> Result<Option<BuildInfo>, BuildInfoConfigError> {
let build_info = serde_json::from_slice::<BuildInfo>(array)?;
let solc_version = build_info.solc_version.parse::<semver::Version>()?;

if crate::compiler::FIRST_SOLC_VERSION_SUPPORTED <= solc_version {
Ok(Some(build_info))
} else {
Ok(None)
}
}

/// Configuration for the [`crate::contract_decoder::ContractDecoder`] unparsed
/// build infos.
#[derive(Clone, Debug)]
pub struct BuildInfoConfigWithBuffers<'a> {
/// Build information to use for decoding contracts.
pub build_infos: Vec<&'a [u8]>,
/// Whether to ignore contracts whose name starts with "Ignored".
pub ignore_contracts: Option<bool>,
}

/// A `BuildInfo` is a file that contains all the information of a solc run. It
/// includes all the necessary information to recreate that exact same run, and
/// all of its output.
Expand Down
3 changes: 3 additions & 0 deletions crates/edr_solidity/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ use crate::{
source_map::decode_instructions,
};

/// First Solc version supported for stack trace generation
pub const FIRST_SOLC_VERSION_SUPPORTED: semver::Version = semver::Version::new(0, 5, 1);

/// For the Solidity compiler version and its standard JSON input and
/// output[^1], creates the source model, decodes the bytecode with source
/// mapping and links them to the source files.
Expand Down
19 changes: 2 additions & 17 deletions crates/edr_solidity/src/contract_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::sync::Arc;

use edr_eth::Bytes;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};

use super::{
nested_trace::CreateMessage,
Expand All @@ -13,23 +12,13 @@ use super::{
},
};
use crate::{
artifacts::BuildInfo,
artifacts::BuildInfoConfig,
build_model::{ContractFunctionType, ContractMetadata},
compiler::create_models_and_decode_bytecodes,
contracts_identifier::ContractsIdentifier,
nested_trace::{NestedTrace, NestedTraceStep},
};

/// Configuration for the [`ContractDecoder`].
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildInfoConfig {
/// Build information to use for decoding contracts.
pub build_infos: Option<Vec<BuildInfo>>,
/// Whether to ignore contracts.
pub ignore_contracts: Option<bool>,
}

/// Errors that can occur during the decoding of the nested trace.
#[derive(Debug, thiserror::Error)]
pub enum ContractDecoderError {
Expand Down Expand Up @@ -219,11 +208,7 @@ fn initialize_contracts_identifier(
) -> anyhow::Result<ContractsIdentifier> {
let mut contracts_identifier = ContractsIdentifier::default();

let Some(build_infos) = &config.build_infos else {
return Ok(contracts_identifier);
};

for build_info in build_infos {
for build_info in &config.build_infos {
let bytecodes = create_models_and_decode_bytecodes(
build_info.solc_version.clone(),
&build_info.input,
Expand Down
2 changes: 1 addition & 1 deletion crates/tools/js/benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"eslint-plugin-import": "2.27.5",
"eslint-plugin-mocha": "10.4.1",
"eslint-plugin-prettier": "5.2.1",
"hardhat": "2.22.17",
"hardhat": "2.22.18",
"lodash": "^4.17.11",
"mocha": "^10.0.0",
"prettier": "^3.2.5",
Expand Down
2 changes: 1 addition & 1 deletion hardhat-tests/integration/smock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"@types/node": "^20.0.0",
"chai": "^4.3.6",
"ethers": "5.7.2",
"hardhat": "2.22.17",
"hardhat": "2.22.18",
"mocha": "^10.0.0"
},
"keywords": [],
Expand Down
2 changes: 1 addition & 1 deletion hardhat-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"ethereumjs-abi": "^0.6.8",
"ethers": "^6.1.0",
"fs-extra": "^7.0.1",
"hardhat": "2.22.17",
"hardhat": "2.22.18",
"mocha": "^10.0.0",
"prettier": "^3.2.5",
"rimraf": "^3.0.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@ import {
} from "@nomicfoundation/ethereumjs-util";

import { defaultHardhatNetworkParams } from "hardhat/internal/core/config/default-config";
import {
MempoolOrder,
TracingConfig,
} from "hardhat/internal/hardhat-network/provider/node-types";
import { MempoolOrder } from "hardhat/internal/hardhat-network/provider/node-types";
import { EdrProviderWrapper } from "hardhat/internal/hardhat-network/provider/provider";
import { LoggerConfig } from "hardhat/internal/hardhat-network/provider/modules/logger";
import { SolidityStackTrace } from "hardhat/internal/hardhat-network/stack-traces/solidity-stack-trace";
import { Response } from "@nomicfoundation/edr";
import { Response, TracingConfigWithBuffers } from "@nomicfoundation/edr";

function toBuffer(x: Parameters<typeof toBytes>[0]) {
return Buffer.from(toBytes(x));
Expand All @@ -28,7 +25,7 @@ const senderAddress = bytesToHex(privateToAddress(toBuffer(senderPrivateKey)));

export async function instantiateProvider(
loggerConfig: LoggerConfig,
tracingConfig: TracingConfig
tracingConfig: TracingConfigWithBuffers
): Promise<EdrProviderWrapper> {
const config = {
hardfork: "cancun",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import {
getLatestSupportedSolcVersion,
linkHexStringBytecode,
stackTraceEntryTypeToString,
TracingConfigWithBuffers,
} from "@nomicfoundation/edr";
import { toBytes } from "@nomicfoundation/ethereumjs-util";
import { assert } from "chai";
import { BUILD_INFO_FORMAT_VERSION } from "hardhat/internal/constants";
import { TracingConfig } from "hardhat/internal/hardhat-network/provider/node-types";
import { EdrProviderWrapper } from "hardhat/internal/hardhat-network/provider/provider";
import { ReturnData } from "hardhat/internal/hardhat-network/provider/return-data";
import {
Expand Down Expand Up @@ -466,8 +466,8 @@ async function runTest(
output: compilerOutput,
};

const tracingConfig: TracingConfig = {
buildInfos: [buildInfo],
const tracingConfig: TracingConfigWithBuffers = {
buildInfos: [Buffer.from(JSON.stringify(buildInfo))],
ignoreContracts: true,
};

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"hardhat>@nomicfoundation/edr": "workspace:*"
},
"patchedDependencies": {
"[email protected].17": "patches/[email protected].17.patch"
"[email protected].18": "patches/[email protected].18.patch"
}
},
"private": true,
Expand Down
Loading
Loading