Skip to content

Commit 32a0e66

Browse files
PatStilesMauroToscanoentropidelic
authored
feat(sdk): Add Max Fee Estimate. Update zkQuiz to better represent how the SDK should be used (#1131)
Co-authored-by: MauroFab <[email protected]> Co-authored-by: Mariano Nicolini <[email protected]>
1 parent 84202a5 commit 32a0e66

File tree

13 files changed

+584
-270
lines changed

13 files changed

+584
-270
lines changed

batcher/Cargo.lock

Lines changed: 171 additions & 150 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

batcher/aligned-batcher/src/lib.rs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ use std::iter::repeat;
1515
use std::net::SocketAddr;
1616
use std::sync::Arc;
1717

18-
use aligned_sdk::core::types::{
19-
BatchInclusionData, ClientMessage, NoncedVerificationData, ResponseMessage,
20-
ValidityResponseMessage, VerificationCommitmentBatch, VerificationData,
21-
VerificationDataCommitment,
18+
use aligned_sdk::core::{
19+
constants::{
20+
ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF, AGGREGATOR_GAS_COST, CONSTANT_GAS_COST,
21+
DEFAULT_AGGREGATOR_FEE_DIVIDER, DEFAULT_AGGREGATOR_FEE_MULTIPLIER,
22+
DEFAULT_MAX_FEE_PER_PROOF, MIN_FEE_PER_PROOF, RESPOND_TO_TASK_FEE_LIMIT_DIVIDER,
23+
RESPOND_TO_TASK_FEE_LIMIT_MULTIPLIER,
24+
},
25+
types::{
26+
BatchInclusionData, ClientMessage, NoncedVerificationData, ResponseMessage,
27+
ValidityResponseMessage, VerificationCommitmentBatch, VerificationData,
28+
VerificationDataCommitment,
29+
},
2230
};
2331
use aws_sdk_s3::client::Client as S3Client;
2432
use eth::{try_create_new_task, BatcherPaymentService, CreateNewTaskFeeParams, SignerMiddlewareT};
@@ -49,20 +57,6 @@ pub mod sp1;
4957
pub mod types;
5058
mod zk_utils;
5159

52-
const AGGREGATOR_GAS_COST: u128 = 400_000;
53-
const BATCHER_SUBMISSION_BASE_GAS_COST: u128 = 125_000;
54-
pub(crate) const ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF: u128 = 13_000;
55-
pub(crate) const CONSTANT_GAS_COST: u128 =
56-
((AGGREGATOR_GAS_COST * DEFAULT_AGGREGATOR_FEE_MULTIPLIER) / DEFAULT_AGGREGATOR_FEE_DIVIDER)
57-
+ BATCHER_SUBMISSION_BASE_GAS_COST;
58-
59-
const DEFAULT_MAX_FEE_PER_PROOF: u128 = ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF * 100_000_000_000; // gas_price = 100 Gwei = 0.0000001 ether (high gas price)
60-
const MIN_FEE_PER_PROOF: u128 = ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF * 100_000_000; // gas_price = 0.1 Gwei = 0.0000000001 ether (low gas price)
61-
const RESPOND_TO_TASK_FEE_LIMIT_MULTIPLIER: u128 = 5; // to set the respondToTaskFeeLimit variable higher than fee_for_aggregator
62-
const RESPOND_TO_TASK_FEE_LIMIT_DIVIDER: u128 = 2;
63-
const DEFAULT_AGGREGATOR_FEE_MULTIPLIER: u128 = 3; // to set the feeForAggregator variable higher than what was calculated
64-
const DEFAULT_AGGREGATOR_FEE_DIVIDER: u128 = 2;
65-
6660
struct BatchState {
6761
batch_queue: BatchQueue,
6862
user_nonces: HashMap<Address, U256>,

batcher/aligned-sdk/Cargo.toml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,28 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7-
ethers = { tag = "v2.0.15-fix-reconnections", features = ["ws", "rustls", "eip712"], git = "https://github.com/yetanotherco/ethers-rs.git" }
8-
log = { version = "0.4.21"}
7+
ethers = { tag = "v2.0.15-fix-reconnections", features = [
8+
"ws",
9+
"rustls",
10+
"eip712",
11+
], git = "https://github.com/yetanotherco/ethers-rs.git" }
12+
log = { version = "0.4.21" }
913
serde_json = "1.0.117"
1014
tokio-tungstenite = { version = "0.23.1", features = ["native-tls"] }
1115
futures-util = "0.3.30"
12-
tokio = { version = "1.37.0", features = ["io-std", "time", "macros", "rt", "rt-multi-thread", "sync"] }
16+
tokio = { version = "1.37.0", features = [
17+
"io-std",
18+
"time",
19+
"macros",
20+
"rt",
21+
"rt-multi-thread",
22+
"sync",
23+
] }
1324
lambdaworks-crypto = { version = "0.7.0", features = ["serde"] }
1425
serde = { version = "1.0.201", features = ["derive"] }
15-
sha3 = { version = "0.10.8"}
26+
sha3 = { version = "0.10.8" }
1627
url = "2.5.0"
1728
hex = "0.4.3"
1829
ciborium = "=0.2.2"
1930
serde_repr = "0.1.19"
31+
dialoguer = "0.11.0"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// Batcher ///
2+
pub const AGGREGATOR_GAS_COST: u128 = 400_000;
3+
pub const BATCHER_SUBMISSION_BASE_GAS_COST: u128 = 125_000;
4+
pub const ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF: u128 = 13_000;
5+
pub const CONSTANT_GAS_COST: u128 = ((AGGREGATOR_GAS_COST * DEFAULT_AGGREGATOR_FEE_MULTIPLIER)
6+
/ DEFAULT_AGGREGATOR_FEE_DIVIDER)
7+
+ BATCHER_SUBMISSION_BASE_GAS_COST;
8+
pub const DEFAULT_MAX_FEE_PER_PROOF: u128 =
9+
ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF * 100_000_000_000; // gas_price = 100 Gwei = 0.0000001 ether (high gas price)
10+
pub const MIN_FEE_PER_PROOF: u128 = ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF * 100_000_000; // gas_price = 0.1 Gwei = 0.0000000001 ether (low gas price)
11+
pub const RESPOND_TO_TASK_FEE_LIMIT_MULTIPLIER: u128 = 5; // to set the respondToTaskFeeLimit variable higher than fee_for_aggregator
12+
pub const RESPOND_TO_TASK_FEE_LIMIT_DIVIDER: u128 = 2;
13+
pub const DEFAULT_AGGREGATOR_FEE_MULTIPLIER: u128 = 3; // to set the feeForAggregator variable higher than what was calculated
14+
pub const DEFAULT_AGGREGATOR_FEE_DIVIDER: u128 = 2;
15+
16+
/// SDK ///
17+
/// Number of proofs we a batch for estimation.
18+
/// This is the number of proofs in a batch of size n, where we set n = 32.
19+
/// i.e. the user pays for the entire batch and his proof is instantly submitted.
20+
pub const MAX_FEE_BATCH_PROOF_NUMBER: usize = 32;
21+
/// Estimated number of proofs for batch submission.
22+
/// This corresponds to the number of proofs to compute for a default max_fee.
23+
pub const MAX_FEE_DEFAULT_PROOF_NUMBER: usize = 10;

batcher/aligned-sdk/src/core/errors.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub enum AlignedError {
1515
VerificationError(VerificationError),
1616
NonceError(NonceError),
1717
ChainIdError(ChainIdError),
18+
MaxFeeEstimateError(MaxFeeEstimateError),
1819
}
1920

2021
impl From<SubmitError> for AlignedError {
@@ -41,13 +42,20 @@ impl From<ChainIdError> for AlignedError {
4142
}
4243
}
4344

45+
impl From<MaxFeeEstimateError> for AlignedError {
46+
fn from(e: MaxFeeEstimateError) -> Self {
47+
AlignedError::MaxFeeEstimateError(e)
48+
}
49+
}
50+
4451
impl fmt::Display for AlignedError {
4552
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
4653
match self {
4754
AlignedError::SubmitError(e) => write!(f, "Submit error: {}", e),
4855
AlignedError::VerificationError(e) => write!(f, "Verification error: {}", e),
4956
AlignedError::NonceError(e) => write!(f, "Nonce error: {}", e),
5057
AlignedError::ChainIdError(e) => write!(f, "Chain ID error: {}", e),
58+
AlignedError::MaxFeeEstimateError(e) => write!(f, "Max fee estimate error: {}", e),
5159
}
5260
}
5361
}
@@ -251,6 +259,25 @@ impl fmt::Display for ChainIdError {
251259
}
252260
}
253261

262+
#[derive(Debug)]
263+
pub enum MaxFeeEstimateError {
264+
EthereumProviderError(String),
265+
EthereumGasPriceError(String),
266+
}
267+
268+
impl fmt::Display for MaxFeeEstimateError {
269+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
270+
match self {
271+
MaxFeeEstimateError::EthereumProviderError(e) => {
272+
write!(f, "Ethereum provider error: {}", e)
273+
}
274+
MaxFeeEstimateError::EthereumGasPriceError(e) => {
275+
write!(f, "Failed to retreive the current gas price: {}", e)
276+
}
277+
}
278+
}
279+
}
280+
254281
#[derive(Debug)]
255282
pub enum VerifySignatureError {
256283
RecoverTypedDataError(SignatureError),

batcher/aligned-sdk/src/core/types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ impl NoncedVerificationData {
7171
}
7272
}
7373

