Skip to content
6 changes: 6 additions & 0 deletions core/bin/external_node/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ impl ExternalNodeConfig {

impl From<&LocalConfig> for InternalApiConfigBase {
fn from(config: &LocalConfig) -> Self {
let state_keeper_config = &config.state_keeper;
let web3_rpc = &config.api.web3_json_rpc;
Self {
l1_chain_id: config.networks.l1_chain_id,
Expand All @@ -216,6 +217,11 @@ impl From<&LocalConfig> for InternalApiConfigBase {
filters_disabled: web3_rpc.filters_disabled,
l1_to_l2_txs_paused: false,
eth_call_gas_cap: web3_rpc.eth_call_gas_cap,
send_raw_tx_sync_max_timeout_ms: web3_rpc.send_raw_tx_sync_max_timeout_ms,
send_raw_tx_sync_default_timeout_ms: web3_rpc.send_raw_tx_sync_default_timeout_ms,
send_raw_tx_sync_poll_interval_ms: state_keeper_config
.l2_block_commit_deadline
.as_millis() as u64,
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions core/bin/external_node/src/config/tests/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ api:
fee_history_limit: 100
whitelisted_tokens_for_aa:
- '0x0000000000000000000000000000000000000001'
send_raw_tx_sync_max_timeout_ms: 10000
send_raw_tx_sync_default_timeout_ms: 2000

contracts:
l1:
Expand All @@ -93,6 +95,7 @@ state_keeper:
l2_block_seal_queue_capacity: 20
save_call_traces: false
protective_reads_persistence_enabled: true
l2_block_commit_deadline_ms: 1000
commitment_generator:
max_parallelism: 4
timestamp_asserter:
Expand Down
4 changes: 4 additions & 0 deletions core/bin/external_node/src/config/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ fn parsing_from_full_env() {
EN_L2_BLOCK_SEAL_QUEUE_CAPACITY=20
EN_PROTECTIVE_READS_PERSISTENCE_ENABLED=true
EN_L2_BLOCK_COMMIT_DEADLINE_MS=1000
# NEW PARAMS: From SharedStateKeeperConfig
EN_SAVE_CALL_TRACES=false
Expand Down Expand Up @@ -305,6 +306,9 @@ fn parsing_from_full_env() {
EN_API_TREE_API_REMOTE_URL=http://tree/
# Tree component config
EN_TREE_API_PORT=2955
EN_API_WEB3_JSON_RPC_SEND_RAW_TX_SYNC_MAX_TIMEOUT_MS=10000
EN_API_WEB3_JSON_RPC_SEND_RAW_TX_SYNC_DEFAULT_TIMEOUT_MS=2000
"#;
let env = smart_config::Environment::from_dotenv("test.env", env)
.unwrap()
Expand Down
5 changes: 3 additions & 2 deletions core/bin/zksync_server/src/node_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,8 +462,9 @@ impl MainNodeBuilder {
pruning_info_refresh_interval: Duration::from_secs(10),
polling_interval: rpc_config.pubsub_polling_interval,
};
let base = InternalApiConfigBase::new(&self.genesis_config, &rpc_config)
.with_l1_to_l2_txs_paused(self.configs.mempool_config.l1_to_l2_txs_paused);
let base =
InternalApiConfigBase::new(&self.genesis_config, &rpc_config, &state_keeper_config)
.with_l1_to_l2_txs_paused(self.configs.mempool_config.l1_to_l2_txs_paused);
Ok((base, optional_config))
}

Expand Down
14 changes: 14 additions & 0 deletions core/lib/config/src/configs/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,12 @@ pub struct Web3JsonRpcConfig {
/// (hundreds or thousands RPS).
#[config(default, alias = "extended_rpc_tracing")]
pub extended_api_tracing: bool,
/// Maximum timeout for `eth_sendRawTransactionSync` in milliseconds.
#[config(default_t = 10_000)]
pub send_raw_tx_sync_max_timeout_ms: u64,
/// Default timeout for `eth_sendRawTransactionSync` in milliseconds.
#[config(default_t = 2_000)]
pub send_raw_tx_sync_default_timeout_ms: u64,
}

impl Web3JsonRpcConfig {
Expand Down Expand Up @@ -546,6 +552,8 @@ mod tests {
extended_api_tracing: true,
gas_price_scale_factor_open_batch: Some(1.3),
eth_call_gas_cap: None,
send_raw_tx_sync_max_timeout_ms: 10000,
send_raw_tx_sync_default_timeout_ms: 2000,
},
healthcheck: HealthCheckConfig {
port: 8081.into(),
Expand Down Expand Up @@ -592,6 +600,8 @@ mod tests {
API_WEB3_JSON_RPC_WEBSOCKET_REQUESTS_PER_MINUTE_LIMIT=10
API_WEB3_JSON_RPC_MEMPOOL_CACHE_SIZE=10000
API_WEB3_JSON_RPC_MEMPOOL_CACHE_UPDATE_INTERVAL=50
API_WEB3_JSON_RPC_SEND_RAW_TX_SYNC_MAX_TIMEOUT_MS=10000
API_WEB3_JSON_RPC_SEND_RAW_TX_SYNC_DEFAULT_TIMEOUT_MS=2000
API_CONTRACT_VERIFICATION_PORT="3070"
API_CONTRACT_VERIFICATION_URL="http://127.0.0.1:3070"
API_WEB3_JSON_RPC_TREE_API_URL="http://tree/"
Expand Down Expand Up @@ -658,6 +668,8 @@ mod tests {
eth_call_gas_cap: null
request_timeout_sec: 20
tree_api_url: "http://tree/"
send_raw_tx_sync_max_timeout_ms: 10000
send_raw_tx_sync_default_timeout_ms: 2000
prometheus:
listener_port: 3312
pushgateway_url: http://127.0.0.1:9091
Expand Down Expand Up @@ -721,6 +733,8 @@ mod tests {
eth_call_gas_cap: null
request_timeout: 20s
tree_api_url: "http://tree/"
send_raw_tx_sync_max_timeout_ms: 10000
send_raw_tx_sync_default_timeout_ms: 2000
prometheus:
listener_port: 3312
pushgateway_url: http://127.0.0.1:9091
Expand Down
16 changes: 9 additions & 7 deletions core/lib/config/src/configs/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ pub struct SharedStateKeeperConfig {
#[config(default_t = 10)]
pub l2_block_seal_queue_capacity: usize,

/// Deadline after which an L2 block should be sealed by the timeout sealer.
#[config(deprecated = "miniblock_commit_deadline")]
#[config(default_t = Duration::from_secs(1))]
pub l2_block_commit_deadline: Duration,

/// Whether to save call traces when processing blocks in the state keeper.
#[config(default_t = true)]
pub save_call_traces: bool,
Expand Down Expand Up @@ -127,10 +132,6 @@ pub struct StateKeeperConfig {
#[config(deprecated = "block_commit_deadline")]
#[config(default_t = Duration::from_millis(2_500))]
pub l1_batch_commit_deadline: Duration,
/// Deadline after which an L2 block should be sealed by the timeout sealer.
#[config(deprecated = "miniblock_commit_deadline")]
#[config(default_t = Duration::from_secs(1))]
pub l2_block_commit_deadline: Duration,
/// The max payload size threshold that triggers sealing of an L2 block.
#[config(deprecated = "miniblock_max_payload_size")]
#[config(default_t = ByteSize(1_000_000), with = Fallback(SizeUnit::Bytes))]
Expand Down Expand Up @@ -177,10 +178,11 @@ impl StateKeeperConfig {
/// Values mostly repeat the values used in the localhost environment.
pub fn for_tests() -> Self {
Self {
shared: SharedStateKeeperConfig::default(),
shared: SharedStateKeeperConfig {
..SharedStateKeeperConfig::default()
},
seal_criteria: SealCriteriaConfig::for_tests(),
l1_batch_commit_deadline: Duration::from_millis(2500),
l2_block_commit_deadline: Duration::from_secs(1),
l2_block_max_payload_size: ByteSize(1_000_000),
max_single_tx_gas: 6000000,
max_allowed_l2_tx_gas_limit: 4000000000,
Expand Down Expand Up @@ -281,6 +283,7 @@ mod tests {
StateKeeperConfig {
shared: SharedStateKeeperConfig {
l2_block_seal_queue_capacity: 10,
l2_block_commit_deadline: Duration::from_millis(1000),
save_call_traces: false,
protective_reads_persistence_enabled: true,
},
Expand All @@ -296,7 +299,6 @@ mod tests {
max_circuits_per_batch: 24100,
},
l1_batch_commit_deadline: Duration::from_millis(2500),
l2_block_commit_deadline: Duration::from_millis(1000),
l2_block_max_payload_size: ByteSize(1_000_000),
max_single_tx_gas: 1_000_000,
max_allowed_l2_tx_gas_limit: 2_000_000_000,
Expand Down
6 changes: 6 additions & 0 deletions core/lib/web3_decl/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ pub enum Web3Error {
InternalError(#[from] anyhow::Error),
#[error("Server is shutting down")]
ServerShuttingDown,
#[error("Transaction {0:?} timeout waiting for receipt")]
TransactionTimeout(zksync_types::H256),
#[error("Transaction processing error: {0}")]
TransactionUnready(String),
#[error("Invalid timeout. Max timeout is {0}ms")]
InvalidTimeout(u64),
}

/// Client RPC error with additional details: the method name and arguments of the called method.
Expand Down
7 changes: 7 additions & 0 deletions core/lib/web3_decl/src/namespaces/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,13 @@ pub trait EthNamespace {
#[method(name = "sendRawTransaction")]
async fn send_raw_transaction(&self, tx_bytes: Bytes) -> RpcResult<H256>;

#[method(name = "sendRawTransactionSync")]
async fn send_raw_transaction_sync(
&self,
tx_bytes: Bytes,
max_wait_ms: Option<U256>,
) -> RpcResult<TransactionReceipt>;

#[method(name = "syncing")]
async fn syncing(&self) -> RpcResult<SyncState>;

Expand Down
3 changes: 3 additions & 0 deletions core/node/api_server/src/web3/backend_jsonrpsee/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,13 @@ impl MethodTracer {
| Web3Error::TooManyTopics
| Web3Error::FilterNotFound
| Web3Error::InvalidFilterBlockHash
| Web3Error::InvalidTimeout(_)
| Web3Error::LogsLimitExceeded(_, _, _) => ErrorCode::InvalidParams.code(),
Web3Error::SubmitTransactionError(_, _)
| Web3Error::SerializationError(_)
| Web3Error::ProxyError(_) => 3,
Web3Error::TransactionTimeout(_) => 4,
Web3Error::TransactionUnready(_) => 5,
Web3Error::TreeApiUnavailable => 6,
Web3Error::ServerShuttingDown => ErrorCode::ServerIsBusy.code(),
};
Expand Down
10 changes: 10 additions & 0 deletions core/node/api_server/src/web3/backend_jsonrpsee/namespaces/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,16 @@ impl EthNamespaceServer for EthNamespace {
.map_err(|err| self.current_method().map_err(err))
}

async fn send_raw_transaction_sync(
&self,
tx_bytes: Bytes,
max_wait_ms: Option<U256>,
) -> RpcResult<TransactionReceipt> {
self.send_raw_transaction_sync_impl(tx_bytes, max_wait_ms)
.await
.map_err(|err| self.current_method().map_err(err))
}

async fn syncing(&self) -> RpcResult<SyncState> {
Ok(self.syncing_impl())
}
Expand Down
6 changes: 6 additions & 0 deletions core/node/api_server/src/web3/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ enum Web3ErrorKind {
LogsLimitExceeded,
InvalidFilterBlockHash,
TreeApiUnavailable,
TransactionTimeout,
TransactionUnready,
InvalidTimeout,
Internal,
}

Expand All @@ -189,6 +192,9 @@ impl Web3ErrorKind {
Web3Error::LogsLimitExceeded(..) => Self::LogsLimitExceeded,
Web3Error::InvalidFilterBlockHash => Self::InvalidFilterBlockHash,
Web3Error::TreeApiUnavailable => Self::TreeApiUnavailable,
Web3Error::TransactionTimeout(_) => Self::TransactionTimeout,
Web3Error::TransactionUnready(_) => Self::TransactionUnready,
Web3Error::InvalidTimeout(_) => Self::InvalidTimeout,
Web3Error::InternalError(_)
| Web3Error::MethodNotImplemented
| Web3Error::ServerShuttingDown => Self::Internal,
Expand Down
46 changes: 46 additions & 0 deletions core/node/api_server/src/web3/namespaces/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,52 @@ impl EthNamespace {
.map_err(|err| self.current_method().map_submit_err(err))
}

pub async fn send_raw_transaction_sync_impl(
&self,
tx_bytes: Bytes,
max_wait_ms: Option<U256>,
) -> Result<TransactionReceipt, Web3Error> {
let timeout_ms = if let Some(timeout) = max_wait_ms {
let timeout_u64 = timeout.as_u64();
if timeout_u64 > self.state.api_config.send_raw_tx_sync_max_timeout_ms {
return Err(Web3Error::InvalidTimeout(
self.state.api_config.send_raw_tx_sync_max_timeout_ms,
));
}
timeout_u64
} else {
self.state.api_config.send_raw_tx_sync_default_timeout_ms
};

// Submit transaction and get hash
let hash = self.send_raw_transaction_impl(tx_bytes).await?;

// Poll for receipt at regular intervals
let poll_interval = std::time::Duration::from_millis(
self.state.api_config.send_raw_tx_sync_poll_interval_ms,
);
let mut interval = tokio::time::interval(poll_interval);
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);

let deadline = tokio::time::sleep(std::time::Duration::from_millis(timeout_ms));
tokio::pin!(deadline);

// Check immediately, then poll at intervals
loop {
tokio::select! {
_ = &mut deadline => {
return Err(Web3Error::TransactionTimeout(hash));
}
_ = interval.tick() => {
if let Some(receipt) = self.get_transaction_receipt_impl(hash).await? {
return Ok(receipt);
}
// Continue waiting
}
}
}
}

pub fn accounts_impl(&self) -> Vec<Address> {
Vec::new()
}
Expand Down
27 changes: 22 additions & 5 deletions core/node/api_server/src/web3/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use vise::GaugeGuard;
use zksync_config::{
configs::{
api::Web3JsonRpcConfig,
chain::StateKeeperConfig,
contracts::{
chain::L2Contracts,
ecosystem::{EcosystemCommonContracts, L1SpecificContracts},
Expand Down Expand Up @@ -122,10 +123,17 @@ pub struct InternalApiConfigBase {
pub filters_disabled: bool,
pub l1_to_l2_txs_paused: bool,
pub eth_call_gas_cap: Option<u64>,
pub send_raw_tx_sync_default_timeout_ms: u64,
pub send_raw_tx_sync_max_timeout_ms: u64,
pub send_raw_tx_sync_poll_interval_ms: u64,
}

impl InternalApiConfigBase {
pub fn new(genesis: &GenesisConfig, web3_config: &Web3JsonRpcConfig) -> Self {
pub fn new(
genesis: &GenesisConfig,
web3_config: &Web3JsonRpcConfig,
state_keeper_config: &StateKeeperConfig,
) -> Self {
Self {
l1_chain_id: genesis.l1_chain_id,
l2_chain_id: genesis.l2_chain_id,
Expand All @@ -139,6 +147,12 @@ impl InternalApiConfigBase {
filters_disabled: web3_config.filters_disabled,
l1_to_l2_txs_paused: false,
eth_call_gas_cap: web3_config.eth_call_gas_cap,
send_raw_tx_sync_default_timeout_ms: web3_config.send_raw_tx_sync_default_timeout_ms,
send_raw_tx_sync_max_timeout_ms: web3_config.send_raw_tx_sync_max_timeout_ms,
send_raw_tx_sync_poll_interval_ms: state_keeper_config
.shared
.l2_block_commit_deadline
.as_millis() as u64,
}
}

Expand Down Expand Up @@ -180,6 +194,9 @@ pub struct InternalApiConfig {
pub l1_to_l2_txs_paused: bool,
pub settlement_layer: Option<SettlementLayer>,
pub eth_call_gas_cap: Option<u64>,
pub send_raw_tx_sync_default_timeout_ms: u64,
pub send_raw_tx_sync_max_timeout_ms: u64,
pub send_raw_tx_sync_poll_interval_ms: u64,
}

impl InternalApiConfig {
Expand Down Expand Up @@ -228,20 +245,20 @@ impl InternalApiConfig {
l1_to_l2_txs_paused: base.l1_to_l2_txs_paused,
settlement_layer,
eth_call_gas_cap: base.eth_call_gas_cap,
send_raw_tx_sync_default_timeout_ms: base.send_raw_tx_sync_default_timeout_ms,
send_raw_tx_sync_max_timeout_ms: base.send_raw_tx_sync_max_timeout_ms,
send_raw_tx_sync_poll_interval_ms: base.send_raw_tx_sync_poll_interval_ms,
}
}

pub fn new(
web3_config: &Web3JsonRpcConfig,
base: InternalApiConfigBase,
l1_contracts_config: &SettlementLayerSpecificContracts,
l1_ecosystem_contracts: &L1SpecificContracts,
l2_contracts: &L2Contracts,
genesis_config: &GenesisConfig,
l1_to_l2_txs_paused: bool,
settlement_layer: SettlementLayer,
) -> Self {
let base = InternalApiConfigBase::new(genesis_config, web3_config)
.with_l1_to_l2_txs_paused(l1_to_l2_txs_paused);
Self::from_base_and_contracts(
base,
l1_contracts_config,
Expand Down
Loading
Loading