Skip to content

Commit f6d86bd

Browse files
authored
feat(eth-sender): add time_in_mempool_cap config (#3018)
Configuration parameter for time_in_mempool_cap, cap for time_in_mempool in eth-sender fee model + default values for parameter_a and parameter_b for GasAdjuster
1 parent 3140769 commit f6d86bd

File tree

10 files changed

+61
-19
lines changed

10 files changed

+61
-19
lines changed

core/lib/config/src/configs/eth_sender.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ impl EthConfig {
4242
pubdata_sending_mode: PubdataSendingMode::Calldata,
4343
tx_aggregation_paused: false,
4444
tx_aggregation_only_prove_and_execute: false,
45+
time_in_mempool_in_l1_blocks_cap: 1800,
4546
}),
4647
gas_adjuster: Some(GasAdjusterConfig {
4748
default_priority_fee_per_gas: 1000000000,
@@ -127,6 +128,10 @@ pub struct SenderConfig {
127128
/// special mode specifically for gateway migration to decrease number of non-executed batches
128129
#[serde(default = "SenderConfig::default_tx_aggregation_only_prove_and_execute")]
129130
pub tx_aggregation_only_prove_and_execute: bool,
131+
132+
/// Cap of time in mempool for price calculations
133+
#[serde(default = "SenderConfig::default_time_in_mempool_in_l1_blocks_cap")]
134+
pub time_in_mempool_in_l1_blocks_cap: u32,
130135
}
131136

132137
impl SenderConfig {
@@ -168,6 +173,13 @@ impl SenderConfig {
168173
const fn default_tx_aggregation_only_prove_and_execute() -> bool {
169174
false
170175
}
176+
177+
pub const fn default_time_in_mempool_in_l1_blocks_cap() -> u32 {
178+
let blocks_per_hour = 3600 / 12;
179+
// we cap it at 6h to not allow nearly infinite values when a tx is stuck for a long time
180+
// 1,001 ^ 1800 ~= 6, so by default we cap exponential price formula at roughly median * 6
181+
blocks_per_hour * 6
182+
}
171183
}
172184

173185
#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Default)]
@@ -177,8 +189,10 @@ pub struct GasAdjusterConfig {
177189
/// Number of blocks collected by GasAdjuster from which base_fee median is taken
178190
pub max_base_fee_samples: usize,
179191
/// Parameter of the transaction base_fee_per_gas pricing formula
192+
#[serde(default = "GasAdjusterConfig::default_pricing_formula_parameter_a")]
180193
pub pricing_formula_parameter_a: f64,
181194
/// Parameter of the transaction base_fee_per_gas pricing formula
195+
#[serde(default = "GasAdjusterConfig::default_pricing_formula_parameter_b")]
182196
pub pricing_formula_parameter_b: f64,
183197
/// Parameter by which the base fee will be multiplied for internal purposes
184198
pub internal_l1_pricing_multiplier: f64,
@@ -225,4 +239,12 @@ impl GasAdjusterConfig {
225239
pub const fn default_internal_pubdata_pricing_multiplier() -> f64 {
226240
1.0
227241
}
242+
243+
pub const fn default_pricing_formula_parameter_a() -> f64 {
244+
1.1
245+
}
246+
247+
pub const fn default_pricing_formula_parameter_b() -> f64 {
248+
1.001
249+
}
228250
}

core/lib/config/src/testonly.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ impl Distribution<configs::eth_sender::SenderConfig> for EncodeDist {
419419
pubdata_sending_mode: PubdataSendingMode::Calldata,
420420
tx_aggregation_paused: false,
421421
tx_aggregation_only_prove_and_execute: false,
422+
time_in_mempool_in_l1_blocks_cap: self.sample(rng),
422423
}
423424
}
424425
}

core/lib/env_config/src/eth_sender.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ mod tests {
7272
pubdata_sending_mode: PubdataSendingMode::Calldata,
7373
tx_aggregation_only_prove_and_execute: false,
7474
tx_aggregation_paused: false,
75+
time_in_mempool_in_l1_blocks_cap: 2000,
7576
}),
7677
gas_adjuster: Some(GasAdjusterConfig {
7778
default_priority_fee_per_gas: 20000000000,
@@ -131,6 +132,7 @@ mod tests {
131132
ETH_SENDER_SENDER_TIMESTAMP_CRITERIA_MAX_ALLOWED_LAG="30"
132133
ETH_SENDER_SENDER_MAX_AGGREGATED_TX_GAS="4000000"
133134
ETH_SENDER_SENDER_MAX_ETH_TX_DATA_SIZE="120000"
135+
ETH_SENDER_SENDER_TIME_IN_MEMPOOL_IN_L1_BLOCKS_CAP="2000"
134136
ETH_SENDER_SENDER_L1_BATCH_MIN_AGE_BEFORE_EXECUTE_SECONDS="1000"
135137
ETH_SENDER_SENDER_MAX_ACCEPTABLE_PRIORITY_FEE_IN_GWEI="100000000000"
136138
ETH_SENDER_SENDER_PUBDATA_SENDING_MODE="Calldata"

core/lib/protobuf_config/src/eth.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ impl ProtoRepr for proto::Sender {
115115
.parse(),
116116
tx_aggregation_only_prove_and_execute: self.tx_aggregation_paused.unwrap_or(false),
117117
tx_aggregation_paused: self.tx_aggregation_only_prove_and_execute.unwrap_or(false),
118+
time_in_mempool_in_l1_blocks_cap: self
119+
.time_in_mempool_in_l1_blocks_cap
120+
.unwrap_or(Self::Type::default_time_in_mempool_in_l1_blocks_cap()),
118121
})
119122
}
120123

@@ -147,6 +150,7 @@ impl ProtoRepr for proto::Sender {
147150
),
148151
tx_aggregation_only_prove_and_execute: Some(this.tx_aggregation_only_prove_and_execute),
149152
tx_aggregation_paused: Some(this.tx_aggregation_paused),
153+
time_in_mempool_in_l1_blocks_cap: Some(this.time_in_mempool_in_l1_blocks_cap),
150154
}
151155
}
152156
}
@@ -161,9 +165,9 @@ impl ProtoRepr for proto::GasAdjuster {
161165
.and_then(|x| Ok((*x).try_into()?))
162166
.context("max_base_fee_samples")?,
163167
pricing_formula_parameter_a: *required(&self.pricing_formula_parameter_a)
164-
.context("pricing_formula_parameter_a")?,
168+
.unwrap_or(&Self::Type::default_pricing_formula_parameter_a()),
165169
pricing_formula_parameter_b: *required(&self.pricing_formula_parameter_b)
166-
.context("pricing_formula_parameter_b")?,
170+
.unwrap_or(&Self::Type::default_pricing_formula_parameter_b()),
167171
internal_l1_pricing_multiplier: *required(&self.internal_l1_pricing_multiplier)
168172
.context("internal_l1_pricing_multiplier")?,
169173
internal_enforced_l1_gas_price: self.internal_enforced_l1_gas_price,

core/lib/protobuf_config/src/proto/config/eth_sender.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ message Sender {
4848
reserved 19; reserved "proof_loading_mode";
4949
optional bool tx_aggregation_paused = 20; // required
5050
optional bool tx_aggregation_only_prove_and_execute = 21; // required
51+
optional uint32 time_in_mempool_in_l1_blocks_cap = 22; // optional
5152
}
5253

5354
message GasAdjuster {

core/node/eth_sender/src/eth_fees_oracle.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub(crate) trait EthFeesOracle: 'static + Sync + Send + fmt::Debug {
2323
fn calculate_fees(
2424
&self,
2525
previous_sent_tx: &Option<TxHistory>,
26-
time_in_mempool: u32,
26+
time_in_mempool_in_l1_blocks: u32,
2727
operator_type: OperatorType,
2828
) -> Result<EthFees, EthSenderError>;
2929
}
@@ -32,6 +32,7 @@ pub(crate) trait EthFeesOracle: 'static + Sync + Send + fmt::Debug {
3232
pub(crate) struct GasAdjusterFeesOracle {
3333
pub gas_adjuster: Arc<dyn TxParamsProvider>,
3434
pub max_acceptable_priority_fee_in_gwei: u64,
35+
pub time_in_mempool_in_l1_blocks_cap: u32,
3536
}
3637

3738
impl GasAdjusterFeesOracle {
@@ -80,11 +81,16 @@ impl GasAdjusterFeesOracle {
8081
fn calculate_fees_no_blob_sidecar(
8182
&self,
8283
previous_sent_tx: &Option<TxHistory>,
83-
time_in_mempool: u32,
84+
time_in_mempool_in_l1_blocks: u32,
8485
) -> Result<EthFees, EthSenderError> {
85-
// cap it at 6h to not allow nearly infinite values when a tx is stuck for a long time
86-
let capped_time_in_mempool = min(time_in_mempool, 1800);
87-
let mut base_fee_per_gas = self.gas_adjuster.get_base_fee(capped_time_in_mempool);
86+
// we cap it to not allow nearly infinite values when a tx is stuck for a long time
87+
let capped_time_in_mempool_in_l1_blocks = min(
88+
time_in_mempool_in_l1_blocks,
89+
self.time_in_mempool_in_l1_blocks_cap,
90+
);
91+
let mut base_fee_per_gas = self
92+
.gas_adjuster
93+
.get_base_fee(capped_time_in_mempool_in_l1_blocks);
8894
self.assert_fee_is_not_zero(base_fee_per_gas, "base");
8995
if let Some(previous_sent_tx) = previous_sent_tx {
9096
self.verify_base_fee_not_too_low_on_resend(
@@ -162,14 +168,14 @@ impl EthFeesOracle for GasAdjusterFeesOracle {
162168
fn calculate_fees(
163169
&self,
164170
previous_sent_tx: &Option<TxHistory>,
165-
time_in_mempool: u32,
171+
time_in_mempool_in_l1_blocks: u32,
166172
operator_type: OperatorType,
167173
) -> Result<EthFees, EthSenderError> {
168174
let has_blob_sidecar = operator_type == OperatorType::Blob;
169175
if has_blob_sidecar {
170176
self.calculate_fees_with_blob_sidecar(previous_sent_tx)
171177
} else {
172-
self.calculate_fees_no_blob_sidecar(previous_sent_tx, time_in_mempool)
178+
self.calculate_fees_no_blob_sidecar(previous_sent_tx, time_in_mempool_in_l1_blocks)
173179
}
174180
}
175181
}

core/node/eth_sender/src/eth_tx_manager.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ impl EthTxManager {
4848
let fees_oracle = GasAdjusterFeesOracle {
4949
gas_adjuster,
5050
max_acceptable_priority_fee_in_gwei: config.max_acceptable_priority_fee_in_gwei,
51+
time_in_mempool_in_l1_blocks_cap: config.time_in_mempool_in_l1_blocks_cap,
5152
};
5253
let l1_interface = Box::new(RealL1Interface {
5354
ethereum_gateway,
@@ -111,7 +112,7 @@ impl EthTxManager {
111112
&mut self,
112113
storage: &mut Connection<'_, Core>,
113114
tx: &EthTx,
114-
time_in_mempool: u32,
115+
time_in_mempool_in_l1_blocks: u32,
115116
current_block: L1BlockNumber,
116117
) -> Result<H256, EthSenderError> {
117118
let previous_sent_tx = storage
@@ -127,7 +128,7 @@ impl EthTxManager {
127128
pubdata_price: _,
128129
} = self.fees_oracle.calculate_fees(
129130
&previous_sent_tx,
130-
time_in_mempool,
131+
time_in_mempool_in_l1_blocks,
131132
self.operator_type(tx),
132133
)?;
133134

@@ -601,13 +602,18 @@ impl EthTxManager {
601602
.await?
602603
{
603604
// New gas price depends on the time this tx spent in mempool.
604-
let time_in_mempool = l1_block_numbers.latest.0 - sent_at_block;
605+
let time_in_mempool_in_l1_blocks = l1_block_numbers.latest.0 - sent_at_block;
605606

606607
// We don't want to return early in case resend does not succeed -
607608
// the error is logged anyway, but early returns will prevent
608609
// sending new operations.
609610
let _ = self
610-
.send_eth_tx(storage, &tx, time_in_mempool, l1_block_numbers.latest)
611+
.send_eth_tx(
612+
storage,
613+
&tx,
614+
time_in_mempool_in_l1_blocks,
615+
l1_block_numbers.latest,
616+
)
611617
.await?;
612618
}
613619
Ok(())

core/node/fee_model/src/l1_gas_price/gas_adjuster/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,14 +317,14 @@ impl TxParamsProvider for GasAdjuster {
317317
// smooth out base_fee increases in general.
318318
// In other words, in order to pay less fees, we are ready to wait longer.
319319
// But the longer we wait, the more we are ready to pay.
320-
fn get_base_fee(&self, time_in_mempool: u32) -> u64 {
320+
fn get_base_fee(&self, time_in_mempool_in_l1_blocks: u32) -> u64 {
321321
let a = self.config.pricing_formula_parameter_a;
322322
let b = self.config.pricing_formula_parameter_b;
323323

324324
// Currently we use an exponential formula.
325325
// The alternative is a linear one:
326-
// `let scale_factor = a + b * time_in_mempool as f64;`
327-
let scale_factor = a * b.powf(time_in_mempool as f64);
326+
// `let scale_factor = a + b * time_in_mempool_in_l1_blocks as f64;`
327+
let scale_factor = a * b.powf(time_in_mempool_in_l1_blocks as f64);
328328
let median = self.base_fee_statistics.median();
329329
METRICS.median_base_fee_per_gas.set(median);
330330
let new_fee = median as f64 * scale_factor;

core/node/fee_model/src/l1_gas_price/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ mod main_node_fetcher;
1616
/// This trait, as a bound, should only be used in components that actually sign and send transactions.
1717
pub trait TxParamsProvider: fmt::Debug + 'static + Send + Sync {
1818
/// Returns the recommended `max_fee_per_gas` value (EIP1559).
19-
fn get_base_fee(&self, time_in_mempool: u32) -> u64;
19+
fn get_base_fee(&self, time_in_mempool_in_l1_blocks: u32) -> u64;
2020

2121
/// Returns the recommended `max_priority_fee_per_gas` value (EIP1559).
2222
fn get_priority_fee(&self) -> u64;

etc/env/base/eth_sender.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ default_priority_fee_per_gas = 1_000_000_000
5555
max_base_fee_samples = 10_000
5656
# These two are parameters of the base_fee_per_gas formula in GasAdjuster.
5757
# The possible formulas are:
58-
# 1. base_fee_median * (A + B * time_in_mempool)
59-
# 2. base_fee_median * A * B ^ time_in_mempool
58+
# 1. base_fee_median * (A + B * time_in_mempool_in_l1_blocks)
59+
# 2. base_fee_median * A * B ^ time_in_mempool_in_l1_blocks
6060
# Currently the second is used.
6161
# To confirm, see core/bin/zksync_core/src/eth_sender/gas_adjuster/mod.rs
6262
pricing_formula_parameter_a = 1.5

0 commit comments

Comments
 (0)