Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: added commitment balance validation multiple test #138

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions bolt-sidecar/src/client/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ impl RpcClient {

self.0.request("debug_traceCall", params).await
}

/// Send a raw transaction to the network.
pub async fn send_raw_transaction(&self, raw: Bytes) -> TransportResult<B256> {
self.0.request("eth_sendRawTransaction", [raw]).await
}
}

impl Deref for RpcClient {
Expand Down
113 changes: 72 additions & 41 deletions bolt-sidecar/src/state/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ impl<C: StateFetcher> ExecutionState<C> {

(
nonce_diff_acc + nonce_diff,
balance_diff_acc + balance_diff,
balance_diff_acc.saturating_add(balance_diff),
u64::max(highest_slot, slot),
)
},
Expand All @@ -279,7 +279,7 @@ impl<C: StateFetcher> ExecutionState<C> {

tracing::trace!(%sender, nonce_diff, %balance_diff, "Applying diffs to account state");

let account_state = match self.account_state(&sender) {
let account_state = match self.account_state(&sender).copied() {
Some(account) => account,
None => {
// Fetch the account state from the client if it does not exist
Expand All @@ -299,8 +299,8 @@ impl<C: StateFetcher> ExecutionState<C> {
};

let account_state_with_diffs = AccountState {
transaction_count: account_state.transaction_count + nonce_diff,
balance: account_state.balance - balance_diff,
transaction_count: account_state.transaction_count.saturating_add(nonce_diff),
balance: account_state.balance.saturating_sub(balance_diff),
has_code: account_state.has_code,
};

Expand Down Expand Up @@ -400,25 +400,9 @@ impl<C: StateFetcher> ExecutionState<C> {
}
}

/// Returns the account state for the given address INCLUDING any intermediate block templates state.
fn account_state(&self, address: &Address) -> Option<AccountState> {
let account_state = self.account_states.get(address).copied();

if let Some(mut account_state) = account_state {
// Iterate over all block templates and apply the state diff
for (_, template) in self.block_templates.iter() {
if let Some((nonce_diff, balance_diff)) = template.get_diff(address) {
// Nonce will always be increased
account_state.transaction_count += nonce_diff;
// Balance will always be decreased
account_state.balance -= balance_diff;
}
}

Some(account_state)
} else {
None
}
/// Returns the cached account state for the given address
fn account_state(&self, address: &Address) -> Option<&AccountState> {
self.account_states.get(address)
}

/// Gets the block template for the given slot number.
Expand All @@ -445,9 +429,8 @@ pub struct StateUpdate {
#[cfg(test)]
mod tests {
use crate::builder::template::StateDiff;
use crate::state::CommitmentDeadline;
use crate::state::Duration;
use std::num::NonZero;
use std::str::FromStr;
use std::{num::NonZero, time::Duration};

use alloy_consensus::constants::ETH_TO_WEI;
use alloy_eips::eip2718::Encodable2718;
Expand All @@ -466,21 +449,6 @@ mod tests {

use super::*;

#[tokio::test]
async fn test_commitment_deadline() {
let time = std::time::Instant::now();
let mut deadline = CommitmentDeadline::new(0, Duration::from_secs(1));

let slot = deadline.wait().await;
println!("Deadline reached. Passed {:?}", time.elapsed());
assert_eq!(slot, Some(0));

let time = std::time::Instant::now();
let slot = deadline.wait().await;
println!("Deadline reached. Passed {:?}", time.elapsed());
assert_eq!(slot, None);
}

#[tokio::test]
async fn test_valid_inclusion_request() -> eyre::Result<()> {
let _ = tracing_subscriber::fmt::try_init();
Expand Down Expand Up @@ -609,6 +577,69 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn test_invalid_inclusion_request_balance_multiple() -> eyre::Result<()> {
let _ = tracing_subscriber::fmt::try_init();

let anvil = launch_anvil();
let client = StateClient::new(anvil.endpoint_url());

let max_comms = NonZero::new(10).unwrap();
let mut state = ExecutionState::new(client.clone(), max_comms).await?;

let sender = anvil.addresses().first().unwrap();
let sender_pk = anvil.keys().first().unwrap();
let signer = Signer::random();

// initialize the state by updating the head once
let slot = client.get_head().await?;
state.update_head(None, slot).await?;

// Set the sender balance to just enough to pay for 1 transaction
let balance = U256::from_str("500000000000000").unwrap(); // leave just 0.0005 ETH
let sender_account = client.get_account_state(sender, None).await.unwrap();
let balance_to_burn = sender_account.balance - balance;

// burn the balance
let tx = default_test_transaction(*sender, Some(0)).with_value(uint!(balance_to_burn));
let request = create_signed_commitment_request(tx, sender_pk, 10).await?;
let tx_bytes = request
.as_inclusion_request()
.unwrap()
.tx
.envelope_encoded();
let _ = client.inner().send_raw_transaction(tx_bytes).await?;

// wait for the transaction to be included to update the sender balance
tokio::time::sleep(Duration::from_secs(2)).await;
let slot = client.get_head().await?;
state.update_head(None, slot).await?;

// create a new transaction and request a preconfirmation for it
let tx = default_test_transaction(*sender, Some(1));
let request = create_signed_commitment_request(tx, sender_pk, 10).await?;

assert!(state.validate_commitment_request(&request).await.is_ok());

let message = ConstraintsMessage::build(0, request.as_inclusion_request().unwrap().clone());
let signature = signer.sign(&message.digest())?.to_string();
let signed_constraints = SignedConstraints { message, signature };
state.add_constraint(10, signed_constraints);

// create a new transaction and request a preconfirmation for it
let tx = default_test_transaction(*sender, Some(2));
let request = create_signed_commitment_request(tx, sender_pk, 10).await?;

// this should fail because the balance is insufficient as we spent
// all of it on the previous preconfirmation
assert!(matches!(
state.validate_commitment_request(&request).await,
Err(ValidationError::InsufficientBalance)
));

Ok(())
}

#[tokio::test]
async fn test_invalid_inclusion_request_basefee() -> eyre::Result<()> {
let _ = tracing_subscriber::fmt::try_init();
Expand Down
7 changes: 7 additions & 0 deletions bolt-sidecar/src/state/fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ impl StateFetcher for StateClient {
}
}

#[cfg(test)]
impl StateClient {
pub fn inner(&self) -> &RpcClient {
&self.client
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
21 changes: 21 additions & 0 deletions bolt-sidecar/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,24 @@ impl Future for CommitmentDeadline {
}
}
}

#[cfg(test)]
mod tests {

use super::*;

#[tokio::test]
async fn test_commitment_deadline() {
let time = std::time::Instant::now();
let mut deadline = CommitmentDeadline::new(0, Duration::from_secs(1));

let slot = deadline.wait().await;
println!("Deadline reached. Passed {:?}", time.elapsed());
assert_eq!(slot, Some(0));

let time = std::time::Instant::now();
let slot = deadline.wait().await;
println!("Deadline reached. Passed {:?}", time.elapsed());
assert_eq!(slot, None);
}
}