Skip to content

Commit

Permalink
feat: add block.timestamp asserter for AA (#3031)
Browse files Browse the repository at this point in the history
This PR adds the ability to use `block.timestamp` in custom AA
contracts. AAs will still not have direct access to `block.timestamp`,
but can utilize it via a proxy that enforces certain constraints. The PR
introduces a `TimestampAsserter` contract that is deployed on every
chain to the user space, similar to `Multicall3`. This contract has a
single function, `assertTimestampInRange(start, end)`, which can be used
by AAs at their discretion.

The `TimestampAsserter` contract ensures that `block.timestamp` falls
within the specified `(start, end)` range. Additionally, the sequencer
verifies that the `block.timestamp` is sufficiently far from the range’s
end. This is to prevent DoS attacks where transactions pass validation
but get stuck in the mempool during execution. This constraint is
configurable and can be adjusted without requiring protocol update.

The PR also introduces two new fields to the `transactions` table:
`timestamp_asserter_range_start` and `timestamp_asserter_range_end`.
These fields are extracted during transaction execution in the sandbox
by the `ValidationTracer`. If multiple assertions are made in a single
transaction, the system captures the maximum of the starts and the
minimum of the ends, resulting in the narrowest possible time range.

Transactions with time range constraints will undergo additional
verification before being included in a block. If the current time falls
outside the transaction’s specified time range, the transaction will be
rejected with an appropriate message.

Sister PR in `era-contracts`:
matter-labs/era-contracts#843

---------

Signed-off-by: Danil <[email protected]>
Co-authored-by: Danil <[email protected]>
  • Loading branch information
ischasny and Deniallugo authored Nov 1, 2024
1 parent 1cfd426 commit 069d38d
Show file tree
Hide file tree
Showing 87 changed files with 1,491 additions and 265 deletions.
84 changes: 67 additions & 17 deletions core/bin/external_node/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{
env,
ffi::OsString,
future::Future,
num::{NonZeroU32, NonZeroU64, NonZeroUsize},
path::PathBuf,
time::Duration,
Expand All @@ -24,7 +25,7 @@ use zksync_core_leftovers::temp_config_store::read_yaml_repr;
use zksync_dal::{ConnectionPool, Core};
use zksync_metadata_calculator::MetadataCalculatorRecoveryConfig;
use zksync_node_api_server::{
tx_sender::TxSenderConfig,
tx_sender::{TimestampAsserterParams, TxSenderConfig},
web3::{state::InternalApiConfig, Namespace},
};
use zksync_protobuf_config::proto;
Expand Down Expand Up @@ -121,6 +122,7 @@ pub(crate) struct RemoteENConfig {
pub l1_weth_bridge_addr: Option<Address>,
pub l2_weth_bridge_addr: Option<Address>,
pub l2_testnet_paymaster_addr: Option<Address>,
pub l2_timestamp_asserter_addr: Option<Address>,
pub base_token_addr: Address,
pub l1_batch_commit_data_generator_mode: L1BatchCommitmentMode,
pub dummy_verifier: bool,
Expand All @@ -146,22 +148,19 @@ impl RemoteENConfig {
.get_main_contract()
.rpc_context("get_main_contract")
.await?;
let base_token_addr = match client.get_base_token_l1_address().await {
Err(ClientError::Call(err))
if [
ErrorCode::MethodNotFound.code(),
// This what `Web3Error::NotImplemented` gets
// `casted` into in the `api` server.
ErrorCode::InternalError.code(),
]
.contains(&(err.code())) =>
{
// This is the fallback case for when the EN tries to interact
// with a node that does not implement the `zks_baseTokenL1Address` endpoint.
ETHEREUM_ADDRESS
}
response => response.context("Failed to fetch base token address")?,
};

let timestamp_asserter_address = handle_rpc_response_with_fallback(
client.get_timestamp_asserter(),
None,
"Failed to fetch timestamp asserter address".to_string(),
)
.await?;
let base_token_addr = handle_rpc_response_with_fallback(
client.get_base_token_l1_address(),
ETHEREUM_ADDRESS,
"Failed to fetch base token address".to_string(),
)
.await?;

// These two config variables should always have the same value.
// TODO(EVM-578): double check and potentially forbid both of them being `None`.
Expand Down Expand Up @@ -206,6 +205,7 @@ impl RemoteENConfig {
.as_ref()
.map(|a| a.dummy_verifier)
.unwrap_or_default(),
l2_timestamp_asserter_addr: timestamp_asserter_address,
})
}

Expand All @@ -227,10 +227,36 @@ impl RemoteENConfig {
l2_legacy_shared_bridge_addr: Some(Address::repeat_byte(7)),
l1_batch_commit_data_generator_mode: L1BatchCommitmentMode::Rollup,
dummy_verifier: true,
l2_timestamp_asserter_addr: None,
}
}
}

