Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 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
7 changes: 7 additions & 0 deletions config-files/config-batcher-docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,10 @@ batcher:
non_paying:
address: '0xa0Ee7A142d267C1f36714E4a8F75612F20a79720' # Anvil address 9
replacement_private_key: ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # Anvil address 1
# When validating if the msg covers the minimum max fee
# A batch of how many proofs should it cover
proofs_to_cover_in_min_max_fee: 32,
# When replacing the message, how much higher should the max fee in comparison to the original one
# The calculation is replacement_max_fee >= original_max_fee + original_max_fee * min_bump_percentage / 100
min_bump_percentage: 10,

7 changes: 7 additions & 0 deletions config-files/config-batcher-ethereum-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,10 @@ batcher:
non_paying:
address: '0xa0Ee7A142d267C1f36714E4a8F75612F20a79720' # Anvil address 9
replacement_private_key: ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # Anvil address 1
# When validating if the msg covers the minimum max fee
# A batch of how many proofs should it cover
proofs_to_cover_in_min_max_fee: 32,
# When replacing the message, how much higher should the max fee in comparison to the original one
# The calculation is replacement_max_fee >= original_max_fee + original_max_fee * min_bump_percentage / 100
min_bump_percentage: 10,

6 changes: 6 additions & 0 deletions config-files/config-batcher.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,9 @@ batcher:
non_paying:
address: '0xa0Ee7A142d267C1f36714E4a8F75612F20a79720' # Anvil address 9
replacement_private_key: ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # Anvil address 1
# When validating if the msg covers the minimum max fee
# A batch of how many proofs should it cover
proofs_to_cover_in_min_max_fee: 32,
# When replacing the message, how much higher should the max fee in comparison to the original one
# The calculation is replacement_max_fee >= original_max_fee + original_max_fee * min_bump_percentage / 100
min_bump_percentage: 10,
2 changes: 2 additions & 0 deletions crates/batcher/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ pub struct BatcherConfigFromYaml {
pub metrics_port: u16,
pub telemetry_ip_port_address: String,
pub non_paying: Option<NonPayingConfigFromYaml>,
pub proofs_to_cover_in_min_max_fee: usize,
pub min_bump_percentage: u64,
}

