Skip to content

Commit d7950a2

Browse files
committed
refactor: send generic transaction
1 parent 1b3fb86 commit d7950a2

File tree

2 files changed

+157
-122
lines changed

2 files changed

+157
-122
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use std::fmt;
2+
3+
use ethrex_common::{
4+
constants::MIN_BASE_FEE_PER_BLOB_GAS,
5+
types::{fake_exponential_checked, BLOB_BASE_FEE_UPDATE_FRACTION},
6+
U256,
7+
};
8+
use ethrex_rpc::{
9+
types::block_identifier::{BlockIdentifier, BlockTag},
10+
EthClient,
11+
};
12+
13+
pub const MAXIMUM_ALLOWED_MAX_FEE_PER_GAS: u64 = 10000000000;
14+
pub const MAXIMUM_ALLOWED_MAX_FEE_PER_BLOB_GAS: u64 = 10000000000;
15+
pub const ARBITRARY_BASE_BLOB_GAS_PRICE: u64 = 1000000000;
16+
17+
#[derive(Clone, Debug)]
18+
pub enum BlobEstimationError {
19+
OverflowError,
20+
FakeExponentialError(String),
21+
}
22+
23+
impl fmt::Display for BlobEstimationError {
24+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25+
match self {
26+
BlobEstimationError::OverflowError => write!(f, "Overflow error"),
27+
BlobEstimationError::FakeExponentialError(e) => {
28+
write!(f, "Fake exponential error: {}", e)
29+
}
30+
}
31+
}
32+
}
33+
34+
/// Estimates the gas price for blob transactions based on the current state of the blockchain.
35+
///
36+
/// # Parameters:
37+
/// - `eth_client`: The Ethereum client used to fetch the latest block.
38+
/// - `arbitrary_base_blob_gas_price`: The base gas price that serves as the minimum price for blob transactions.
39+
/// - `headroom`: Percentage applied to the estimated gas price to provide a buffer against fluctuations.
40+
///
41+
/// # Formula:
42+
/// The gas price is estimated using an exponential function based on the blob gas used in the latest block and the
43+
/// excess blob gas from the block header, following the formula from EIP-4844:
44+
/// ```
45+
/// blob_gas = arbitrary_base_blob_gas_price + (excess_blob_gas + blob_gas_used) * headroom
46+
/// ```
47+
///
48+
/// see: https://github.com/lambdaclass/ethrex/blob/a7655e53f9f75fc590cdc6de0f6ad79a0de551b4/crates/l2/sequencer/l1_committer.rs#L819
49+
pub async fn estimate_blob_gas(
50+
eth_client: &EthClient,
51+
headroom: u64,
52+
) -> Result<U256, BlobEstimationError> {
53+
let latest_block = eth_client
54+
.get_block_by_number(BlockIdentifier::Tag(BlockTag::Latest), false)
55+
.await
56+
.unwrap();
57+
58+
let blob_gas_used = latest_block.header.blob_gas_used.unwrap_or(0);
59+
let excess_blob_gas = latest_block.header.excess_blob_gas.unwrap_or(0);
60+
61+
// Check if adding the blob gas used and excess blob gas would overflow
62+
let total_blob_gas = excess_blob_gas
63+
.checked_add(blob_gas_used)
64+
.ok_or(BlobEstimationError::OverflowError)?;
65+
66+
// If the blob's market is in high demand, the equation may give a really big number.
67+
// This function doesn't panic, it performs checked/saturating operations.
68+
let blob_gas = fake_exponential_checked(
69+
MIN_BASE_FEE_PER_BLOB_GAS,
70+
total_blob_gas,
71+
BLOB_BASE_FEE_UPDATE_FRACTION,
72+
)
73+
.map_err(|e| BlobEstimationError::FakeExponentialError(e.to_string()))?;
74+
75+
let gas_with_headroom = (blob_gas * (100 + headroom)) / 100;
76+
77+
// Check if we have an overflow when we take the headroom into account.
78+
let blob_gas = ARBITRARY_BASE_BLOB_GAS_PRICE
79+
.checked_add(gas_with_headroom)
80+
.ok_or(BlobEstimationError::OverflowError)?;
81+
82+
Ok(U256::from(blob_gas))
83+
}