async fn handle_rpc_response_with_fallback<T, F>(
rpc_call: F,
fallback: T,
context: String,
) -> anyhow::Result<T>
where
F: Future<Output = Result<T, ClientError>>,
T: Clone,
{
match rpc_call.await {
Err(ClientError::Call(err))
if [
ErrorCode::MethodNotFound.code(),
// This what `Web3Error::NotImplemented` gets
// `casted` into in the `api` server.
ErrorCode::InternalError.code(),
]
.contains(&(err.code())) =>
{
Ok(fallback)
}
response => response.context(context),
}
}

/// This part of the external node config is completely optional to provide.
/// It can tweak limits of the API, delay intervals of certain components, etc.
/// If any of the fields are not provided, the default values will be used.
Expand Down Expand Up @@ -454,6 +480,9 @@ pub(crate) struct OptionalENConfig {
pub gateway_url: Option<SensitiveUrl>,
/// Interval for bridge addresses refreshing in seconds.
bridge_addresses_refresh_interval_sec: Option<NonZeroU64>,
/// Minimum time between current block.timestamp and the end of the asserted range for TimestampAsserter
#[serde(default = "OptionalENConfig::default_timestamp_asserter_min_time_till_end_sec")]
pub timestamp_asserter_min_time_till_end_sec: u32,
}

impl OptionalENConfig {
Expand Down Expand Up @@ -685,6 +714,11 @@ impl OptionalENConfig {
contracts_diamond_proxy_addr: None,
gateway_url: enconfig.gateway_url.clone(),
bridge_addresses_refresh_interval_sec: enconfig.bridge_addresses_refresh_interval_sec,
timestamp_asserter_min_time_till_end_sec: general_config
.timestamp_asserter_config
.as_ref()
.map(|x| x.min_time_till_end_sec)
.unwrap_or_else(Self::default_timestamp_asserter_min_time_till_end_sec),
})
}

Expand Down Expand Up @@ -819,6 +853,10 @@ impl OptionalENConfig {
3_600 * 24 * 7 // 7 days
}

const fn default_timestamp_asserter_min_time_till_end_sec() -> u32 {
60
}