#[derive(Debug, Deserialize)]
Expand Down
40 changes: 37 additions & 3 deletions crates/batcher/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ pub struct Batcher {
disabled_verifiers: Mutex<U256>,
aggregator_fee_percentage_multiplier: u128,
aggregator_gas_cost: u128,
latest_block_gas_price: RwLock<U256>,
proofs_to_cover_in_min_max_fee: usize,
min_bump_percentage: U256,
pub metrics: metrics::BatcherMetrics,
pub telemetry: TelemetrySender,
}
Expand Down Expand Up @@ -266,6 +269,8 @@ impl Batcher {
max_proof_size: config.batcher.max_proof_size,
max_batch_byte_size: config.batcher.max_batch_byte_size,
max_batch_proof_qty: config.batcher.max_batch_proof_qty,
proofs_to_cover_in_min_max_fee: config.batcher.proofs_to_cover_in_min_max_fee,
min_bump_percentage: U256::from(config.batcher.min_bump_percentage),
last_uploaded_batch_block: Mutex::new(last_uploaded_batch_block),
pre_verification_is_enabled: config.batcher.pre_verification_is_enabled,
non_paying_config,
Expand All @@ -276,6 +281,7 @@ impl Batcher {
posting_batch: Mutex::new(false),
batch_state: Mutex::new(batch_state),
disabled_verifiers: Mutex::new(disabled_verifiers),
latest_block_gas_price: RwLock::new(U256::zero()),
metrics,
telemetry,
}
Expand Down Expand Up @@ -662,6 +668,19 @@ impl Batcher {
nonced_verification_data = aux_verification_data
}

// Before moving on to process the message, verify that the max fee covers the
// minimum max fee allowed. This prevents users from spamming with very low max fees
// the min max fee is enforced by checking if it can cover a batch of [`proofs_to_cover_in_min_max_fee`]
let msg_max_fee = nonced_verification_data.max_fee;
if !self.msg_covers_minimum_max_fee(msg_max_fee).await {
send_message(
ws_conn_sink.clone(),
SubmitProofResponseMessage::UnderpricedProof,
)
.await;
return Ok(());
};

// When pre-verification is enabled, batcher will verify proofs for faster feedback with clients
if self.pre_verification_is_enabled {
let verification_data = &nonced_verification_data.verification_data;
Expand Down Expand Up @@ -766,7 +785,6 @@ impl Batcher {

let mut batch_state_lock = self.batch_state.lock().await;

let msg_max_fee = nonced_verification_data.max_fee;
let Some(user_last_max_fee_limit) =
batch_state_lock.get_user_last_max_fee_limit(&addr).await
else {
Expand Down Expand Up @@ -989,13 +1007,17 @@ impl Batcher {
return;
};

// Validate that the max fee is at least higher or equal to the original fee + a [`min_bump_percentage`]
let original_max_fee = entry.nonced_verification_data.max_fee;
Comment thread
JuArce marked this conversation as resolved.
if original_max_fee > replacement_max_fee {
let min_bump =
original_max_fee + (original_max_fee * self.min_bump_percentage) / U256::from(100);

if replacement_max_fee < min_bump {
std::mem::drop(batch_state_lock);
warn!("Invalid replacement message for address {addr}, had max fee: {original_max_fee:?}, received fee: {replacement_max_fee:?}");
Comment thread
JuArce marked this conversation as resolved.
Outdated
send_message(
ws_conn_sink.clone(),
SubmitProofResponseMessage::InvalidReplacementMessage,
SubmitProofResponseMessage::UnderpricedProof,
)
.await;
self.metrics
Expand Down Expand Up @@ -1502,6 +1524,9 @@ impl Batcher {
let gas_price = gas_price.map_err(|_| BatcherError::GasPriceError)?;

{
let mut latest_block_gas_price = self.latest_block_gas_price.write().await;
Comment thread
JuArce marked this conversation as resolved.
Outdated
*latest_block_gas_price = gas_price;

let new_disable_verifiers = disable_verifiers
.map_err(|e| BatcherError::DisabledVerifiersError(e.to_string()))?;
let mut disabled_verifiers_lock = self.disabled_verifiers.lock().await;
Expand Down Expand Up @@ -2020,6 +2045,15 @@ impl Batcher {
true
}

async fn msg_covers_minimum_max_fee(&self, msg_max_fee: U256) -> bool {
let gas_price = *self.latest_block_gas_price.read().await;
let min_max_fee_per_proof = aligned_sdk::verification_layer::compute_fee_per_proof_formula(
self.proofs_to_cover_in_min_max_fee,
gas_price,
);
msg_max_fee >= min_max_fee_per_proof
}

/// Checks if the user's balance is unlocked
/// Returns false if balance is unlocked, logs the error,
/// and sends it to the metrics server
Expand Down
4 changes: 2 additions & 2 deletions crates/sdk/src/communication/messaging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,8 @@ async fn handle_batcher_response(msg: Message) -> Result<BatchInclusionData, Sub
Err(SubmitError::GenericError(e))
}
Ok(SubmitProofResponseMessage::UnderpricedProof) => {
error!("Batcher responded with error: queue limit has been exceeded. Funds have not been spent.");
Err(SubmitError::BatchQueueLimitExceededError)
error!("Batcher responded with error: proof underpriced. Funds have not been spent.");
Err(SubmitError::InvalidMaxFee)
}
Err(e) => {
error!(
Expand Down
39 changes: 29 additions & 10 deletions crates/sdk/src/verification_layer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,39 @@ pub async fn calculate_fee_per_proof_for_batch_of_size(
})?;
let gas_price = fetch_gas_price(&eth_rpc_provider).await?;

// Cost for estimate `num_proofs_per_batch` proofs
let fee_per_proof = compute_fee_per_proof_formula(num_proofs_in_batch, gas_price);
Ok(fee_per_proof)
}

/// Estimates the fee per proof based on the given batch size and gas price.
///
/// This function models the cost of submitting a batch of proofs to the network
/// by computing an estimated gas cost per proof. The total gas cost is composed of:
/// - a constant base gas cost for any batch submission (`DEFAULT_CONSTANT_GAS_COST`)
/// - an additional gas cost that scales linearly with the number of proofs in the batch
/// (`ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF * num_proofs_in_batch`)
///
/// The final fee per proof is calculated by:
/// (estimated_gas_per_proof * gas_price * GAS_PRICE_PERCENTAGE_MULTIPLIER) / PERCENTAGE_DIVIDER
///
///
/// # Arguments
/// * `num_proofs_in_batch` - Number of proofs in the batch (must be > 0).
/// * `gas_price` - Current gas price (in wei).
///
/// # Returns
/// * Estimated fee per individual proof (in wei).
///
/// # Panics
/// This function panics if `num_proofs_in_batch` is 0 due to division by zero.
pub fn compute_fee_per_proof_formula(num_proofs_in_batch: usize, gas_price: U256) -> U256 {
// Gas cost for `num_proofs_per_batch` proofs
let estimated_gas_per_proof = (DEFAULT_CONSTANT_GAS_COST
+ ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF * num_proofs_in_batch as u128)
/ num_proofs_in_batch as u128;

// Price of 1 proof in a batch of size `num_proofs_in_batch` i.e. (1 / `num_proofs_in_batch`).
// The computed price is adjusted with respect to the percentage multiplier from:
// https://github.com/yetanotherco/aligned_layer/blob/staging/crates/batcher/src/lib.rs#L1401
let fee_per_proof = (U256::from(estimated_gas_per_proof)
* gas_price
* U256::from(GAS_PRICE_PERCENTAGE_MULTIPLIER))
/ U256::from(PERCENTAGE_DIVIDER);

Ok(fee_per_proof)
(U256::from(estimated_gas_per_proof) * gas_price * U256::from(GAS_PRICE_PERCENTAGE_MULTIPLIER))
/ U256::from(PERCENTAGE_DIVIDER)
}

async fn fetch_gas_price(
Expand Down
Loading