Skip to content

Commit 92f097f

Browse files
feat(batcher): get last max fee msg (#2127)
Co-authored-by: JuArce <[email protected]>
1 parent 38f16e2 commit 92f097f

File tree

7 files changed

+299
-6
lines changed

7 files changed

+299
-6
lines changed

crates/batcher/src/lib.rs

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ use aligned_sdk::common::constants::{
3333
RESPOND_TO_TASK_FEE_LIMIT_PERCENTAGE_MULTIPLIER,
3434
};
3535
use aligned_sdk::common::types::{
36-
ClientMessage, GetNonceResponseMessage, NoncedVerificationData, ProofInvalidReason,
37-
ProvingSystemId, SubmitProofMessage, SubmitProofResponseMessage, VerificationCommitmentBatch,
38-
VerificationData, VerificationDataCommitment,
36+
ClientMessage, GetLastMaxFeeResponseMessage, GetNonceResponseMessage, NoncedVerificationData,
37+
ProofInvalidReason, ProvingSystemId, SubmitProofMessage, SubmitProofResponseMessage,
38+
VerificationCommitmentBatch, VerificationData, VerificationDataCommitment,
3939
};
4040

4141
use aws_sdk_s3::client::Client as S3Client;
@@ -916,6 +916,11 @@ impl Batcher {
916916
.handle_submit_proof_msg(msg, ws_conn_sink)
917917
.await
918918
}
919+
ClientMessage::GetLastMaxFee(address) => {
920+
self.clone()
921+
.handle_get_last_max_fee(address, ws_conn_sink)
922+
.await
923+
}
919924
}
920925
}
921926

@@ -1004,6 +1009,84 @@ impl Batcher {
10041009
Ok(())
10051010
}
10061011

