diff --git a/core/bin/zksync_server/src/main.rs b/core/bin/zksync_server/src/main.rs index 7e0ff0e49..9dbc9ac8f 100644 --- a/core/bin/zksync_server/src/main.rs +++ b/core/bin/zksync_server/src/main.rs @@ -11,6 +11,7 @@ use zksync_config::{ }, fri_prover_group::FriProverGroupConfig, house_keeper::HouseKeeperConfig, + tx_sink::TxSinkConfig, BasicWitnessInputProducerConfig, ContractsConfig, DatabaseSecrets, ExperimentalVmConfig, ExternalPriceApiClientConfig, FriProofCompressorConfig, FriProverConfig, FriProverGatewayConfig, FriWitnessGeneratorConfig, FriWitnessVectorGeneratorConfig, @@ -44,7 +45,7 @@ struct Cli { /// Comma-separated list of components to launch. #[arg( long, - default_value = "api,tree,eth,state_keeper,housekeeper,tee_verifier_input_producer,commitment_generator,da_dispatcher,vm_runner_protective_reads" + default_value = "api,tree,eth,state_keeper,housekeeper,tee_verifier_input_producer,commitment_generator,da_dispatcher,vm_runner_protective_reads,deny_list" )] components: ComponentsToRun, /// Path to the yaml config. If set, it will be used instead of env vars. @@ -211,5 +212,6 @@ fn load_env_config() -> anyhow::Result { external_proof_integration_api_config: ExternalProofIntegrationApiConfig::from_env().ok(), experimental_vm_config: ExperimentalVmConfig::from_env().ok(), prover_job_monitor_config: None, + tx_sink_config: TxSinkConfig::from_env().ok(), }) } diff --git a/core/bin/zksync_server/src/node_builder.rs b/core/bin/zksync_server/src/node_builder.rs index 70afca4d8..29680ed9a 100644 --- a/core/bin/zksync_server/src/node_builder.rs +++ b/core/bin/zksync_server/src/node_builder.rs @@ -68,13 +68,14 @@ use zksync_node_framework::{ server::{Web3ServerLayer, Web3ServerOptionalConfig}, tree_api_client::TreeApiClientLayer, tx_sender::{PostgresStorageCachesConfig, TxSenderLayer}, - tx_sink::MasterPoolSinkLayer, + tx_sink::{DenyListPoolSinkLayer, MasterPoolSinkLayer}, }, }, service::{ZkStackService, ZkStackServiceBuilder}, }; use zksync_types::{settlement::SettlementMode, SHARED_BRIDGE_ETHER_TOKEN_ADDRESS}; use zksync_vlog::prometheus::PrometheusExporterConfig; + /// Macro that looks into a path to fetch an optional config, /// and clones it into a variable. macro_rules! try_load_config { @@ -319,7 +320,7 @@ impl MainNodeBuilder { Ok(self) } - fn add_tx_sender_layer(mut self) -> anyhow::Result { + fn add_tx_sender_layer(mut self, deny_list_enabled: bool) -> anyhow::Result { 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 postgres_storage_caches_config = PostgresStorageCachesConfig { @@ -328,8 +329,15 @@ impl MainNodeBuilder { latest_values_cache_size: rpc_config.latest_values_cache_size() as u64, }; - // On main node we always use master pool sink. - self.node.add_layer(MasterPoolSinkLayer); + let tx_sink_config = try_load_config!(self.configs.tx_sink_config); + if deny_list_enabled && tx_sink_config.deny_list().is_some() { + self.node.add_layer(DenyListPoolSinkLayer::new( + tx_sink_config.deny_list().unwrap(), + )); + } else { + self.node.add_layer(MasterPoolSinkLayer); + } + self.node.add_layer(TxSenderLayer::new( TxSenderConfig::new( &sk_config, @@ -708,6 +716,8 @@ impl MainNodeBuilder { _ => 0, }); + let mut deny_list_enabled = false; + // Add "component-specific" layers. // Note that the layers are added only once, so it's fine to add the same layer multiple times. for component in &components { @@ -724,7 +734,7 @@ impl MainNodeBuilder { Component::HttpApi => { self = self .add_l1_gas_layer()? - .add_tx_sender_layer()? + .add_tx_sender_layer(deny_list_enabled)? .add_tree_api_client_layer()? .add_api_caches_layer()? .add_http_web3_api_layer()?; @@ -732,7 +742,7 @@ impl MainNodeBuilder { Component::WsApi => { self = self .add_l1_gas_layer()? - .add_tx_sender_layer()? + .add_tx_sender_layer(deny_list_enabled)? .add_tree_api_client_layer()? .add_api_caches_layer()? .add_ws_web3_api_layer()?; @@ -800,6 +810,9 @@ impl MainNodeBuilder { Component::ExternalProofIntegrationApi => { self = self.add_external_proof_integration_api_layer()?; } + Component::TxSinkDenyList => { + deny_list_enabled = true; + } } } Ok(self.node.build()) diff --git a/core/lib/config/src/configs/general.rs b/core/lib/config/src/configs/general.rs index 38ffd3d45..28fb0a49d 100644 --- a/core/lib/config/src/configs/general.rs +++ b/core/lib/config/src/configs/general.rs @@ -9,6 +9,7 @@ use crate::{ prover_job_monitor::ProverJobMonitorConfig, pruning::PruningConfig, snapshot_recovery::SnapshotRecoveryConfig, + tx_sink::TxSinkConfig, vm_runner::{BasicWitnessInputProducerConfig, ProtectiveReadsWriterConfig}, CommitmentGeneratorConfig, ExperimentalVmConfig, ExternalPriceApiClientConfig, FriProofCompressorConfig, FriProverConfig, FriProverGatewayConfig, @@ -54,4 +55,5 @@ pub struct GeneralConfig { pub external_proof_integration_api_config: Option, pub experimental_vm_config: Option, pub prover_job_monitor_config: Option, + pub tx_sink_config: Option, } diff --git a/core/lib/config/src/configs/mod.rs b/core/lib/config/src/configs/mod.rs index b213060f7..ecf501ea0 100644 --- a/core/lib/config/src/configs/mod.rs +++ b/core/lib/config/src/configs/mod.rs @@ -27,6 +27,7 @@ pub use self::{ secrets::{DatabaseSecrets, L1Secrets, Secrets}, snapshot_recovery::SnapshotRecoveryConfig, snapshots_creator::SnapshotsCreatorConfig, + tx_sink::TxSinkConfig, utils::PrometheusConfig, vm_runner::{BasicWitnessInputProducerConfig, ProtectiveReadsWriterConfig}, }; @@ -63,6 +64,7 @@ pub mod pruning; pub mod secrets; pub mod snapshot_recovery; pub mod snapshots_creator; +pub mod tx_sink; pub mod utils; pub mod vm_runner; pub mod wallets; diff --git a/core/lib/config/src/configs/tx_sink.rs b/core/lib/config/src/configs/tx_sink.rs new file mode 100644 index 000000000..2d3b981aa --- /dev/null +++ b/core/lib/config/src/configs/tx_sink.rs @@ -0,0 +1,19 @@ +use std::{collections::HashSet, str::FromStr}; + +use serde::Deserialize; +use zksync_basic_types::Address; + +#[derive(Debug, Deserialize, Clone, PartialEq)] +pub struct TxSinkConfig { + pub deny_list: Option, +} + +impl TxSinkConfig { + pub fn deny_list(&self) -> Option> { + self.deny_list.as_ref().map(|list| { + list.split(',') + .map(|element| Address::from_str(element).unwrap()) + .collect() + }) + } +} diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index 3318e9bb6..3d007419a 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -1148,6 +1148,15 @@ impl Distribution for EncodeDist { external_proof_integration_api_config: self.sample(rng), experimental_vm_config: self.sample(rng), prover_job_monitor_config: self.sample(rng), + tx_sink_config: self.sample(rng), + } + } +} + +impl Distribution for EncodeDist { + fn sample(&self, rng: &mut R) -> configs::TxSinkConfig { + configs::TxSinkConfig { + deny_list: self.sample(rng), } } } diff --git a/core/lib/env_config/src/lib.rs b/core/lib/env_config/src/lib.rs index 8cfa7b58a..e6f177b95 100644 --- a/core/lib/env_config/src/lib.rs +++ b/core/lib/env_config/src/lib.rs @@ -19,6 +19,7 @@ pub mod object_store; mod observability; mod proof_data_handler; mod snapshots_creator; +mod tx_sink; mod utils; mod base_token_adjuster; diff --git a/core/lib/env_config/src/tx_sink.rs b/core/lib/env_config/src/tx_sink.rs new file mode 100644 index 000000000..92890bac8 --- /dev/null +++ b/core/lib/env_config/src/tx_sink.rs @@ -0,0 +1,35 @@ +use zksync_config::configs::TxSinkConfig; + +use crate::{envy_load, FromEnv}; + +impl FromEnv for TxSinkConfig { + fn from_env() -> anyhow::Result { + envy_load("tx_sink", "TX_SINK_") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::EnvMutex; + + static MUTEX: EnvMutex = EnvMutex::new(); + + fn expected_config() -> TxSinkConfig { + TxSinkConfig { + deny_list: Some("0x1234567890abcdef".to_string()), + } + } + + #[test] + fn from_env() { + let mut lock = MUTEX.lock(); + let config = r#" + TX_SINK_DENY_LIST="0x1234567890abcdef" + "#; + lock.set_env(config); + + let actual = TxSinkConfig::from_env().unwrap(); + assert_eq!(actual, expected_config()); + } +} diff --git a/core/lib/protobuf_config/src/general.rs b/core/lib/protobuf_config/src/general.rs index 87bca88db..74b54eb71 100644 --- a/core/lib/protobuf_config/src/general.rs +++ b/core/lib/protobuf_config/src/general.rs @@ -45,6 +45,7 @@ impl ProtoRepr for proto::GeneralConfig { ), experimental_vm_config: read_optional_repr(&self.experimental_vm), prover_job_monitor_config: read_optional_repr(&self.prover_job_monitor), + tx_sink_config: read_optional_repr(&self.tx_sink), }) } @@ -104,6 +105,7 @@ impl ProtoRepr for proto::GeneralConfig { .prover_job_monitor_config .as_ref() .map(ProtoRepr::build), + tx_sink: this.tx_sink_config.as_ref().map(ProtoRepr::build), } } } diff --git a/core/lib/protobuf_config/src/lib.rs b/core/lib/protobuf_config/src/lib.rs index f4d0188ea..d9548dcdd 100644 --- a/core/lib/protobuf_config/src/lib.rs +++ b/core/lib/protobuf_config/src/lib.rs @@ -35,6 +35,7 @@ mod prover_job_monitor; mod snapshot_recovery; #[cfg(test)] mod tests; +mod tx_sink; mod utils; mod vm_runner; mod wallets; diff --git a/core/lib/protobuf_config/src/proto/config/general.proto b/core/lib/protobuf_config/src/proto/config/general.proto index 359546894..df8192305 100644 --- a/core/lib/protobuf_config/src/proto/config/general.proto +++ b/core/lib/protobuf_config/src/proto/config/general.proto @@ -2,62 +2,64 @@ syntax = "proto3"; package zksync.config.general; -import "zksync/config/prover.proto"; import "zksync/config/api.proto"; +import "zksync/config/base_token_adjuster.proto"; import "zksync/config/chain.proto"; +import "zksync/config/circuit_breaker.proto"; +import "zksync/config/commitment_generator.proto"; import "zksync/config/contract_verifier.proto"; +import "zksync/config/da_dispatcher.proto"; import "zksync/config/database.proto"; -import "zksync/config/circuit_breaker.proto"; import "zksync/config/eth_sender.proto"; import "zksync/config/experimental.proto"; +import "zksync/config/external_price_api_client.proto"; +import "zksync/config/external_proof_integration_api.proto"; import "zksync/config/house_keeper.proto"; +import "zksync/config/object_store.proto"; import "zksync/config/observability.proto"; +import "zksync/config/prover.proto"; +import "zksync/config/prover_job_monitor.proto"; +import "zksync/config/pruning.proto"; +import "zksync/config/snapshot_recovery.proto"; import "zksync/config/snapshots_creator.proto"; +import "zksync/config/tx_sink.proto"; import "zksync/config/utils.proto"; -import "zksync/config/da_dispatcher.proto"; import "zksync/config/vm_runner.proto"; -import "zksync/config/commitment_generator.proto"; -import "zksync/config/snapshot_recovery.proto"; -import "zksync/config/pruning.proto"; -import "zksync/config/object_store.proto"; -import "zksync/config/base_token_adjuster.proto"; -import "zksync/config/external_price_api_client.proto"; -import "zksync/config/external_proof_integration_api.proto"; import "zksync/core/consensus.proto"; -import "zksync/config/prover_job_monitor.proto"; message GeneralConfig { - optional database.Postgres postgres = 1; - optional api.Api api = 2; - optional contract_verifier.ContractVerifier contract_verifier = 3; - optional circuit_breaker.CircuitBreaker circuit_breaker = 5; - optional chain.Mempool mempool = 6; - optional chain.OperationsManager operations_manager = 8; - optional chain.StateKeeper state_keeper = 9; - optional house_keeper.HouseKeeper house_keeper = 10; - optional prover.Prover prover = 12; - optional utils.Prometheus prometheus = 15; - optional database.DB db = 20; - optional eth.ETH eth = 22; - optional prover.WitnessGenerator witness_generator = 24; - optional prover.WitnessVectorGenerator witness_vector_generator = 25; - optional prover.ProofCompressor proof_compressor = 27; - optional prover.ProofDataHandler data_handler = 28; - optional prover.ProverGroup prover_group = 29; - optional prover.ProverGateway prover_gateway = 30; - optional snapshot_creator.SnapshotsCreator snapshot_creator = 31; - optional observability.Observability observability = 32; - optional vm_runner.ProtectiveReadsWriter protective_reads_writer = 33; - optional object_store.ObjectStore core_object_store = 34; - optional snapshot_recovery.SnapshotRecovery snapshot_recovery = 35; - optional pruning.Pruning pruning = 36; - optional commitment_generator.CommitmentGenerator commitment_generator = 37; - optional da_dispatcher.DataAvailabilityDispatcher da_dispatcher = 38; - optional base_token_adjuster.BaseTokenAdjuster base_token_adjuster = 39; - optional vm_runner.BasicWitnessInputProducer basic_witness_input_producer = 40; - optional external_price_api_client.ExternalPriceApiClient external_price_api_client = 41; - optional core.consensus.Config consensus = 42; - optional external_proof_integration_api.ExternalProofIntegrationApi external_proof_integration_api = 43; - optional experimental.Vm experimental_vm = 44; - optional prover_job_monitor.ProverJobMonitor prover_job_monitor = 45; + optional database.Postgres postgres = 1; + optional api.Api api = 2; + optional contract_verifier.ContractVerifier contract_verifier = 3; + optional circuit_breaker.CircuitBreaker circuit_breaker = 5; + optional chain.Mempool mempool = 6; + optional chain.OperationsManager operations_manager = 8; + optional chain.StateKeeper state_keeper = 9; + optional house_keeper.HouseKeeper house_keeper = 10; + optional prover.Prover prover = 12; + optional utils.Prometheus prometheus = 15; + optional database.DB db = 20; + optional eth.ETH eth = 22; + optional prover.WitnessGenerator witness_generator = 24; + optional prover.WitnessVectorGenerator witness_vector_generator = 25; + optional prover.ProofCompressor proof_compressor = 27; + optional prover.ProofDataHandler data_handler = 28; + optional prover.ProverGroup prover_group = 29; + optional prover.ProverGateway prover_gateway = 30; + optional snapshot_creator.SnapshotsCreator snapshot_creator = 31; + optional observability.Observability observability = 32; + optional vm_runner.ProtectiveReadsWriter protective_reads_writer = 33; + optional object_store.ObjectStore core_object_store = 34; + optional snapshot_recovery.SnapshotRecovery snapshot_recovery = 35; + optional pruning.Pruning pruning = 36; + optional commitment_generator.CommitmentGenerator commitment_generator = 37; + optional da_dispatcher.DataAvailabilityDispatcher da_dispatcher = 38; + optional base_token_adjuster.BaseTokenAdjuster base_token_adjuster = 39; + optional vm_runner.BasicWitnessInputProducer basic_witness_input_producer = 40; + optional external_price_api_client.ExternalPriceApiClient external_price_api_client = 41; + optional core.consensus.Config consensus = 42; + optional external_proof_integration_api.ExternalProofIntegrationApi external_proof_integration_api = 43; + optional experimental.Vm experimental_vm = 44; + optional prover_job_monitor.ProverJobMonitor prover_job_monitor = 45; + optional tx_sink.TxSink tx_sink = 100; } diff --git a/core/lib/protobuf_config/src/proto/config/tx_sink.proto b/core/lib/protobuf_config/src/proto/config/tx_sink.proto new file mode 100644 index 000000000..24a9d1b0e --- /dev/null +++ b/core/lib/protobuf_config/src/proto/config/tx_sink.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package zksync.config.tx_sink; + +message TxSink { + optional string deny_list = 1; // optional +} diff --git a/core/lib/protobuf_config/src/tests.rs b/core/lib/protobuf_config/src/tests.rs index 695f404f6..8483f93f3 100644 --- a/core/lib/protobuf_config/src/tests.rs +++ b/core/lib/protobuf_config/src/tests.rs @@ -54,6 +54,7 @@ fn test_encoding() { rng, ); test_encode_all_formats::>(rng); + test_encode_all_formats::>(rng); } #[test] diff --git a/core/lib/protobuf_config/src/tx_sink.rs b/core/lib/protobuf_config/src/tx_sink.rs new file mode 100644 index 000000000..7d6114093 --- /dev/null +++ b/core/lib/protobuf_config/src/tx_sink.rs @@ -0,0 +1,19 @@ +use zksync_config::configs; +use zksync_protobuf::repr::ProtoRepr; + +use crate::proto::tx_sink as proto; + +impl ProtoRepr for proto::TxSink { + type Type = configs::tx_sink::TxSinkConfig; + fn read(&self) -> anyhow::Result { + Ok(Self::Type { + deny_list: self.deny_list.clone(), + }) + } + + fn build(this: &Self::Type) -> Self { + Self { + deny_list: this.deny_list.clone(), + } + } +} diff --git a/core/lib/zksync_core_leftovers/src/lib.rs b/core/lib/zksync_core_leftovers/src/lib.rs index 9d399bdd0..ff0cf68f5 100644 --- a/core/lib/zksync_core_leftovers/src/lib.rs +++ b/core/lib/zksync_core_leftovers/src/lib.rs @@ -66,6 +66,8 @@ pub enum Component { ExternalProofIntegrationApi, /// VM runner-based component that allows to test experimental VM features. Doesn't save any data to Postgres. VmPlayground, + /// Component for filtering L2 transactions by denylist + TxSinkDenyList, } #[derive(Debug)] @@ -114,6 +116,7 @@ impl FromStr for Components { "external_proof_integration_api" => { Ok(Components(vec![Component::ExternalProofIntegrationApi])) } + "deny_list" => Ok(Components(vec![Component::TxSinkDenyList])), other => Err(format!("{} is not a valid component name", other)), } } diff --git a/core/lib/zksync_core_leftovers/src/temp_config_store/mod.rs b/core/lib/zksync_core_leftovers/src/temp_config_store/mod.rs index 8224b03da..6184a115d 100644 --- a/core/lib/zksync_core_leftovers/src/temp_config_store/mod.rs +++ b/core/lib/zksync_core_leftovers/src/temp_config_store/mod.rs @@ -17,6 +17,7 @@ use zksync_config::{ FriProverGatewayConfig, FriWitnessGeneratorConfig, FriWitnessVectorGeneratorConfig, GeneralConfig, ObservabilityConfig, PrometheusConfig, ProofDataHandlerConfig, ProtectiveReadsWriterConfig, ProverJobMonitorConfig, PruningConfig, SnapshotRecoveryConfig, + TxSinkConfig, }, ApiConfig, BaseTokenAdjusterConfig, ContractVerifierConfig, DADispatcherConfig, DBConfig, EthConfig, EthWatchConfig, ExternalProofIntegrationApiConfig, GasAdjusterConfig, @@ -80,6 +81,7 @@ pub struct TempConfigStore { pub external_proof_integration_api_config: Option, pub experimental_vm_config: Option, pub prover_job_monitor_config: Option, + pub tx_sink_config: Option, } impl TempConfigStore { @@ -120,6 +122,7 @@ impl TempConfigStore { .clone(), experimental_vm_config: self.experimental_vm_config.clone(), prover_job_monitor_config: self.prover_job_monitor_config.clone(), + tx_sink_config: self.tx_sink_config.clone(), } } @@ -200,6 +203,7 @@ fn load_env_config() -> anyhow::Result { external_proof_integration_api_config: ExternalProofIntegrationApiConfig::from_env().ok(), experimental_vm_config: ExperimentalVmConfig::from_env().ok(), prover_job_monitor_config: ProverJobMonitorConfig::from_env().ok(), + tx_sink_config: TxSinkConfig::from_env().ok(), }) } diff --git a/core/node/api_server/src/tx_sender/deny_list_pool_sink.rs b/core/node/api_server/src/tx_sender/deny_list_pool_sink.rs new file mode 100644 index 000000000..81c1735e0 --- /dev/null +++ b/core/node/api_server/src/tx_sender/deny_list_pool_sink.rs @@ -0,0 +1,40 @@ +use std::collections::HashSet; + +use zksync_dal::transactions_dal::L2TxSubmissionResult; +use zksync_multivm::interface::TransactionExecutionMetrics; +use zksync_types::{l2::L2Tx, Address}; + +use super::{master_pool_sink::MasterPoolSink, tx_sink::TxSink, SubmitTxError}; +//use crate::api_server::tx_sender::master_pool_sink::MasterPoolSink; + +/// Wrapper for the master DB pool that allows to submit transactions to the mempool. +#[derive(Debug)] +pub struct DenyListPoolSink { + deny_list: HashSet
, + master_pool_sync: MasterPoolSink, +} + +impl DenyListPoolSink { + pub fn new(master_pool_sync: MasterPoolSink, deny_list: HashSet
) -> Self { + Self { + master_pool_sync, + deny_list, + } + } +} + +#[async_trait::async_trait] +impl TxSink for DenyListPoolSink { + async fn submit_tx( + &self, + tx: &L2Tx, + execution_metrics: TransactionExecutionMetrics, + ) -> Result { + let address_and_nonce = (tx.initiator_account(), tx.nonce()); + if self.deny_list.contains(&address_and_nonce.0) { + return Err(SubmitTxError::SenderInDenyList(tx.initiator_account())); + } + + self.master_pool_sync.submit_tx(tx, execution_metrics).await + } +} diff --git a/core/node/api_server/src/tx_sender/mod.rs b/core/node/api_server/src/tx_sender/mod.rs index 5f913e305..5f73ec3ed 100644 --- a/core/node/api_server/src/tx_sender/mod.rs +++ b/core/node/api_server/src/tx_sender/mod.rs @@ -1,10 +1,11 @@ //! Helper module to submit transactions into the ZKsync Network. -use std::{sync::Arc, time::Instant}; +use std::{collections::HashSet, sync::Arc, time::Instant}; use anyhow::Context as _; +use deny_list_pool_sink::DenyListPoolSink; use tokio::sync::RwLock; -use zksync_config::configs::{api::Web3JsonRpcConfig, chain::StateKeeperConfig}; +use zksync_config::configs::{api::Web3JsonRpcConfig, chain::StateKeeperConfig, TxSinkConfig}; use zksync_contracts::BaseSystemContracts; use zksync_dal::{ transactions_dal::L2TxSubmissionResult, Connection, ConnectionPool, Core, CoreDal, @@ -47,6 +48,7 @@ use crate::{ tx_sender::result::ApiCallResult, }; +pub mod deny_list_pool_sink; pub mod master_pool_sink; pub mod proxy; mod result; @@ -54,25 +56,45 @@ mod result; pub(crate) mod tests; pub mod tx_sink; +pub struct TxSenderBuilderConfigs { + pub tx_sender_config: TxSenderConfig, + pub web3_json_config: Web3JsonRpcConfig, + pub state_keeper_config: StateKeeperConfig, + pub tx_sink_config: Option, +} + pub async fn build_tx_sender( - tx_sender_config: &TxSenderConfig, - web3_json_config: &Web3JsonRpcConfig, - state_keeper_config: &StateKeeperConfig, + builder_config: TxSenderBuilderConfigs, replica_pool: ConnectionPool, master_pool: ConnectionPool, batch_fee_model_input_provider: Arc, storage_caches: PostgresStorageCaches, ) -> anyhow::Result<(TxSender, VmConcurrencyBarrier)> { - let sequencer_sealer = SequencerSealer::new(state_keeper_config.clone()); - let master_pool_sink = MasterPoolSink::new(master_pool); - let tx_sender_builder = TxSenderBuilder::new( - tx_sender_config.clone(), - replica_pool.clone(), - Arc::new(master_pool_sink), - ) - .with_sealer(Arc::new(sequencer_sealer)); - - let max_concurrency = web3_json_config.vm_concurrency_limit(); + let sequencer_sealer = SequencerSealer::new(builder_config.state_keeper_config); + + let tx_sender_builder = if let Some(config) = builder_config.tx_sink_config { + let deny_list_pool_sink = if let Some(list) = config.deny_list() { + DenyListPoolSink::new(MasterPoolSink::new(master_pool), list) + } else { + DenyListPoolSink::new(MasterPoolSink::new(master_pool), HashSet::
::new()) + }; + + TxSenderBuilder::new( + builder_config.tx_sender_config.clone(), + replica_pool.clone(), + Arc::new(deny_list_pool_sink), + ) + .with_sealer(Arc::new(sequencer_sealer)) + } else { + TxSenderBuilder::new( + builder_config.tx_sender_config.clone(), + replica_pool.clone(), + Arc::new(MasterPoolSink::new(master_pool)), + ) + .with_sealer(Arc::new(sequencer_sealer)) + }; + + let max_concurrency = builder_config.web3_json_config.vm_concurrency_limit(); let (vm_concurrency_limiter, vm_barrier) = VmConcurrencyLimiter::new(max_concurrency); let batch_fee_input_provider = diff --git a/core/node/api_server/src/tx_sender/result.rs b/core/node/api_server/src/tx_sender/result.rs index f4bda54ef..9a6cb3fb3 100644 --- a/core/node/api_server/src/tx_sender/result.rs +++ b/core/node/api_server/src/tx_sender/result.rs @@ -1,6 +1,6 @@ use thiserror::Error; use zksync_multivm::interface::{ExecutionResult, VmExecutionResultAndLogs}; -use zksync_types::{l2::error::TxCheckError, U256}; +use zksync_types::{l2::error::TxCheckError, Address, U256}; use zksync_web3_decl::error::EnrichedClientError; use crate::execution_sandbox::{SandboxExecutionError, ValidationError}; @@ -75,6 +75,8 @@ pub enum SubmitTxError { /// Catch-all internal error (e.g., database error) that should not be exposed to the caller. #[error("internal error")] Internal(#[from] anyhow::Error), + #[error("sender address {0} is in deny list")] + SenderInDenyList(Address), } impl SubmitTxError { @@ -108,6 +110,7 @@ impl SubmitTxError { Self::ProxyError(_) => "proxy-error", Self::FailedToPublishCompressedBytecodes => "failed-to-publish-compressed-bytecodes", Self::Internal(_) => "internal", + Self::SenderInDenyList(_) => "sender-in-deny-list", } } diff --git a/core/node/api_server/src/web3/testonly.rs b/core/node/api_server/src/web3/testonly.rs index 9f6b30b60..f732674cd 100644 --- a/core/node/api_server/src/web3/testonly.rs +++ b/core/node/api_server/src/web3/testonly.rs @@ -18,7 +18,7 @@ use zksync_types::{ use super::{metrics::ApiTransportLabel, *}; use crate::{ execution_sandbox::{testonly::MockOneshotExecutor, TransactionExecutor}, - tx_sender::TxSenderConfig, + tx_sender::{TxSenderBuilderConfigs, TxSenderConfig}, }; const TEST_TIMEOUT: Duration = Duration::from_secs(90); @@ -65,15 +65,20 @@ pub(crate) async fn create_test_tx_sender( l2_chain_id, ); + let config = TxSenderBuilderConfigs { + tx_sender_config: tx_sender_config.clone(), + web3_json_config: web3_config.clone(), + state_keeper_config: state_keeper_config.clone(), + tx_sink_config: None, + }; + let storage_caches = PostgresStorageCaches::new(1, 1); let batch_fee_model_input_provider = Arc::new(MockApiBatchFeeParamsProvider { inner: MockBatchFeeParamsProvider::default(), pool: pool.clone(), }); let (mut tx_sender, vm_barrier) = crate::tx_sender::build_tx_sender( - &tx_sender_config, - &web3_config, - &state_keeper_config, + config, pool.clone(), pool, batch_fee_model_input_provider, diff --git a/core/node/node_framework/src/implementations/layers/web3_api/tx_sink/deny_list_pool_sink.rs b/core/node/node_framework/src/implementations/layers/web3_api/tx_sink/deny_list_pool_sink.rs new file mode 100644 index 000000000..050b31142 --- /dev/null +++ b/core/node/node_framework/src/implementations/layers/web3_api/tx_sink/deny_list_pool_sink.rs @@ -0,0 +1,55 @@ +use std::collections::HashSet; + +use zksync_node_api_server::tx_sender::{ + deny_list_pool_sink::DenyListPoolSink, master_pool_sink::MasterPoolSink, +}; +use zksync_types::Address; + +use crate::{ + implementations::resources::{ + pools::{MasterPool, PoolResource}, + web3_api::TxSinkResource, + }, + wiring_layer::{WiringError, WiringLayer}, + FromContext, IntoContext, +}; + +/// Wiring layer for [`DenyListPoolSink`], [`TxSink`](zksync_node_api_server::tx_sender::tx_sink::TxSink) implementation. +pub struct DenyListPoolSinkLayer { + deny_list: HashSet
, +} + +impl DenyListPoolSinkLayer { + pub fn new(deny_list: HashSet
) -> Self { + Self { deny_list } + } +} + +#[derive(Debug, FromContext)] +#[context(crate = crate)] +pub struct Input { + pub pool: PoolResource, +} + +#[derive(Debug, IntoContext)] +#[context(crate = crate)] +pub struct Output { + pub tx_sink: TxSinkResource, +} + +#[async_trait::async_trait] +impl WiringLayer for DenyListPoolSinkLayer { + type Input = Input; + type Output = Output; + + fn layer_name(&self) -> &'static str { + "deny_list_pool_sink_layer" + } + + async fn wire(self, input: Self::Input) -> Result { + let pool = input.pool.get().await?; + Ok(Output { + tx_sink: DenyListPoolSink::new(MasterPoolSink::new(pool), self.deny_list).into(), + }) + } +} diff --git a/core/node/node_framework/src/implementations/layers/web3_api/tx_sink/mod.rs b/core/node/node_framework/src/implementations/layers/web3_api/tx_sink/mod.rs index 61b9fb1d9..daadacb8f 100644 --- a/core/node/node_framework/src/implementations/layers/web3_api/tx_sink/mod.rs +++ b/core/node/node_framework/src/implementations/layers/web3_api/tx_sink/mod.rs @@ -1,4 +1,8 @@ -pub use self::{master_pool_sink::MasterPoolSinkLayer, proxy_sink::ProxySinkLayer}; +pub use self::{ + deny_list_pool_sink::DenyListPoolSinkLayer, master_pool_sink::MasterPoolSinkLayer, + proxy_sink::ProxySinkLayer, +}; +pub mod deny_list_pool_sink; pub mod master_pool_sink; pub mod proxy_sink;