aggregation_mode/src/backend/mod.rs

Lines changed: 74 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
pub mod config;
2+
mod eth;
23
pub mod fetcher;
34
mod merkle_tree;
45
mod retry;
56
mod s3;
67
mod types;
78

8-
use crate::aggregators::{AlignedProof, ProofAggregationError, ZKVMEngine};
9+
use crate::{
10+
aggregators::{AlignedProof, ProofAggregationError, ZKVMEngine},
11+
backend::eth::{
12+
estimate_blob_gas, MAXIMUM_ALLOWED_MAX_FEE_PER_BLOB_GAS, MAXIMUM_ALLOWED_MAX_FEE_PER_GAS,
13+
},
14+
};
915

1016
use alloy::{
1117
eips::eip4844::BYTES_PER_BLOB,
@@ -17,20 +23,18 @@ use alloy::{
1723
};
1824
use config::Config;
1925
use ethrex_common::{
20-
constants::MIN_BASE_FEE_PER_BLOB_GAS,
21-
types::{fake_exponential_checked, BlobsBundle, Fork, BLOB_BASE_FEE_UPDATE_FRACTION},
22-
H256, U256,
26+
types::{BlobsBundle, Fork},
27+
H256,
2328
};
2429
use ethrex_l2_rpc::signer::LocalSigner as EthrexLocalSigner;
2530
use ethrex_rpc::{
2631
clients::{
2732
eth::{BACKOFF_FACTOR, MAX_NUMBER_OF_RETRIES, MAX_RETRY_DELAY, MIN_RETRY_DELAY},
2833
Overrides,
2934
},
30-
types::block_identifier::{BlockIdentifier, BlockTag},
3135
EthClient,
3236
};
33-
use ethrex_sdk::{build_generic_tx, calldata::encode_calldata, send_generic_transaction, transfer};
37+
use ethrex_sdk::{build_generic_tx, calldata::encode_calldata, send_generic_transaction};
3438
use fetcher::{ProofsFetcher, ProofsFetcherError};
3539
use merkle_tree::compute_proofs_merkle_root;
3640
use risc0_ethereum_contracts::encode_seal;
@@ -64,41 +68,6 @@ pub struct ProofAggregator {
6468
ethrex_signer: ethrex_l2_rpc::signer::Signer,
6569
}
6670

67-
async fn estimate_blob_gas(
68-
eth_client: &EthClient,
69-
arbitrary_base_blob_gas_price: u64,
70-
headroom: u64,
71-
) -> u64 {
72-
let latest_block = eth_client
73-
.get_block_by_number(BlockIdentifier::Tag(BlockTag::Latest), false)
74-
.await
75-
.unwrap();
76-
77-
let blob_gas_used = latest_block.header.blob_gas_used.unwrap_or(0);
78-
let excess_blob_gas = latest_block.header.excess_blob_gas.unwrap_or(0);
79-
80-
// Check if adding the blob gas used and excess blob gas would overflow
81-
let total_blob_gas = excess_blob_gas.checked_add(blob_gas_used).unwrap();
82-
83-
// If the blob's market is in high demand, the equation may give a really big number.
84-
// This function doesn't panic, it performs checked/saturating operations.
85-
let blob_gas = fake_exponential_checked(
86-
MIN_BASE_FEE_PER_BLOB_GAS,
87-
total_blob_gas,
88-
BLOB_BASE_FEE_UPDATE_FRACTION,
89-
)
90-
.unwrap();
91-
92-
let gas_with_headroom = (blob_gas * (100 + headroom)) / 100;
93-
94-
// Check if we have an overflow when we take the headroom into account.
95-
let blob_gas = arbitrary_base_blob_gas_price
96-
.checked_add(gas_with_headroom)
97-
.unwrap();
98-
99-
blob_gas
100-
}
101-
10271
impl ProofAggregator {
10372
pub fn new(config: Config) -> Self {
10473
let rpc_url = config.eth_rpc_url.parse().expect("RPC URL should be valid");
@@ -124,8 +93,8 @@ impl ProofAggregator {
12493
BACKOFF_FACTOR,
12594
MIN_RETRY_DELAY,
12695
MAX_RETRY_DELAY,
127-
Some(10000000000),
128-
Some(10000000000),
96+
Some(MAXIMUM_ALLOWED_MAX_FEE_PER_GAS),
97+
Some(MAXIMUM_ALLOWED_MAX_FEE_PER_BLOB_GAS),
12998
)
13099
.expect("rpc url to be valid");
131100

@@ -221,61 +190,29 @@ impl ProofAggregator {
221190
blob_versioned_hash: [u8; 32],
222191
aggregated_proof: AlignedProof,
223192
) -> Result<H256, AggregatedProofSubmissionError> {
224-
match aggregated_proof {
225-
AlignedProof::SP1(proof) => {
226-
let calldata = encode_calldata(
227-
"verifySP1(bytes32,bytes,bytes)",
228-
&[
229-
ethrex_l2_common::calldata::Value::FixedBytes(
230-
blob_versioned_hash.to_vec().into(),
231-
),
232-
ethrex_l2_common::calldata::Value::Bytes(
233-
proof.proof_with_pub_values.public_values.to_vec().into(),
234-
),
235-
ethrex_l2_common::calldata::Value::Bytes(
236-
proof.proof_with_pub_values.bytes().into(),
237-
),
238-
],
239-
)
240-
.map_err(|e| AggregatedProofSubmissionError::BuildingCalldata(e.to_string()))?;
193+
let calldata = match aggregated_proof {
194+
AlignedProof::SP1(proof) => encode_calldata(
195+
"verifySP1(bytes32,bytes,bytes)",
196+
&[
197+
ethrex_l2_common::calldata::Value::FixedBytes(
198+
blob_versioned_hash.to_vec().into(),
199+
),
200+
ethrex_l2_common::calldata::Value::Bytes(
201+
proof.proof_with_pub_values.public_values.to_vec().into(),
202+
),
203+
ethrex_l2_common::calldata::Value::Bytes(
204+
proof.proof_with_pub_values.bytes().into(),
205+
),
206+
],
207+
)
208+
.map_err(|e| AggregatedProofSubmissionError::BuildingCalldata(e.to_string()))?,
241209

242-
let le_bytes = estimate_blob_gas(&self.ethrex_eth_client, 1000000000, 20)
243-
.await
244-
.to_le_bytes();
245-
let gas_price_per_blob = U256::from_little_endian(&le_bytes);
246-
247-
let tx = build_generic_tx(
248-
&self.ethrex_eth_client,
249-
ethrex_common::types::TxType::EIP4844,
250-
self.proof_aggregation_service.address().0 .0.into(),
251-
self.ethrex_signer.address(),
252-
calldata.into(),
253-
Overrides {
254-
blobs_bundle: Some(blob_bundle),
255-
gas_price_per_blob: Some(gas_price_per_blob),
256-
..Default::default()
257-
},
258-
)
259-
.await
260-
.map_err(|e| AggregatedProofSubmissionError::BuildingTx(e.to_string()))?;
261-
262-
let tx_hash =
263-
send_generic_transaction(&self.ethrex_eth_client, tx, &self.ethrex_signer)
264-
.await
265-
.map_err(|e| {
266-
AggregatedProofSubmissionError::SendVerifyAggregatedProofTransaction(
267-
e.to_string(),
268-
)
269-
})?;
270-
271-
Ok(tx_hash)
272-
}
273210
AlignedProof::Risc0(proof) => {
274211
let encoded_seal = encode_seal(&proof.receipt).map_err(|e| {
275212
AggregatedProofSubmissionError::Risc0EncodingSeal(e.to_string())
276213
})?;
277214

278-
let calldata = encode_calldata(
215+
encode_calldata(
279216
"verifyRisc0(bytes32,bytes,bytes)",
280217
&[
281218
ethrex_l2_common::calldata::Value::FixedBytes(
@@ -287,40 +224,55 @@ impl ProofAggregator {
287224
),
288225
],
289226
)
290-
.map_err(|e| AggregatedProofSubmissionError::BuildingCalldata(e.to_string()))?;
291-
292-
let le_bytes = estimate_blob_gas(&self.ethrex_eth_client, 1000000000, 20)
293-
.await
294-
.to_le_bytes();
295-
let gas_price_per_blob = U256::from_little_endian(&le_bytes);
227+
.map_err(|e| AggregatedProofSubmissionError::BuildingCalldata(e.to_string()))?
228+
}
229+
};
296230

297-
let tx = build_generic_tx(
298-
&self.ethrex_eth_client,
299-
ethrex_common::types::TxType::EIP4844,
300-
self.proof_aggregation_service.address().0 .0.into(),
301-
self.ethrex_signer.address(),
302-
calldata.into(),
303-
Overrides {
304-
blobs_bundle: Some(blob_bundle),
305-
gas_price_per_blob: Some(gas_price_per_blob),
306-
..Default::default()
307-
},
231+
// ethrex auto calulates max_fee_per_gas and max_priority_fee_per_gas for us
232+
// but does not for max_fee_per_blob_gas but, so we need to estimate it ourselves
233+
let gas_price_per_blob = estimate_blob_gas(&self.ethrex_eth_client, 20)
234+
.await
235+
.map_err(|e| {
236+
AggregatedProofSubmissionError::SendVerifyAggregatedProofTransaction(e.to_string())
237+
})?;
238+
let gas_price = self
239+
.ethrex_eth_client
240+
.get_gas_price_with_extra(20)
241+
.await
242+
.map_err(|e| {
243+
AggregatedProofSubmissionError::SendVerifyAggregatedProofTransaction(e.to_string())
244+
})?
245+
.try_into()
246+
.map_err(|_| {
247+
AggregatedProofSubmissionError::SendVerifyAggregatedProofTransaction(
248+
"Failed to convert gas price to u64".into(),
308249
)
309-
.await
310-
.map_err(|e| AggregatedProofSubmissionError::BuildingTx(e.to_string()))?;
250+
})?;
251+
252+
let tx = build_generic_tx(
253+
&self.ethrex_eth_client,
254+
ethrex_common::types::TxType::EIP4844,
255+
self.proof_aggregation_service.address().0 .0.into(),
256+
self.ethrex_signer.address(),
257+
calldata.into(),
258+
Overrides {
259+
blobs_bundle: Some(blob_bundle),
260+
gas_price_per_blob: Some(gas_price_per_blob),
261+
max_fee_per_gas: Some(gas_price),
262+
max_priority_fee_per_gas: Some(gas_price),
263+
..Default::default()
264+
},
265+
)
266+
.await
267+
.map_err(|e| AggregatedProofSubmissionError::BuildingTx(e.to_string()))?;
311268

312-
let tx_hash =
313-
send_generic_transaction(&self.ethrex_eth_client, tx, &self.ethrex_signer)
314-
.await
315-
.map_err(|e| {
316-
AggregatedProofSubmissionError::SendVerifyAggregatedProofTransaction(
317-
e.to_string(),
318-
)
319-
})?;
269+
let tx_hash = send_generic_transaction(&self.ethrex_eth_client, tx, &self.ethrex_signer)
270+
.await
271+
.map_err(|e| {
272+
AggregatedProofSubmissionError::SendVerifyAggregatedProofTransaction(e.to_string())
273+
})?;
320274

321-
Ok(tx_hash)
322-
}
323-
}
275+
Ok(tx_hash)
324276
}
325277

326278
/// ### Blob capacity

0 commit comments

Comments
 (0)