1012+
async fn handle_get_last_max_fee(
1013+
self: Arc<Self>,
1014+
mut address: Address,
1015+
ws_conn_sink: WsMessageSink,
1016+
) -> Result<(), Error> {
1017+
// If the address is not paying, we will return the last max fee of the aligned_payment_address
1018+
if !self.has_to_pay(&address) {
1019+
info!("Handling nonpaying message");
1020+
let Some(non_paying_config) = self.non_paying_config.as_ref() else {
1021+
warn!(
1022+
"There isn't a non-paying configuration loaded. This message will be ignored"
1023+
);
1024+
send_message(
1025+
ws_conn_sink.clone(),
1026+
GetLastMaxFeeResponseMessage::InvalidRequest(
1027+
"There isn't a non-paying configuration loaded.".to_string(),
1028+
),
1029+
)
1030+
.await;
1031+
return Ok(());
1032+
};
1033+
let replacement_addr = non_paying_config.replacement.address();
1034+
address = replacement_addr;
1035+
}
1036+
1037+
let user_states_guard = match timeout(MESSAGE_HANDLER_LOCK_TIMEOUT, self.user_states.read())
1038+
.await
1039+
{
1040+
Ok(guard) => guard,
1041+
Err(_) => {
1042+
warn!("User states read lock acquisition timed out in handle_get_last_max_fee_for_address_msg");
1043+
self.metrics.inc_message_handler_user_states_lock_timeouts();
1044+
send_message(ws_conn_sink, GetLastMaxFeeResponseMessage::ServerBusy).await;
1045+
return Ok(());
1046+
}
1047+
};
1048+
1049+
let Some(usr_ref) = user_states_guard.get(&address).cloned() else {
1050+
drop(user_states_guard);
1051+
send_message(
1052+
ws_conn_sink.clone(),
1053+
GetLastMaxFeeResponseMessage::LastMaxFee(U256::MAX),
1054+
)
1055+
.await;
1056+
return Ok(());
1057+
};
1058+
1059+
let Some(usr_lock) = self
1060+
.try_user_lock_with_timeout(address, usr_ref.lock())
1061+
.await
1062+
else {
1063+
drop(user_states_guard);
1064+
send_message(
1065+
ws_conn_sink.clone(),
1066+
GetLastMaxFeeResponseMessage::ServerBusy,
1067+
)
1068+
.await;
1069+
return Ok(());
1070+
};
1071+
1072+
let proofs_in_queue = usr_lock.proofs_in_batch;
1073+
let max_fee = if proofs_in_queue > 0 {
1074+
usr_lock.last_max_fee_limit
1075+
} else {
1076+
U256::MAX
1077+
};
1078+
drop(usr_lock);
1079+
drop(user_states_guard);
1080+
1081+
send_message(
1082+
ws_conn_sink.clone(),
1083+
GetLastMaxFeeResponseMessage::LastMaxFee(max_fee),
1084+
)
1085+
.await;
1086+
1087+
Ok(())
1088+
}
1089+
10071090
/// Returns the Aligned-funded address that will be used to pay for proofs when users don't need to pay themselves.
10081091
/// This function assumes that the non-paying configuration is set.
10091092
fn aligned_payment_address(&self) -> Address {

crates/cli/src/main.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use aligned_sdk::communication::serialization::cbor_deserialize;
2020
use aligned_sdk::verification_layer;
2121
use aligned_sdk::verification_layer::estimate_fee;
2222
use aligned_sdk::verification_layer::get_chain_id;
23+
use aligned_sdk::verification_layer::get_last_max_fee;
2324
use aligned_sdk::verification_layer::get_nonce_from_batcher;
2425
use aligned_sdk::verification_layer::get_nonce_from_ethereum;
2526
use aligned_sdk::verification_layer::get_unlock_block_time;
@@ -46,6 +47,7 @@ use transaction::eip2718::TypedTransaction;
4647
use crate::AlignedCommands::DepositToBatcher;
4748
use crate::AlignedCommands::GetUserAmountOfQueuedProofs;
4849
use crate::AlignedCommands::GetUserBalance;
50+
use crate::AlignedCommands::GetUserLastMaxFee;
4951
use crate::AlignedCommands::GetUserNonce;
5052
use crate::AlignedCommands::GetUserNonceFromEthereum;
5153
use crate::AlignedCommands::GetVkCommitment;
@@ -94,6 +96,11 @@ pub enum AlignedCommands {
9496
name = "get-user-nonce-from-ethereum"
9597
)]
9698
GetUserNonceFromEthereum(GetUserNonceFromEthereumArgs),
99+
#[clap(
100+
about = "Gets user current last max fee from the batcher. This is the max limit you can send in your next proof.",
101+
name = "get-user-last-max-fee"
102+
)]
103+
GetUserLastMaxFee(GetUserLastMaxFeeArgs),
97104
#[clap(
98105
about = "Gets the number of proofs a user has queued in the Batcher.",
99106
name = "get-user-amount-of-queued-proofs"
@@ -316,6 +323,15 @@ pub struct GetUserNonceArgs {
316323
address: String,
317324
}
318325

