Skip to content

Commit f459d2d

Browse files
Revert "feat(executor): validate no confined account is modified" (#745)
1 parent e4e19b4 commit f459d2d

File tree

8 files changed

+94
-178
lines changed

8 files changed

+94
-178
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: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ serde_json = "1.0"
131131
serde_with = "3.16"
132132
serial_test = "3.2"
133133
sha3 = "0.10.8"
134-
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "57158728" }
134+
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "1beed4c" }
135135
solana-account-decoder = { version = "2.2" }
136136
solana-account-decoder-client-types = { version = "2.2" }
137137
solana-account-info = { version = "2.2" }
@@ -216,10 +216,11 @@ features = ["lz4"]
216216
# some solana dependencies have solana-storage-proto as dependency
217217
# we need to patch them with our version, because they use protobuf-src v1.1.0
218218
# and we use protobuf-src v2.1.1. Otherwise compilation fails
219-
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "57158728" }
219+
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "1beed4c" }
220220
solana-storage-proto = { path = "./storage-proto" }
221221
solana-svm = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "3e9456ec4" }
222222
# Fork is used to enable `disable_manual_compaction` usage
223223
# Fork is based on commit d4e9e16 of rocksdb (parent commit of 0.23.0 release)
224224
# without patching update isn't possible due to conflict with solana deps
225-
rocksdb = { git = "https://github.com/magicblock-labs/rust-rocksdb.git", rev = "6d975197" }
225+
# TODO(edwin): remove once solana deps upgraded and are using rust-rocksdb 0.25.0(likely)
226+
rocksdb = { git = "https://github.com/magicblock-labs/rust-rocksdb.git", rev = "6d975197" }