fn from_env() -> anyhow::Result<Self> {
let mut result: OptionalENConfig = envy::prefixed("EN_")
.from_env()
Expand Down Expand Up @@ -1425,6 +1463,7 @@ impl From<&ExternalNodeConfig> for InternalApiConfig {
filters_disabled: config.optional.filters_disabled,
dummy_verifier: config.remote.dummy_verifier,
l1_batch_commit_data_generator_mode: config.remote.l1_batch_commit_data_generator_mode,
timestamp_asserter_address: config.remote.l2_timestamp_asserter_addr,
}
}
}
Expand All @@ -1447,6 +1486,17 @@ impl From<&ExternalNodeConfig> for TxSenderConfig {
chain_id: config.required.l2_chain_id,
// Does not matter for EN.
whitelisted_tokens_for_aa: Default::default(),
timestamp_asserter_params: config.remote.l2_timestamp_asserter_addr.map(|address| {
TimestampAsserterParams {
address,
min_time_till_end: Duration::from_secs(
config
.optional
.timestamp_asserter_min_time_till_end_sec
.into(),
),
}
}),
}
}
}
1 change: 1 addition & 0 deletions core/bin/external_node/src/config/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ fn parsing_optional_config_from_env() {
"zks_getProof=100,eth_call=2",
),
("EN_L1_BATCH_COMMIT_DATA_GENERATOR_MODE", "Validium"),
("EN_TIMESTAMP_ASSERTER_MIN_TIME_TILL_END_SEC", "2"),
];
let env_vars = env_vars
.into_iter()
Expand Down
3 changes: 2 additions & 1 deletion core/bin/zksync_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use zksync_config::{
api::{HealthCheckConfig, MerkleTreeApiConfig, Web3JsonRpcConfig},
chain::{
CircuitBreakerConfig, MempoolConfig, NetworkConfig, OperationsManagerConfig,
StateKeeperConfig,
StateKeeperConfig, TimestampAsserterConfig,
},
fri_prover_group::FriProverGroupConfig,
house_keeper::HouseKeeperConfig,
Expand Down Expand Up @@ -195,5 +195,6 @@ fn load_env_config() -> anyhow::Result<TempConfigStore> {
external_proof_integration_api_config: ExternalProofIntegrationApiConfig::from_env().ok(),
experimental_vm_config: ExperimentalVmConfig::from_env().ok(),
prover_job_monitor_config: None,
timestamp_asserter_config: TimestampAsserterConfig::from_env().ok(),
})
}
19 changes: 18 additions & 1 deletion core/bin/zksync_server/src/node_builder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! This module provides a "builder" for the main node,
//! as well as an interface to run the node with the specified components.

use std::time::Duration;