326+
#[derive(Parser, Debug)]
327+
#[command(version, about, long_about = None)]
328+
pub struct GetUserLastMaxFeeArgs {
329+
#[clap(flatten)]
330+
network: NetworkArg,
331+
#[arg(name = "The user's Ethereum address", required = true)]
332+
address: String,
333+
}
334+
319335
#[derive(Parser, Debug)]
320336
#[command(version, about, long_about = None)]
321337
pub struct GetUserNonceFromEthereumArgs {
@@ -1041,6 +1057,22 @@ async fn main() -> Result<(), AlignedError> {
10411057
}
10421058
}
10431059
}
1060+
GetUserLastMaxFee(args) => {
1061+
let address = H160::from_str(&args.address).unwrap();
1062+
match get_last_max_fee(args.network.into(), address).await {
1063+
Ok(last_max_fee) => {
1064+
let last_max_fee_ether = ethers::utils::format_ether(last_max_fee);
1065+
info!(
1066+
"Last max fee for address {} is {}eth",
1067+
address, last_max_fee_ether
1068+
);
1069+
}
1070+
Err(e) => {
1071+
error!("Error while getting last max fee: {:?}", e);
1072+
return Ok(());
1073+
}
1074+
}
1075+
}
10441076
GetUserAmountOfQueuedProofs(args) => {
10451077
let address = H160::from_str(&args.address).unwrap();
10461078
let network: Network = args.network.into();

crates/sdk/src/common/errors.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,16 @@ pub enum GetNonceError {
266266
GenericError(String),
267267
}
268268

269+
#[derive(Debug, Serialize, Deserialize, Clone)]
270+
pub enum GetLastMaxFeeError {
271+
ConnectionFailed(String),
272+
SerializationError(String),
273+
UnexpectedResponse(String),
274+
InvalidRequest(String),
275+
ProtocolMismatch { current: u16, expected: u16 },
276+
GenericError(String),
277+
}
278+
269279
#[derive(Debug)]
270280
pub enum ChainIdError {
271281
EthereumProviderError(String),

crates/sdk/src/common/types.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ pub enum ClientMessage {
295295
// Needs to be wrapped in box as the message is 3x bigger than the others
296296
// see https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
297297
SubmitProof(Box<SubmitProofMessage>),
298+
GetLastMaxFee(Address),
298299
}
299300

300301
impl Display for ClientMessage {
@@ -303,6 +304,7 @@ impl Display for ClientMessage {
303304
match self {
304305
ClientMessage::GetNonceForAddress(_) => write!(f, "GetNonceForAddress"),
305306
ClientMessage::SubmitProof(_) => write!(f, "SubmitProof"),
307+
ClientMessage::GetLastMaxFee(_) => write!(f, "GetLastMaxFee"),
306308
}
307309
}
308310
}
@@ -465,6 +467,13 @@ pub enum GetNonceResponseMessage {
465467
ServerBusy,
466468
}
467469

470+
#[derive(Debug, Clone, Serialize, Deserialize)]
471+
pub enum GetLastMaxFeeResponseMessage {
472+
LastMaxFee(U256),
473+
InvalidRequest(String),
474+
ServerBusy,
475+
}
476+
468477
#[derive(Debug, Clone)]
469478
pub enum Network {
470479
Devnet,

crates/sdk/src/verification_layer/mod.rs

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ use crate::{
55
DEFAULT_MAX_FEE_BATCH_SIZE, GAS_PRICE_PERCENTAGE_MULTIPLIER,
66
INSTANT_MAX_FEE_BATCH_SIZE, PERCENTAGE_DIVIDER,
77
},
8-
errors::{self, GetNonceError, PaymentError},
8+
errors::{self, GetLastMaxFeeError, GetNonceError, PaymentError},
99
types::{
10-
AlignedVerificationData, ClientMessage, FeeEstimationType, GetNonceResponseMessage,
11-
Network, ProvingSystemId, VerificationData,
10+
AlignedVerificationData, ClientMessage, FeeEstimationType,
11+
GetLastMaxFeeResponseMessage, GetNonceResponseMessage, Network, ProvingSystemId,
12+
VerificationData,
1213
},
1314
},
1415
communication::{
@@ -650,6 +651,87 @@ pub async fn get_nonce_from_ethereum(
650651
}
651652
}
652653

654+
/// Retrieves the `max_fee` of the proof with the highest nonce in the batcher queue for a given address.
655+
///
656+
/// This value represents the maximum fee limit that can be used when submitting the next proof.
657+
/// To increase the fee limit for a new proof, you must first bump the fee of the previous proofs,
658+
/// and continue doing so recursively until you reach the proof with the highest nonce (this one).
659+
///
660+
/// Read more here: https://docs.alignedlayer.com/architecture/1_proof_verification_layer/1_batcher#max-fee-priority-queue
661+
///
662+
/// # Arguments
663+
/// * `network` - The network from which to retrieve the last `max_fee`.
664+
/// * `address` - The user address whose last `max_fee` will be retrieved.
665+
///
666+
/// # Returns
667+
/// * `Ok(U256)` - The `max_fee` of the proof with the highest nonce for the given user.
668+
/// * `Ok(U256::MAX)` - If the user has no proofs in the queue.
669+
pub async fn get_last_max_fee(
670+
network: Network,
671+
address: Address,
672+
) -> Result<U256, GetLastMaxFeeError> {
673+
let (ws_stream, _) = connect_async(network.get_batcher_url())
674+
.await
675+
.map_err(|_| {
676+
GetLastMaxFeeError::ConnectionFailed("Ws connection to batcher failed".to_string())
677+
})?;
678+
679+
debug!("WebSocket handshake has been successfully completed");
680+
let (mut ws_write, mut ws_read) = ws_stream.split();
681+
check_protocol_version(&mut ws_read)
682+
.map_err(|e| match e {
683+
errors::SubmitError::ProtocolVersionMismatch { current, expected } => {
684+
GetLastMaxFeeError::ProtocolMismatch { current, expected }
685+
}
686+
_ => GetLastMaxFeeError::UnexpectedResponse(
687+
"Unexpected response, expected protocol version".to_string(),
688+
),
689+
})
690+
.await?;
691+
692+
let msg = ClientMessage::GetLastMaxFee(address);
693+
694+
let msg_bin = cbor_serialize(&msg).map_err(|_| {
695+
GetLastMaxFeeError::SerializationError("Failed to serialize msg".to_string())
696+
})?;
697+
ws_write
698+
.send(Message::Binary(msg_bin.clone()))
699+
.await
700+
.map_err(|_| {
701+
GetLastMaxFeeError::ConnectionFailed(
702+
"Ws connection failed to send message to batcher".to_string(),
703+
)
704+
})?;
705+
706+
let mut response_stream: ResponseStream =
707+
ws_read.try_filter(|msg| futures_util::future::ready(msg.is_binary()));
708+
709+
let msg = match response_stream.next().await {
710+
Some(Ok(msg)) => msg,
711+
_ => {
712+
return Err(GetLastMaxFeeError::ConnectionFailed(
713+
"Connection was closed without close message before receiving all messages"
714+
.to_string(),
715+
));
716+
}
717+
};
718+
719+
let _ = ws_write.close().await;
720+
721+
match cbor_deserialize(msg.into_data().as_slice()) {
722+
Ok(GetLastMaxFeeResponseMessage::LastMaxFee(last_max_fee)) => Ok(last_max_fee),
723+
Ok(GetLastMaxFeeResponseMessage::InvalidRequest(e)) => {
724+
Err(GetLastMaxFeeError::InvalidRequest(e))
725+
}
726+
Ok(GetLastMaxFeeResponseMessage::ServerBusy) => Err(GetLastMaxFeeError::GenericError(
727+
"Server is busy processing requests, please retry".to_string(),
728+
)),
729+
Err(_) => Err(GetLastMaxFeeError::SerializationError(
730+
"Failed to deserialize batcher message".to_string(),
731+
)),
732+
}
733+
}
734+
653735
/// Returns the chain ID of the Ethereum network.
654736
///
655737
/// # Arguments

docs/3_guides/1.2_SDK_api_reference.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,43 @@ pub async fn get_nonce_from_batcher(
274274

275275
- `EthRpcError` if the batcher has an error in the Ethereum call when retrieving the nonce if not already cached.
276276

277+
### `get_last_max_fee`
278+
279+
Retrieves the `max_fee` of the proof with the highest nonce in the batcher queue for a given address.
280+
281+
This value represents the maximum fee limit that can be used when submitting the next proof. To increase the fee limit for a new proof, you must first bump the fee of the previous proofs queued in the batcher.
282+
283+
Read more here: https://docs.alignedlayer.com/architecture/1_proof_verification_layer/1_batcher#max-fee-priority-queue
284+
285+
```rust
286+
pub async fn get_last_max_fee(
287+
network: Network,
288+
address: Address,
289+
) -> Result<U256, GetLastMaxFeeError>
290+
```
291+
292+
#### Arguments
293+
294+
- `network` - The network from which to retrieve the last `max_fee`.
295+
- `address` - The user address whose last `max_fee` will be retrieved.
296+
297+
#### Returns
298+
299+
- `Result<U256, GetLastMaxFeeError>` - The `max_fee` of the proof with the highest nonce for the given user, or `U256::MAX` if the user has no proofs in the queue.
300+
301+
#### Errors
302+
303+
- `ConnectionFailed` if there is an error connecting to the batcher.
304+
- `ProtocolMismatch` if the protocol version doesn't match.
305+
- `SerializationError` if there is an error serializing/deserializing the message.
306+
- `InvalidRequest` if the request is invalid.
307+
- `UnexpectedResponse` if the batcher responds with an unexpected message.
308+
- `GenericError` if the error doesn't match any of the previous ones.
309+
310+
#### Notes
311+
312+
- Returns `U256::MAX` (2^256 - 1) when no proofs are present in the queue for the user.
313+
277314
### `get_chain_id`
278315

279316
Returns the chain ID for a given rpc url.

0 commit comments

Comments
 (0)