magicblock-accounts-db/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ impl AccountsDb {
101101
// atomic counter. New readers will see the latest update.
102102
acc.commit();
103103
// check whether the account's owner has changed
104-
if !acc.owner_changed() {
104+
if !acc.owner_changed {
105105
return;
106106
}
107-
// and perform some index bookkeeping to ensure a correct owner
107+
// and perform some index bookkeeping to ensure correct owner
108108
let _ = self
109109
.index
110110
.ensure_correct_owner(pubkey, account.owner())

magicblock-processor/src/executor/processing.rs

Lines changed: 28 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -341,18 +341,16 @@ impl super::TransactionExecutor {
341341

342342
/// Ensure that no post execution account state violations occurred:
343343
/// 1. No modification of the non-delegated feepayer in gasless mode
344-
/// 2. No lamport modification of confined accounts
344+
/// 2. No illegal account resizing when the balance is zero
345345
fn verify_account_states(&self, processed: &mut ProcessedTransaction) {
346346
let ProcessedTransaction::Executed(executed) = processed else {
347347
return;
348348
};
349349
let txn = &executed.loaded_transaction;
350350
let feepayer = txn.accounts.first();
351+
let rollback_lamports =
352+
rollback_feepayer_lamports(&txn.rollback_accounts);
351353

352-
let logs = executed
353-
.execution_details
354-
.log_messages
355-
.get_or_insert_default();
356354
let gasless = self.environment.fee_lamports_per_signature == 0;
357355
if gasless {
358356
// If we are running in the gasless mode, we should not allow
@@ -361,50 +359,40 @@ impl super::TransactionExecutor {
361359
// from undelegated feepayers to delegated accounts, which would
362360
// result in validator loosing funds upon balance settling.
363361
let undelegated_feepayer_was_modified = feepayer
364-
.map(|(_, acc)| {
365-
let mutated = acc
366-
.as_borrowed()
367-
.map(|a| a.lamports_changed())
368-
// NOTE: this branch can be taken only, if the account
369-
// has been upgraded to the Owned variant, which indicates
370-
// that it has been resized, which in turn is a clear
371-
// violation of the feepayer immutability rule in this mode
372-
.unwrap_or(true);
373-
!self.is_auto_airdrop_lamports_enabled
374-
&& mutated
375-
&& !acc.delegated()
376-
&& !acc.privileged()
362+
.map(|acc| {
363+
(acc.1.is_dirty()
364+
&& !self.is_auto_airdrop_lamports_enabled
365+
&& (acc.1.lamports() != 0 || rollback_lamports != 0))
366+
&& !acc.1.delegated()
367+
&& !acc.1.privileged()
377368
})
378369
.unwrap_or_default();
379370
if undelegated_feepayer_was_modified {
380371
executed.execution_details.status =
381372
Err(TransactionError::InvalidAccountForFee);
382-
let msg = "Feepayer balance has been illegally modified".into();
373+
let logs = executed
374+
.execution_details
375+
.log_messages
376+
.get_or_insert_default();
377+
let msg = "Feepayer balance has been modified illegally".into();
383378
logs.push(msg);
384-
return;
385379
}
386380
}
387-
for (pubkey, acc) in &txn.accounts {
388-
if !acc.confined() {
389-
continue;
390-
}
391-
// If the confined account was modified in any way that affected its lamport
392-
// balance, then an corresponding marker must have been set, in which case we
393-
// fail the transaction, since this is regarded as a validator draining attack
394-
let balance_changed = acc
395-
.as_borrowed()
396-
.map(|a| a.lamports_changed())
397-
.unwrap_or(true);
381+
}
382+
}
398383

399-
if balance_changed {
400-
executed.execution_details.status =
401-
Err(TransactionError::UnbalancedTransaction);
402-
let msg = format!(
403-
"Confined account {pubkey} has been illegally modified"
404-
);
405-
logs.push(msg);
406-
break;
407-
}
384+
// A utility to extract the rollback lamports of the feepayer
385+
fn rollback_feepayer_lamports(rollback: &RollbackAccounts) -> u64 {
386+
match rollback {
387+
RollbackAccounts::FeePayerOnly { fee_payer_account } => {
388+
fee_payer_account.lamports()
389+
}
390+
RollbackAccounts::SameNonceAndFeePayer { nonce } => {
391+
nonce.account().lamports()
408392
}
393+
RollbackAccounts::SeparateNonceAndFeePayer {
394+
fee_payer_account,
395+
..
396+
} => fee_payer_account.lamports(),
409397
}
410398
}

magicblock-processor/tests/fees.rs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{collections::HashSet, time::Duration};
22

33
use guinea::GuineaInstruction;
4+
use magicblock_core::traits::AccountsBank;
45
use solana_account::{ReadableAccount, WritableAccount};
56
use solana_keypair::Keypair;
67
use solana_program::{
@@ -50,7 +51,7 @@ async fn test_insufficient_fee() {
5051
let env = ExecutionTestEnv::new();
5152
let mut payer = env.get_payer();
5253
payer.set_lamports(ExecutionTestEnv::BASE_FEE - 1);
53-
payer.commit();
54+
payer.commmit();
5455

5556
let (ix, _) =
5657
setup_guinea_instruction(&env, &GuineaInstruction::PrintSizes, false);
@@ -104,7 +105,7 @@ async fn test_non_delegated_payer_rejection() {
104105
let mut payer = env.get_payer();
105106
payer.set_delegated(false); // Mark the payer as not delegated
106107
let fee_payer_initial_balance = payer.lamports();
107-
payer.commit();
108+
payer.commmit();
108109

109110
let (ix, _) =
110111
setup_guinea_instruction(&env, &GuineaInstruction::PrintSizes, false);
@@ -132,7 +133,7 @@ async fn test_escrowed_payer_success() {
132133
payer.set_lamports(ExecutionTestEnv::BASE_FEE - 1);
133134
payer.set_delegated(false);
134135
let escrow = ephemeral_balance_pda_from_payer(&payer.pubkey);
135-
payer.commit();
136+
payer.commmit();
136137

137138
env.fund_account(escrow, LAMPORTS_PER_SOL); // Fund the escrow PDA
138139

@@ -219,7 +220,7 @@ async fn test_escrow_charged_for_failed_transaction() {
219220
payer.set_lamports(0);
220221
payer.set_delegated(false);
221222
let escrow = ephemeral_balance_pda_from_payer(&payer.pubkey);
222-
payer.commit();
223+
payer.commmit();
223224
let account = env
224225
.create_account_with_config(LAMPORTS_PER_SOL, 0, guinea::ID) // Account with no data
225226
.pubkey();
@@ -265,7 +266,7 @@ async fn test_transaction_gasless_mode() {
265266
payer.set_lamports(1); // Not enough to cover standard fee
266267
payer.set_delegated(false); // Explicitly set the payer as NON-delegated.
267268
let initial_balance = payer.lamports();
268-
payer.commit();
269+
payer.commmit();
269270

270271
let ix = Instruction::new_with_bincode(
271272
guinea::ID,
@@ -311,7 +312,7 @@ async fn test_transaction_gasless_mode_with_not_existing_account() {
311312
payer.set_lamports(1); // Not enough to cover standard fee
312313
payer.set_delegated(false); // Explicitly set the payer as NON-delegated.
313314
let initial_balance = payer.lamports();
314-
payer.commit();
315+
payer.commmit();
315316

316317
let ix = Instruction::new_with_bincode(
317318
guinea::ID,
@@ -350,3 +351,51 @@ async fn test_transaction_gasless_mode_with_not_existing_account() {
350351
"payer balance should not change in gasless mode"
351352
);
352353
}
354+
355+
/// Verifies that in zero-fee ("gasless") mode, transactions are processed
356+
/// successfully even when the fee payer does not exists.
357+
#[tokio::test]
358+
async fn test_transaction_gasless_mode_not_existing_feepayer() {
359+
// Initialize the environment with a base fee of 0.
360+
let env = ExecutionTestEnv::new_with_config(0);
361+
let payer = env.get_payer().pubkey;
362+
env.accountsdb.remove_account(&payer);
363+
364+
// Simple noop instruction that does not touch the fee payer account
365+
let ix = Instruction::new_with_bincode(
366+
guinea::ID,
367+
&GuineaInstruction::PrintSizes,
368+
vec![],
369+
);
370+
let txn = env.build_transaction(&[ix]);
371+
let signature = txn.signatures[0];
372+
373+
// In a normal fee-paying mode, this execution would fail.
374+
env.execute_transaction(txn)
375+
.await
376+
.expect("transaction should succeed in gasless mode");
377+
378+
// Verify the transaction was fully processed and broadcast successfully.
379+
let status = env
380+
.dispatch
381+
.transaction_status
382+
.recv_timeout(Duration::from_millis(100))
383+
.expect("should receive a transaction status update");
384+
385+
assert_eq!(status.signature, signature);
386+
assert!(
387+
status.result.result.is_ok(),
388+
"Transaction execution should be successful"
389+
);
390+
391+
// Verify that the payer balance is zero (or doesn't exist)
392+
let final_balance = env
393+
.accountsdb
394+
.get_account(&payer)
395+
.unwrap_or_default()
396+
.lamports();
397+
assert_eq!(
398+
final_balance, 0,
399+
"payer balance of a not existing feepayer should be 0 in gasless mode"
400+
);
401+
}

magicblock-processor/tests/security.rs

Lines changed: 0 additions & 122 deletions
This file was deleted.

test-integration/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ rayon = "1.10.0"
7474
schedulecommit-client = { path = "schedulecommit/client" }
7575
serde = "1.0.217"
7676
serial_test = "3.2.0"
77-
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "57158728" }
77+
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "1beed4c" }
7878
solana-loader-v2-interface = "2.2"
7979
solana-loader-v3-interface = "4.0"
8080
solana-loader-v4-interface = "2.1"
@@ -106,5 +106,5 @@ url = "2.5.0"
106106
# and we use protobuf-src v2.1.1. Otherwise compilation fails
107107
solana-storage-proto = { path = "../storage-proto" }
108108
# same reason as above
109-
rocksdb = { git = "https://github.com/magicblock-labs/rust-rocksdb.git", rev = "6d975197" }
110-
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "57158728" }
109+
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "1beed4c" }
110+
rocksdb = { git = "https://github.com/magicblock-labs/rust-rocksdb.git", rev = "6d975197" }

0 commit comments

Comments
 (0)