use anyhow::{bail, Context};
use zksync_config::{
configs::{
Expand All @@ -12,7 +14,7 @@ use zksync_config::{
use zksync_core_leftovers::Component;
use zksync_metadata_calculator::MetadataCalculatorConfig;
use zksync_node_api_server::{
tx_sender::TxSenderConfig,
tx_sender::{TimestampAsserterParams, TxSenderConfig},
web3::{state::InternalApiConfig, Namespace},
};
use zksync_node_framework::{
Expand Down Expand Up @@ -303,6 +305,20 @@ impl MainNodeBuilder {
fn add_tx_sender_layer(mut self) -> anyhow::Result<Self> {
let sk_config = try_load_config!(self.configs.state_keeper_config);
let rpc_config = try_load_config!(self.configs.api_config).web3_json_rpc;

let timestamp_asserter_params = match self.contracts_config.l2_timestamp_asserter_addr {
Some(address) => {
let timestamp_asserter_config =
try_load_config!(self.configs.timestamp_asserter_config);
Some(TimestampAsserterParams {
address,
min_time_till_end: Duration::from_secs(
timestamp_asserter_config.min_time_till_end_sec.into(),
),
})
}
None => None,
};
let postgres_storage_caches_config = PostgresStorageCachesConfig {
factory_deps_cache_size: rpc_config.factory_deps_cache_size() as u64,
initial_writes_cache_size: rpc_config.initial_writes_cache_size() as u64,
Expand All @@ -322,6 +338,7 @@ impl MainNodeBuilder {
.fee_account
.address(),
self.genesis_config.l2_chain_id,
timestamp_asserter_params,
),
postgres_storage_caches_config,
rpc_config.vm_concurrency_limit(),
Expand Down
8 changes: 7 additions & 1 deletion core/lib/config/src/configs/chain.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{str::FromStr, time::Duration};

use serde::Deserialize;
use serde::{Deserialize, Serialize};
use zksync_basic_types::{
commitment::L1BatchCommitmentMode, network::Network, Address, L2ChainId, H256,
};
Expand Down Expand Up @@ -244,3 +244,9 @@ impl MempoolConfig {
Duration::from_millis(self.delay_interval)
}
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
pub struct TimestampAsserterConfig {
/// Minimum time between current block.timestamp and the end of the asserted range
pub min_time_till_end_sec: u32,
}
2 changes: 2 additions & 0 deletions core/lib/config/src/configs/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub struct ContractsConfig {
pub l1_weth_bridge_proxy_addr: Option<Address>,
pub l2_weth_bridge_addr: Option<Address>,
pub l2_testnet_paymaster_addr: Option<Address>,
pub l2_timestamp_asserter_addr: Option<Address>,
pub l1_multicall3_addr: Address,
pub ecosystem_contracts: Option<EcosystemContracts>,
// Used by the RPC API and by the node builder in wiring the BaseTokenRatioProvider layer.
Expand All @@ -65,6 +66,7 @@ impl ContractsConfig {
l2_weth_bridge_addr: Some(Address::repeat_byte(0x0c)),
l2_testnet_paymaster_addr: Some(Address::repeat_byte(0x11)),
l1_multicall3_addr: Address::repeat_byte(0x12),
l2_timestamp_asserter_addr: Some(Address::repeat_byte(0x19)),
governance_addr: Address::repeat_byte(0x13),
base_token_addr: Some(Address::repeat_byte(0x14)),
ecosystem_contracts: Some(EcosystemContracts::for_tests()),
Expand Down
6 changes: 5 additions & 1 deletion core/lib/config/src/configs/general.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::{
configs::{
base_token_adjuster::BaseTokenAdjusterConfig,
chain::{CircuitBreakerConfig, MempoolConfig, OperationsManagerConfig, StateKeeperConfig},
chain::{
CircuitBreakerConfig, MempoolConfig, OperationsManagerConfig, StateKeeperConfig,
TimestampAsserterConfig,
},
consensus::ConsensusConfig,
da_client::DAClientConfig,
da_dispatcher::DADispatcherConfig,
Expand Down Expand Up @@ -56,4 +59,5 @@ pub struct GeneralConfig {
pub external_proof_integration_api_config: Option<ExternalProofIntegrationApiConfig>,
pub experimental_vm_config: Option<ExperimentalVmConfig>,
pub prover_job_monitor_config: Option<ProverJobMonitorConfig>,
pub timestamp_asserter_config: Option<TimestampAsserterConfig>,
}
11 changes: 11 additions & 0 deletions core/lib/config/src/testonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use zksync_crypto_primitives::K256PrivateKey;
use crate::{
configs::{
self,
chain::TimestampAsserterConfig,
da_client::{
avail::{AvailClientConfig, AvailDefaultConfig},
DAClientConfig::Avail,
Expand Down Expand Up @@ -265,6 +266,7 @@ impl Distribution<configs::ContractsConfig> for EncodeDist {
l1_weth_bridge_proxy_addr: self.sample_opt(|| rng.gen()),
l2_weth_bridge_addr: self.sample_opt(|| rng.gen()),
l2_testnet_paymaster_addr: self.sample_opt(|| rng.gen()),
l2_timestamp_asserter_addr: self.sample_opt(|| rng.gen()),
l1_multicall3_addr: rng.gen(),
ecosystem_contracts: self.sample(rng),
base_token_addr: self.sample_opt(|| rng.gen()),
Expand Down Expand Up @@ -1181,6 +1183,15 @@ impl Distribution<configs::GeneralConfig> for EncodeDist {
external_proof_integration_api_config: self.sample(rng),
experimental_vm_config: self.sample(rng),
prover_job_monitor_config: self.sample(rng),
timestamp_asserter_config: self.sample(rng),
}
}
}

impl Distribution<TimestampAsserterConfig> for EncodeDist {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TimestampAsserterConfig {
TimestampAsserterConfig {
min_time_till_end_sec: self.sample(rng),
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 069d38d

Please sign in to comment.