74+
// Defines an estimate price preference for the user.
75+
#[derive(Debug, Serialize, Deserialize, Clone)]
76+
pub enum PriceEstimate {
77+
Min,
78+
Default,
79+
Instant,
80+
}
81+
7482
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
7583
pub struct VerificationDataCommitment {
7684
pub proof_commitment: [u8; 32],

batcher/aligned-sdk/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod core {
2+
pub mod constants;
23
pub mod errors;
34
pub mod types;
45
}

batcher/aligned-sdk/src/sdk.rs

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ use crate::{
55
protocol::check_protocol_version,
66
},
77
core::{
8+
constants::{
9+
ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF, CONSTANT_GAS_COST,
10+
MAX_FEE_BATCH_PROOF_NUMBER, MAX_FEE_DEFAULT_PROOF_NUMBER,
11+
},
812
errors,
913
types::{
10-
AlignedVerificationData, Network, ProvingSystemId, VerificationData,
14+
AlignedVerificationData, Network, PriceEstimate, ProvingSystemId, VerificationData,
1115
VerificationDataCommitment,
1216
},
1317
},
@@ -92,6 +96,107 @@ pub async fn submit_multiple_and_wait_verification(
9296
Ok(aligned_verification_data)
9397
}
9498

99+
/// Returns the estimated `max_fee` depending on the batch inclusion preference of the user, based on the max priority gas price.
100+
/// NOTE: The `max_fee` is computed from an rpc nodes max priority gas price.
101+
/// To estimate the `max_fee` of a batch we use a compute the `max_fee` with respect to a batch of ~32 proofs present.
102+
/// The `max_fee` estimates therefore are:
103+
/// * `Min`: Specifies a `max_fee` equivalent to the cost of 1 proof in a 32 proof batch.
104+
/// This estimates the lowest possible `max_fee` the user should specify for there proof with lowest priority.
105+
/// * `Default`: Specifies a `max_fee` equivalent to the cost of 10 proofs in a 32 proof batch.
106+
/// This estimates the `max_fee` the user should specify for inclusion within the batch.
107+
/// * `Instant`: specifies a `max_fee` equivalent to the cost of all proofs within in a 32 proof batch.
108+
/// This estimates the `max_fee` the user should specify to pay for the entire batch of proofs and have there proof included instantly.
109+
/// # Arguments
110+
/// * `eth_rpc_url` - The URL of the Ethereum RPC node.
111+
/// * `estimate` - Enum specifying the type of price estimate: MIN, DEFAULT, INSTANT.
112+
/// # Returns
113+
/// The estimated `max_fee` in gas for a proof based on the users `PriceEstimate` as a `U256`.
114+
/// # Errors
115+
/// * `EthereumProviderError` if there is an error in the connection with the RPC provider.
116+
/// * `EthereumGasPriceError` if there is an error retrieving the Ethereum gas price.
117+
pub async fn estimate_fee(
118+
eth_rpc_url: &str,
119+
estimate: PriceEstimate,
120+
) -> Result<U256, errors::MaxFeeEstimateError> {
121+
// Price of 1 proof in 32 proof batch
122+
let fee_per_proof = fee_per_proof(eth_rpc_url, MAX_FEE_BATCH_PROOF_NUMBER).await?;
123+
124+
let proof_price = match estimate {
125+
PriceEstimate::Min => fee_per_proof,
126+
PriceEstimate::Default => U256::from(MAX_FEE_DEFAULT_PROOF_NUMBER) * fee_per_proof,
127+
PriceEstimate::Instant => U256::from(MAX_FEE_BATCH_PROOF_NUMBER) * fee_per_proof,
128+
};
129+
Ok(proof_price)
130+
}
131+
132+
/// Returns the computed `max_fee` for a proof based on the number of proofs in a batch (`num_proofs_per_batch`) and
133+
/// number of proofs (`num_proofs`) in that batch the user would pay for i.e (`num_proofs` / `num_proofs_per_batch`).
134+
/// NOTE: The `max_fee` is computed from an rpc nodes max priority gas price.
135+
/// # Arguments
136+
/// * `eth_rpc_url` - The URL of the users Ethereum RPC node.
137+
/// * `num_proofs` - number of proofs in a batch the user would pay for.
138+
/// * `num_proofs_per_batch` - number of proofs within a batch.
139+
/// # Returns
140+
/// * The calculated `max_fee` as a `U256`.
141+
/// # Errors
142+
/// * `EthereumProviderError` if there is an error in the connection with the RPC provider.
143+
/// * `EthereumGasPriceError` if there is an error retrieving the Ethereum gas price.
144+
pub async fn compute_max_fee(
145+
eth_rpc_url: &str,
146+
num_proofs: usize,
147+
num_proofs_per_batch: usize,
148+
) -> Result<U256, errors::MaxFeeEstimateError> {
149+
let fee_per_proof = fee_per_proof(eth_rpc_url, num_proofs_per_batch).await?;
150+
Ok(fee_per_proof * num_proofs)
151+
}
152+
153+
/// Returns the `fee_per_proof` based on the current gas price for a batch compromised of `num_proofs_per_batch`
154+
/// i.e. (1 / `num_proofs_per_batch`).
155+
// NOTE: The `fee_per_proof` is computed from an rpc nodes max priority gas price.
156+
/// # Arguments
157+
/// * `eth_rpc_url` - The URL of the users Ethereum RPC node.
158+
/// * `num_proofs_per_batch` - number of proofs within a batch.
159+
/// # Returns
160+
/// * The fee per proof of a batch as a `U256`.
161+
/// # Errors
162+
/// * `EthereumProviderError` if there is an error in the connection with the RPC provider.
163+
/// * `EthereumGasPriceError` if there is an error retrieving the Ethereum gas price.
164+
pub async fn fee_per_proof(
165+
eth_rpc_url: &str,
166+
num_proofs_per_batch: usize,
167+
) -> Result<U256, errors::MaxFeeEstimateError> {
168+
let eth_rpc_provider =
169+
Provider::<Http>::try_from(eth_rpc_url).map_err(|e: url::ParseError| {
170+
errors::MaxFeeEstimateError::EthereumProviderError(e.to_string())
171+
})?;
172+
let gas_price = fetch_gas_price(&eth_rpc_provider).await?;
173+
174+
// Cost for estimate `num_proofs_per_batch` proofs
175+
let estimated_gas_per_proof = (CONSTANT_GAS_COST
176+
+ ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF * num_proofs_per_batch as u128)
177+
/ num_proofs_per_batch as u128;
178+
179+
// Price of 1 proof in 32 proof batch
180+
let fee_per_proof = U256::from(estimated_gas_per_proof) * gas_price;
181+
182+
Ok(fee_per_proof)
183+
}
184+
185+
async fn fetch_gas_price(
186+
eth_rpc_provider: &Provider<Http>,
187+
) -> Result<U256, errors::MaxFeeEstimateError> {
188+
let gas_price = match eth_rpc_provider.get_gas_price().await {
189+
Ok(price) => price,
190+
Err(e) => {
191+
return Err(errors::MaxFeeEstimateError::EthereumGasPriceError(
192+
e.to_string(),
193+
))
194+
}
195+
};
196+
197+
Ok(gas_price)
198+
}
199+
95200
/// Submits multiple proofs to the batcher to be verified in Aligned.
96201
/// # Arguments
97202
/// * `batcher_url` - The url of the batcher to which the proof will be submitted.
@@ -476,3 +581,50 @@ pub async fn get_chain_id(eth_rpc_url: &str) -> Result<u64, errors::ChainIdError
476581

477582
Ok(chain_id.as_u64())
478583
}
584+
585+
#[cfg(test)]
586+
mod test {
587+
//Public constants for convenience
588+
pub const HOLESKY_PUBLIC_RPC_URL: &str = "https://ethereum-holesky-rpc.publicnode.com";
589+
use super::*;
590+
591+
#[tokio::test]
592+
async fn computed_max_fee_for_larger_batch_is_smaller() {
593+
let small_fee = compute_max_fee(HOLESKY_PUBLIC_RPC_URL, 2, 10)
594+
.await
595+
.unwrap();
596+
let large_fee = compute_max_fee(HOLESKY_PUBLIC_RPC_URL, 5, 10)
597+
.await
598+
.unwrap();
599+
600+
assert!(small_fee < large_fee);
601+
}
602+
603+
#[tokio::test]
604+
async fn computed_max_fee_for_more_proofs_larger_than_for_less_proofs() {
605+
let small_fee = compute_max_fee(HOLESKY_PUBLIC_RPC_URL, 5, 20)
606+
.await
607+
.unwrap();
608+
let large_fee = compute_max_fee(HOLESKY_PUBLIC_RPC_URL, 5, 10)
609+
.await
610+
.unwrap();
611+
612+
assert!(small_fee < large_fee);
613+
}
614+
615+
#[tokio::test]
616+
async fn estimate_fee_are_larger_than_one_another() {
617+
let min_fee = estimate_fee(HOLESKY_PUBLIC_RPC_URL, PriceEstimate::Min)
618+
.await
619+
.unwrap();
620+
let default_fee = estimate_fee(HOLESKY_PUBLIC_RPC_URL, PriceEstimate::Default)
621+
.await
622+
.unwrap();
623+
let instant_fee = estimate_fee(HOLESKY_PUBLIC_RPC_URL, PriceEstimate::Instant)
624+
.await
625+
.unwrap();
626+
627+
assert!(min_fee < default_fee);
628+
assert!(default_fee < instant_fee);
629+
}
630+
}

0 commit comments

Comments
 (0)