Skip to content

Commit e480fa2

Browse files
authored
Merge pull request #6 from magicblock-labs/bmuddha/feat/improved-txn-diagnostics
feat: on failed txn checks, return more diagnostics info
2 parents 11bbaf2 + 78f5f29 commit e480fa2

File tree

6 files changed

+29
-168
lines changed

6 files changed

+29
-168
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,4 @@ shuttle-test = [
127127
]
128128

129129
[patch.crates-io]
130-
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "a892d2" }
130+
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "731fa50" }

src/access_permissions.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
use solana_pubkey::Pubkey;
12
use solana_svm_transaction::svm_message::SVMMessage;
2-
use solana_transaction_error::{TransactionError, TransactionResult};
3+
use solana_transaction_error::TransactionError;
34

45
use crate::account_loader::LoadedTransaction;
56

@@ -24,7 +25,7 @@ impl LoadedTransaction {
2425
pub(crate) fn validate_accounts_access(
2526
&self,
2627
message: &impl SVMMessage,
27-
) -> TransactionResult<()> {
28+
) -> Result<(), (TransactionError, Pubkey)> {
2829
let payer = self.accounts.first().map(|(_, acc)| acc);
2930
if payer.map(|p| p.privileged()).unwrap_or_default() {
3031
// Payer has privileged access, so we can skip the validation.
@@ -33,10 +34,10 @@ impl LoadedTransaction {
3334

3435
// For non-privileged payers, validate the rest of the accounts.
3536
// Skip the fee payer (index 0), as its writability is validated elsewhere.
36-
for (i, (_, acc)) in self.accounts.iter().enumerate().skip(1) {
37+
for (i, (pk, acc)) in self.accounts.iter().enumerate().skip(1) {
3738
// Enforce that any account intended to be writable must be a delegated account.
3839
if message.is_writable(i) && !acc.delegated() {
39-
return Err(TransactionError::InvalidWritableAccount);
40+
return Err((TransactionError::InvalidWritableAccount, *pk));
4041
}
4142
}
4243
Ok(())

src/account_loader.rs

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2141,55 +2141,6 @@ mod tests {
21412141
assert_eq!(account.rent_epoch(), RENT_EXEMPT_RENT_EPOCH);
21422142
}
21432143

2144-
#[test]
2145-
fn test_collect_rent_from_account_rent_paying() {
2146-
let feature_set = FeatureSet::all_enabled();
2147-
let rent_collector = RentCollector {
2148-
epoch: 1,
2149-
..RentCollector::default()
2150-
};
2151-
2152-
let address = Pubkey::new_unique();
2153-
let mut account = AccountSharedData::from(Account {
2154-
lamports: 1,
2155-
..Account::default()
2156-
});
2157-
2158-
assert_eq!(
2159-
collect_rent_from_account(&feature_set, &rent_collector, &address, &mut account),
2160-
CollectedInfo::default()
2161-
);
2162-
assert_eq!(account.rent_epoch(), 0);
2163-
assert_eq!(account.lamports(), 1);
2164-
}
2165-
2166-
#[test]
2167-
fn test_collect_rent_from_account_rent_enabled() {
2168-
let feature_set =
2169-
all_features_except(Some(&[feature_set::disable_rent_fees_collection::id()]));
2170-
let rent_collector = RentCollector {
2171-
epoch: 1,
2172-
..RentCollector::default()
2173-
};
2174-
2175-
let address = Pubkey::new_unique();
2176-
let mut account = AccountSharedData::from(Account {
2177-
lamports: 1,
2178-
data: vec![0],
2179-
..Account::default()
2180-
});
2181-
2182-
assert_eq!(
2183-
collect_rent_from_account(&feature_set, &rent_collector, &address, &mut account),
2184-
CollectedInfo {
2185-
rent_amount: 1,
2186-
account_data_len_reclaimed: 1
2187-
}
2188-
);
2189-
assert_eq!(account.rent_epoch(), 0);
2190-
assert_eq!(account.lamports(), 0);
2191-
}
2192-
21932144
// Ensure `TransactionProcessingCallback::inspect_account()` is called when
21942145
// loading accounts for transaction processing.
21952146
#[test]

src/rollback_accounts.rs

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -125,37 +125,6 @@ mod tests {
125125
solana_sdk_ids::system_program,
126126
};
127127

128-
#[test]
129-
fn test_new_fee_payer_only() {
130-
let fee_payer_address = Pubkey::new_unique();
131-
let fee_payer_account = AccountSharedData::new(100, 0, &Pubkey::default());
132-
let fee_payer_rent_epoch = fee_payer_account.rent_epoch();
133-
134-
const TEST_RENT_DEBIT: u64 = 1;
135-
let rent_collected_fee_payer_account = {
136-
let mut account = fee_payer_account.clone();
137-
account.set_lamports(fee_payer_account.lamports() - TEST_RENT_DEBIT);
138-
account.set_rent_epoch(fee_payer_rent_epoch + 1);
139-
account
140-
};
141-
142-
let rollback_accounts = RollbackAccounts::new(
143-
None,
144-
fee_payer_address,
145-
rent_collected_fee_payer_account,
146-
TEST_RENT_DEBIT,
147-
fee_payer_rent_epoch,
148-
);
149-
150-
let expected_fee_payer_account = fee_payer_account;
151-
match rollback_accounts {
152-
RollbackAccounts::FeePayerOnly { fee_payer_account } => {
153-
assert_eq!(expected_fee_payer_account, fee_payer_account);
154-
}
155-
_ => panic!("Expected FeePayerOnly variant"),
156-
}
157-
}
158-
159128
#[test]
160129
fn test_new_same_nonce_and_fee_payer() {
161130
let nonce_address = Pubkey::new_unique();

src/transaction_processor.rs

Lines changed: 22 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -469,8 +469,28 @@ impl<FG: ForkGraph> TransactionBatchProcessor<FG> {
469469
// This check is unnecessary for other load results (like `FeesOnly`),
470470
// as those states imply the transaction failed to load these other accounts,
471471
// and the fee payer is validated separately.
472-
if let Err(err) = loaded_transaction.validate_accounts_access(tx) {
473-
processing_results.push(Err(err));
472+
if let Err((err, offender)) = loaded_transaction.validate_accounts_access(tx) {
473+
// If an account access violation was detected, we construct a fake
474+
// execution for the convenience of the user, so that the transaction
475+
// will be persisted to the ledger with some useful debug information
476+
let execution_details = TransactionExecutionDetails {
477+
status: Err(err),
478+
log_messages: Some(vec![format!(
479+
"Account {offender} was used as writeable\
480+
without being delegated to this ER"
481+
)]),
482+
accounts_data_len_delta: 0,
483+
return_data: None,
484+
executed_units: 0,
485+
inner_instructions: None,
486+
};
487+
let txn = ExecutedTransaction {
488+
loaded_transaction,
489+
execution_details,
490+
programs_modified_by_tx: Default::default(),
491+
};
492+
let result = ProcessedTransaction::Executed(Box::new(txn));
493+
processing_results.push(Ok(result));
474494
continue;
475495
}
476496
// observe all the account balances before executing the transaction
@@ -2398,86 +2418,6 @@ mod tests {
23982418
);
23992419
}
24002420

2401-
#[test]
2402-
fn test_validate_transaction_fee_payer_rent_paying() {
2403-
let lamports_per_signature = 5000;
2404-
let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2405-
&[],
2406-
Some(&Pubkey::new_unique()),
2407-
&Hash::new_unique(),
2408-
));
2409-
let compute_budget_limits = process_compute_budget_instructions(
2410-
SVMMessage::program_instructions_iter(&message),
2411-
&FeatureSet::default(),
2412-
)
2413-
.unwrap();
2414-
let fee_payer_address = message.fee_payer();
2415-
let mut rent_collector = RentCollector::default();
2416-
rent_collector.rent.lamports_per_byte_year = 1_000_000;
2417-
let min_balance = rent_collector.rent.minimum_balance(0);
2418-
let transaction_fee = lamports_per_signature;
2419-
let starting_balance = min_balance - 1;
2420-
let mut fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default());
2421-
fee_payer_account.set_delegated(true);
2422-
let fee_payer_rent_debit = rent_collector
2423-
.get_rent_due(
2424-
fee_payer_account.lamports(),
2425-
fee_payer_account.data().len(),
2426-
fee_payer_account.rent_epoch(),
2427-
)
2428-
.lamports();
2429-
assert!(fee_payer_rent_debit > 0);
2430-
2431-
let mut mock_accounts = HashMap::new();
2432-
mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2433-
let mock_bank = MockBankCallback {
2434-
account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2435-
..Default::default()
2436-
};
2437-
let mut account_loader = (&mock_bank).into();
2438-
2439-
let mut error_counters = TransactionErrorMetrics::default();
2440-
let result =
2441-
TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2442-
&mut account_loader,
2443-
&message,
2444-
CheckedTransactionDetails::new(None, lamports_per_signature),
2445-
&Hash::default(),
2446-
FeeStructure::default().lamports_per_signature,
2447-
&rent_collector,
2448-
&mut error_counters,
2449-
&mock_bank,
2450-
);
2451-
2452-
let post_validation_fee_payer_account = {
2453-
let mut account = fee_payer_account.clone();
2454-
account.set_rent_epoch(1);
2455-
account.set_lamports(starting_balance - transaction_fee - fee_payer_rent_debit);
2456-
account
2457-
};
2458-
2459-
assert_eq!(
2460-
result,
2461-
Ok(ValidatedTransactionDetails {
2462-
rollback_accounts: RollbackAccounts::new(
2463-
None, // nonce
2464-
*fee_payer_address,
2465-
post_validation_fee_payer_account.clone(),
2466-
fee_payer_rent_debit,
2467-
0, // rent epoch
2468-
),
2469-
compute_budget_limits,
2470-
fee_details: FeeDetails::new(transaction_fee, 0),
2471-
loaded_fee_payer_account: LoadedTransactionAccount {
2472-
loaded_size: fee_payer_account.data().len(),
2473-
account: post_validation_fee_payer_account,
2474-
rent_collected: fee_payer_rent_debit,
2475-
},
2476-
fee_payer_address: *fee_payer_address,
2477-
})
2478-
);
2479-
}
2480-
24812421
#[test]
24822422
fn test_validate_transaction_fee_payer_not_found() {
24832423
let lamports_per_signature = 5000;

0 commit comments

Comments
 (0)