Skip to content

Commit

Permalink
✨ Simplify possible errors, transaction error handler contains transa…
Browse files Browse the repository at this point in the history
…ction error
  • Loading branch information
lukacan committed Jan 13, 2025
1 parent fc9100b commit 0f05457
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 64 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ incremented upon a breaking change and the patch version will be incremented for
- Trident SVM + AFL (see the PR for more details) ([234](https://github.com/Ackee-Blockchain/trident/pull/234))

**Removed**

- removed fuzz_iteration from test_fuzz.rs ([243](https://github.com/Ackee-Blockchain/trident/pull/243))

**Changed**

- errors are simplified and transaction error contains only transaction error ([244](https://github.com/Ackee-Blockchain/trident/pull/244))

## [0.8.1] - 2024-11-14

**Removed**
Expand Down
26 changes: 4 additions & 22 deletions crates/fuzz/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ use thiserror::Error;

#[derive(Debug, Error)]
pub enum FuzzClientError {
#[error("Custom fuzzing error: {0}")]
#[error("Custom fuzz client error: {0}")]
Custom(u32),
#[error("Not able to initialize client: {0}")]
ClientInitError(#[from] TransactionError),
#[error("Transaction failed: {0}")]
TransactionFailed(TransactionError),
TransactionFailed(#[from] TransactionError),
}

#[derive(Debug, Error)]
Expand All @@ -20,31 +18,15 @@ pub enum FuzzingError {
Custom(u32),
#[error("Fuzzing error with Custom Message: {0}\n")]
CustomMessage(String),
#[error("Not able to deserialize account: {0}\n")]
CannotDeserializeAccount(String),
#[error("Optional Account not provided: {0}\n")]
OptionalAccountNotProvided(String),
#[error("Not enough Accounts: {0}\n")]
NotEnoughAccounts(String),
#[error("Account not Found: {0}\n")]
AccountNotFound(String),
#[error("Not Able To Obtain AccountInfos\n")]
NotAbleToObtainAccountInfos,
#[error("Balance Mismatch\n")]
BalanceMismatch,
#[error("Data Mismatch\n")]
DataMismatch,
#[error("Unable to obtain Data\n")]
UnableToObtainData,
}

impl FuzzClientError {
pub fn with_origin(self, origin: Origin) -> FuzzClientErrorWithOrigin {
pub(crate) fn with_origin(self, origin: Origin) -> FuzzClientErrorWithOrigin {
let mut error_with_origin = FuzzClientErrorWithOrigin::from(self);
error_with_origin.origin = Some(origin);
error_with_origin
}
pub fn with_context(self, context: Context) -> FuzzClientErrorWithOrigin {
pub(crate) fn with_context(self, context: Context) -> FuzzClientErrorWithOrigin {
let mut error_with_origin = FuzzClientErrorWithOrigin::from(self);
error_with_origin.context = Some(context);
error_with_origin
Expand Down
7 changes: 4 additions & 3 deletions crates/fuzz/src/fuzz_client.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#![allow(dead_code)]

use crate::error::*;
use solana_sdk::account::AccountSharedData;
use solana_sdk::hash::Hash;
use solana_sdk::instruction::Instruction;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair;
use solana_sdk::sysvar::Sysvar;
use solana_sdk::transaction::TransactionError;

use trident_config::Config;
use trident_svm::utils::ProgramEntrypoint;

Expand All @@ -23,7 +24,7 @@ pub trait FuzzClient {
fn warp_to_slot(&mut self, warp_slot: u64);

/// Forward in time by the desired number of seconds
fn forward_in_time(&mut self, seconds: i64) -> Result<(), FuzzClientError>;
fn forward_in_time(&mut self, seconds: i64);

/// Create or overwrite a custom account, subverting normal runtime checks.
fn set_account_custom(&mut self, address: &Pubkey, account: &AccountSharedData);
Expand All @@ -41,7 +42,7 @@ pub trait FuzzClient {
fn process_instructions(
&mut self,
_instructions: &[Instruction],
) -> Result<(), FuzzClientError>;
) -> Result<(), TransactionError>;

// Clear Temp account created during fuzzing iteration
fn clear_accounts(&mut self);
Expand Down
11 changes: 3 additions & 8 deletions crates/fuzz/src/fuzz_client_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use trident_svm::utils::SBFTargets;
use trident_svm::utils::TridentAccountSharedData;

use crate::fuzz_client::FuzzClient;
use solana_sdk::transaction::TransactionError;

impl FuzzClient for TridentSVM<'_> {
fn new_client(programs: &[ProgramEntrypoint], config: &Config) -> Self {
Expand Down Expand Up @@ -61,12 +62,11 @@ impl FuzzClient for TridentSVM<'_> {
self.set_sysvar(&clock);
}

fn forward_in_time(&mut self, seconds: i64) -> Result<(), crate::error::FuzzClientError> {
fn forward_in_time(&mut self, seconds: i64) {
let mut clock = self.get_sysvar::<Clock>();

clock.unix_timestamp = clock.unix_timestamp.saturating_add(seconds);
self.set_sysvar(&clock);
Ok(())
}

fn set_account_custom(&mut self, address: &Pubkey, account: &AccountSharedData) {
Expand All @@ -88,12 +88,7 @@ impl FuzzClient for TridentSVM<'_> {
fn process_instructions(
&mut self,
instructions: &[Instruction],
) -> Result<(), crate::error::FuzzClientError> {
// Currently Trident supports only one instruction in a transaction
if instructions.len() != 1 {
return Err(crate::error::FuzzClientError::Custom(55000));
}

) -> Result<(), TransactionError> {
// there should be at least 1 RW fee-payer account.
// But we do not pay for TX currently so has to be manually updated
// tx.message.header.num_required_signatures = 1;
Expand Down
7 changes: 4 additions & 3 deletions crates/fuzz/src/ix_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::fuzz_client::FuzzClient;
use crate::snapshot::SnapshotAccount;
use solana_sdk::instruction::AccountMeta;
use solana_sdk::signature::Keypair;
use solana_sdk::transaction::TransactionError;

/// A trait providing methods to prepare data and accounts for the fuzzed instructions and allowing
/// users to implement custom invariants checks and transactions error handling.
Expand Down Expand Up @@ -74,10 +75,10 @@ pub trait IxOps {
#[allow(unused_variables)]
fn tx_error_handler(
&self,
e: FuzzClientErrorWithOrigin,
e: TransactionError,
ix_data: Vec<u8>,
pre_ix_acc_infos: &[SnapshotAccount],
) -> Result<(), FuzzClientErrorWithOrigin> {
pre_ix_accounts: &[SnapshotAccount],
) -> Result<(), TransactionError> {
Err(e)
}
}
1 change: 1 addition & 0 deletions crates/fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub mod fuzzing {
pub use solana_sdk::signer::keypair::Keypair;
pub use solana_sdk::signer::Signer;
pub use solana_sdk::transaction::Transaction;
pub use solana_sdk::transaction::TransactionError;

pub use afl::fuzz as fuzz_afl;
pub use arbitrary;
Expand Down
57 changes: 42 additions & 15 deletions crates/fuzz/src/transaction_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ use std::cell::RefCell;

use solana_sdk::instruction::Instruction;

use crate::{
error::{FuzzClientErrorWithOrigin, Origin},
fuzz_client::FuzzClient,
fuzz_stats::FuzzingStatistics,
ix_ops::IxOps,
snapshot::Snapshot,
};
use crate::error::FuzzClientError;
use crate::error::FuzzClientErrorWithOrigin;
use crate::error::Origin;
use crate::fuzz_client::FuzzClient;
use crate::fuzz_stats::FuzzingStatistics;
use crate::ix_ops::IxOps;
use crate::snapshot::Snapshot;

use trident_config::Config;

Expand All @@ -25,45 +25,60 @@ impl TransactionExecutor {
where
I: IxOps,
{
// Obtain the program id
let program_id = ix.get_program_id();

// Obtain the instruction data
let data = ix
.get_data(client, &mut accounts.borrow_mut())
.map_err(|e| e.with_origin(Origin::Instruction(instruction_name.to_owned())))
.expect("Data calculation expect");

// Obtain the account metas and signers
let (_signers, account_metas) = ix
.get_accounts(client, &mut accounts.borrow_mut())
.map_err(|e| e.with_origin(Origin::Instruction(instruction_name.to_owned())))
.expect("Accounts calculation expect");

// Initializes the snapshot from the account metas
let mut snapshot = Snapshot::new(&account_metas);

// Capture the accounts before the instruction is executed
snapshot.capture_before(client).unwrap();

// Create the instruction to be executed
let ixx = Instruction {
program_id,
accounts: account_metas,
data: data.clone(),
};

// If stats are enabled, log the invocation of the instruction
if config.get_fuzzing_with_stats() {
let mut stats_logger = FuzzingStatistics::new();

stats_logger.increase_invoked(instruction_name.to_owned());

let tx_result = client
.process_instructions(&[ixx])
.map_err(|e| e.with_origin(Origin::Instruction(instruction_name.to_owned())));
// Execute the instruction
let tx_result = client.process_instructions(&[ixx]);

// Check the result of the instruction execution
match tx_result {
Ok(_) => {
// Log the successful execution of the instruction
stats_logger.increase_successful(instruction_name.to_owned());

// Capture the accounts after the instruction is executed
snapshot.capture_after(client).unwrap();

// Get the snapshot of the accounts before and after the instruction execution
let (acc_before, acc_after) = snapshot.get_snapshot();

// Let the user perform custom checks on the accounts
if let Err(e) = ix.check(acc_before, acc_after, data).map_err(|e| {
e.with_origin(Origin::Instruction(instruction_name.to_owned()))
}) {
// Log the failure of the custom check
stats_logger.increase_failed_check(instruction_name.to_owned());
stats_logger.output_serialized();

Expand All @@ -73,22 +88,30 @@ impl TransactionExecutor {
stats_logger.output_serialized();
}
Err(e) => {
// Log the failure of the instruction execution
stats_logger.increase_failed(instruction_name.to_owned());
stats_logger.output_serialized();

// Let use use transaction error handler to handle the error
let raw_accounts = snapshot.get_before();
ix.tx_error_handler(e, data, raw_accounts)?
ix.tx_error_handler(e, data, raw_accounts).map_err(|e| {
FuzzClientError::from(e)
.with_origin(Origin::Instruction(instruction_name.to_owned()))
})?
}
}
} else {
let tx_result = client
.process_instructions(&[ixx])
.map_err(|e| e.with_origin(Origin::Instruction(instruction_name.to_owned())));
// If stats are not enabled, execute the instruction directly
let tx_result = client.process_instructions(&[ixx]);
match tx_result {
Ok(_) => {
// Capture the accounts after the instruction is executed
snapshot.capture_after(client).unwrap();

// Get the snapshot of the accounts before and after the instruction execution
let (acc_before, acc_after) = snapshot.get_snapshot();

// Let the user perform custom checks on the accounts
if let Err(e) = ix.check(acc_before, acc_after, data).map_err(|e| {
e.with_origin(Origin::Instruction(instruction_name.to_owned()))
}) {
Expand All @@ -97,8 +120,12 @@ impl TransactionExecutor {
}
}
Err(e) => {
// Let use use transaction error handler to handle the error
let raw_accounts = snapshot.get_before();
ix.tx_error_handler(e, data, raw_accounts)?
ix.tx_error_handler(e, data, raw_accounts).map_err(|e| {
FuzzClientError::from(e)
.with_origin(Origin::Instruction(instruction_name.to_owned()))
})?
}
}
}
Expand Down
24 changes: 12 additions & 12 deletions documentation/docs/features/error-handlers.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
# Error Handler

Trident allows you to specify custom error handler for each Instruction.
Trident allows you to specify a custom error handler for each instruction.

This can be particularly helpful:
This can be particularly helpful in the following scenarios:

- If Transaction returns Error, you can specify to omit this error and continue with the fuzzing instruction.
- Using the `tx_error_handler` you can check if the error returned is desired based on the Accounts and Input data that were used.
- If a transaction returns an error, you can choose to omit this error and continue the fuzzing process.
- Using the `tx_error_handler`, you can check if the returned error is desired based on the accounts and input data that were used.

!!! tip

The default behavior of the function is that the error is returned.
By default, transaction errors are propagated, meaning that if the transaction fails, the fuzzing iteration is stopped, and a new fuzzing iteration is started.

```rust
/// default implementation
/// Default implementation
fn tx_error_handler(
&self,
e: FuzzClientErrorWithOrigin,
e: TransactionError,
ix_data: Vec<u8>,
pre_ix_acc_infos: &[SnapshotAccount],
) -> Result<(), FuzzClientErrorWithOrigin> {
) -> Result<(), TransactionError> {
Err(e)
}
```

To omit the Error and continue with the next Instruction in the iteration, you can do
To omit the error and continue with the next instruction in the iteration, you can use the following implementation:

```rust
/// custom implementation
/// Custom implementation
fn tx_error_handler(
&self,
e: FuzzClientErrorWithOrigin,
e: TransactionError,
ix_data: Vec<u8>,
pre_ix_acc_infos: &[SnapshotAccount],
) -> Result<(), FuzzClientErrorWithOrigin> {
) -> Result<(), TransactionError> {
Ok(())
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ impl IxOps for WithdrawUnlocked {
if escrow.recipient == recipient {
if recipient_token_account_pre.amount == recipient_token_account_post.amount {
// Recipient was not able to withdraw
return Err(FuzzingError::BalanceMismatch);
return Err(FuzzingError::Custom(55));
} else if recipient_token_account_pre.amount + escrow.amount
!= recipient_token_account_post.amount
{
Expand Down

0 comments on commit 0f05457

Please sign in to comment.