Skip to content

Commit

Permalink
Merge pull request #593 from chainbound/price
Browse files Browse the repository at this point in the history
feat(bolt-sidecar): implement pricing models for preconfs
  • Loading branch information
estensen authored Dec 18, 2024
2 parents 435fa2a + d693fff commit fb00734
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 6 deletions.
11 changes: 5 additions & 6 deletions bolt-sidecar/src/chain_io/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,12 @@ impl BoltManager {
))
.await;
continue;
} else {
warn!(
"Non-retryable transport error when connecting to EL node: {}",
transport_err
);
return Err(transport_err.into());
}
warn!(
"Non-retryable transport error when connecting to EL node: {}",
transport_err
);
return Err(transport_err.into());
}
Err(err) => {
// For other errors, parse and return immediately
Expand Down
4 changes: 4 additions & 0 deletions bolt-sidecar/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ use tokio::time::Sleep;
mod execution;
pub use execution::{ExecutionState, ValidationError};

/// Module to calculate pricing.
pub mod pricing;
pub use pricing::PreconfPricing;

/// Module to fetch state from the Execution layer.
pub mod fetcher;
pub use fetcher::StateClient;
Expand Down
271 changes: 271 additions & 0 deletions bolt-sidecar/src/state/pricing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
/// Handles pricing calculations for preconfirmations
#[derive(Debug)]
pub struct PreconfPricing {
block_gas_limit: u64,
base_multiplier: f64,
gas_scalar: f64,
}

/// Errors that can occur during pricing calculations
#[derive(Debug, thiserror::Error)]
pub enum PricingError {
/// Preconfirmed gas exceeds the block limit
#[error("Preconfirmed gas {0} exceeds block limit {1}")]
ExceedsBlockLimit(u64, u64),
/// Insufficient remaining gas for the incoming transaction
#[error("Insufficient remaining gas: requested {requested}, available {available}")]
/// Insufficient remaining gas for the incoming transaction
InsufficientGas {
/// Gas requested by the incoming transaction
requested: u64,
/// Gas available in the block
available: u64,
},
/// Incoming gas is zero
#[error("Invalid gas limit: Incoming gas ({incoming_gas}) is zero")]
InvalidGasLimit {
/// Gas required by the incoming transaction
incoming_gas: u64,
},
}

impl Default for PreconfPricing {
fn default() -> Self {
Self::new(30_000_000)
}
}

impl PreconfPricing {
/// Initializes a new PreconfPricing with default parameters.
pub fn new(block_gas_limit: u64) -> Self {
Self { block_gas_limit, base_multiplier: 0.019, gas_scalar: 1.02e-6 }
}

/// Calculate the minimum priority fee for a preconfirmation based on
/// https://research.lido.fi/t/a-pricing-model-for-inclusion-preconfirmations/9136
///
/// # Arguments
/// * `incoming_gas` - Gas required by the incoming transaction
/// * `preconfirmed_gas` - Total gas already preconfirmed
///
/// # Returns
/// * `Ok(f64)` - The minimum priority fee in Wei
/// * `Err(PricingError)` - If the calculation cannot be performed
pub fn calculate_min_priority_fee(
&self,
incoming_gas: u64,
preconfirmed_gas: u64,
) -> Result<u64, PricingError> {
// Check if preconfirmed gas exceeds block limit
if preconfirmed_gas >= self.block_gas_limit {
return Err(PricingError::ExceedsBlockLimit(preconfirmed_gas, self.block_gas_limit));
}

// Validate incoming gas
if incoming_gas == 0 {
return Err(PricingError::InvalidGasLimit { incoming_gas });
}

// Check if there is enough gas remaining in the block
let remaining_gas = self.block_gas_limit - preconfirmed_gas;
if incoming_gas > remaining_gas {
return Err(PricingError::InsufficientGas {
requested: incoming_gas,
available: remaining_gas,
});
}

// T(IG,UG) = 0.019 * ln(1.02⋅10^-6(30M-UG)+1 / 1.02⋅10^-6(30M-UG-IG)+1) / IG
// where
// IG = Gas used by the incoming transaction
// UG = Gas already preconfirmed
// T = Inclusion tip per gas
// 30M = Current gas limit (36M soon?)
let after_gas = remaining_gas - incoming_gas;

// Calculate numerator and denominator for the logarithm
let numerator = self.gas_scalar * (remaining_gas as f64) + 1.0;
let denominator = self.gas_scalar * (after_gas as f64) + 1.0;

// Calculate gas used
let expected_val = self.base_multiplier * (numerator / denominator).ln();

// Calculate the inclusion tip
let inclusion_tip_ether = expected_val / (incoming_gas as f64);
let inclusion_tip_wei = (inclusion_tip_ether * 1e18) as u64;

Ok(inclusion_tip_wei)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_min_priority_fee_zero_preconfirmed() {
let pricing = PreconfPricing::default();

// Test minimum fee (21k gas ETH transfer, 0 preconfirmed)
let incoming_gas = 21_000;
let preconfirmed_gas = 0;
let min_fee_wei =
pricing.calculate_min_priority_fee(incoming_gas, preconfirmed_gas).unwrap();

// Pricing model article expects fee of 0.61 Gwei
assert!(
(min_fee_wei as f64 - 613_499_092.0).abs() < 1_000.0,
"Expected ~613,499,092 Wei, got {} Wei",
min_fee_wei
);
}

#[test]
fn test_min_priority_fee_zero_big_preconfirmed() {
let pricing = PreconfPricing::default();

// Test minimum fee (210k gas ETH transfer, 0 preconfirmed)
let incoming_gas = 210_000;
let preconfirmed_gas = 0;
let min_fee_wei =
pricing.calculate_min_priority_fee(incoming_gas, preconfirmed_gas).unwrap();

// This preconf uses 10x more gas than the 21k preconf,
// but the fee is only slightly higher.
assert!(
(min_fee_wei as f64 - 615_379_171.0).abs() < 1_000.0,
"Expected ~615,379,171 Wei, got {} Wei",
min_fee_wei
);
}

#[test]
fn test_min_priority_fee_medium_load() {
let pricing = PreconfPricing::default();

// Test medium load (21k gas, 15M preconfirmed)
let incoming_gas = 21_000;
let preconfirmed_gas = 15_000_000;
let min_fee_wei =
pricing.calculate_min_priority_fee(incoming_gas, preconfirmed_gas).unwrap();

// Pricing model article expects fee of ~1.17 Gwei
assert!(
(min_fee_wei as f64 - 1_189_738_950.0).abs() < 1_000.0,
"Expected ~1,189,738,950 Wei, got {} Wei",
min_fee_wei
);
}

#[test]
fn test_min_priority_fee_max_load() {
let pricing = PreconfPricing::default();

// Test last preconfirmed transaction (21k gas, almost 30M preconfirmed)
let incoming_gas = 21_000;
let preconfirmed_gas = 30_000_000 - 21_000;
let min_fee_wei =
pricing.calculate_min_priority_fee(incoming_gas, preconfirmed_gas).unwrap();

// Expected fee: ~19 Gwei
// This will likely never happen, since you want to reserve some gas
// on top of the block for MEV, but enforcing this is not the responsibility
// of the pricing model.
assert!(
(min_fee_wei as f64 - 19_175_357_339.0).abs() < 1_000.0,
"Expected ~19,175,357,339 Wei, got {} Wei",
min_fee_wei
);
}

#[test]
fn test_min_priority_fee_zero_preconfirmed_36m() {
let pricing = PreconfPricing::new(36_000_000);

// Test minimum fee (21k gas ETH transfer, 0 preconfirmed)
let incoming_gas = 21_000;
let preconfirmed_gas = 0;
let min_fee_wei =
pricing.calculate_min_priority_fee(incoming_gas, preconfirmed_gas).unwrap();

assert!(
(min_fee_wei as f64 - 513_931_726.0).abs() < 1_000.0,
"Expected ~513,931,726 Wei, got {} Wei",
min_fee_wei
);
}

#[test]
fn test_min_priority_fee_medium_load_36m() {
let pricing = PreconfPricing::new(36_000_000);

// Test medium load (21k gas, 18M preconfirmed)
let incoming_gas = 21_000;
let preconfirmed_gas = 18_000_000;
let min_fee_wei =
pricing.calculate_min_priority_fee(incoming_gas, preconfirmed_gas).unwrap();

assert!(
(min_fee_wei as f64 - 1_001_587_240.0).abs() < 1_000.0,
"Expected ~1,001,587,240 Wei, got {} Wei",
min_fee_wei
);
}

#[test]
fn test_min_priority_fee_max_load_36m() {
let pricing = PreconfPricing::new(36_000_000);

// Test last preconfirmed transaction (21k gas, almost 30M preconfirmed)
let incoming_gas = 21_000;
let preconfirmed_gas = 36_000_000 - 21_000;
let min_fee_wei =
pricing.calculate_min_priority_fee(incoming_gas, preconfirmed_gas).unwrap();

// Expected fee: ~19 Gwei
// This will likely never happen, since you want to reserve some gas
// on top of the block for MEV, but enforcing this is not the responsibility
// of the pricing model.
assert!(
(min_fee_wei as f64 - 19_175_357_339.0).abs() < 1_000.0,
"Expected ~19,175,357,339 Wei, got {} Wei",
min_fee_wei
);
}

#[test]
fn test_error_exceeds_block_limit() {
let pricing = PreconfPricing::default();

let incoming_gas = 21_000;
let preconfirmed_gas = 30_000_001;

let result = pricing.calculate_min_priority_fee(incoming_gas, preconfirmed_gas);
assert!(matches!(result, Err(PricingError::ExceedsBlockLimit(30_000_001, 30_000_000))));
}

#[test]
fn test_error_insufficient_gas() {
let pricing = PreconfPricing::default();

let incoming_gas = 15_000_001;
let preconfirmed_gas = 15_000_000;

let result = pricing.calculate_min_priority_fee(incoming_gas, preconfirmed_gas);
assert!(matches!(
result,
Err(PricingError::InsufficientGas { requested: 15_000_001, available: 15_000_000 })
));
}

#[test]
fn test_error_zero_incoming_gas() {
let pricing = PreconfPricing::default();

let incoming_gas = 0;
let preconfirmed_gas = 0;

let result = pricing.calculate_min_priority_fee(incoming_gas, preconfirmed_gas);
assert!(matches!(result, Err(PricingError::InvalidGasLimit { incoming_gas: 0 })));
}
}

0 comments on commit fb00734

Please sign in to comment.