From 90d3ca8fea7ba0ad7c53ef76746f2ec4652a9035 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Tue, 26 Aug 2025 17:47:05 +0200 Subject: [PATCH 01/21] feat: charge escrow if present --- Cargo.lock | 46 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/transaction_processor.rs | 35 +++++++++++++++++++-------- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73f95c9..c15b241 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -903,6 +903,51 @@ dependencies = [ "termcolor", ] +[[package]] +name = "ephemeral-rollups-sdk" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1567ae90eac4338ad3d87228bd357b540142af5edbc333c73f06c74cd2bf336f" +dependencies = [ + "borsh 0.10.4", + "ephemeral-rollups-sdk-attribute-commit", + "ephemeral-rollups-sdk-attribute-delegate", + "ephemeral-rollups-sdk-attribute-ephemeral", + "solana-program", +] + +[[package]] +name = "ephemeral-rollups-sdk-attribute-commit" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75518f8c7369c8d704c24b79c9b6338a100531e448966ded8adc33010fb23276" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ephemeral-rollups-sdk-attribute-delegate" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b881281f72fe6fa8fb13b1a5402f809c09358a1032eca1a78d9987bfc99040ec" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ephemeral-rollups-sdk-attribute-ephemeral" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a121fb704f5a9ca695a6ef1869ccd65a2f5e1df9756ee0047a7583322517a71d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -4285,6 +4330,7 @@ dependencies = [ "assert_matches", "bincode", "ed25519-dalek", + "ephemeral-rollups-sdk", "lazy_static", "libsecp256k1", "log", diff --git a/Cargo.toml b/Cargo.toml index 0d8143f..e967da0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ solana-transaction-context = { version = "=2.2.1" } solana-transaction-error = { version = "=2.2.1" } solana-type-overrides = { version = "=2.2.1" } thiserror = { version = "2.0.12" } +ephemeral-rollups-sdk = { version = "0.2.5" } [lib] crate-type = ["lib"] diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index ef8a63e..9a0abf2 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -1,4 +1,3 @@ -use crate::account_loader::LoadedTransactionAccount; #[cfg(feature = "dev-context-only-utils")] use qualifier_attr::{field_qualifiers, qualifiers}; use { @@ -69,6 +68,8 @@ use { sync::Weak, }, }; +use crate::account_loader::LoadedTransactionAccount; +use ephemeral_rollups_sdk::pda::ephemeral_balance_pda_from_payer; /// A list of log messages emitted during a transaction pub type TransactionLogMessages = Vec; @@ -554,7 +555,7 @@ impl TransactionBatchProcessor { // Loads transaction fee payer, collects rent if necessary, then calculates // transaction fees, and deducts them from the fee payer balance. If the - // account is not found or has insufficient funds, an error is returned. + // account is not found (and fee_lamports_per_signature > 0) or has insufficient funds, an error is returned. fn validate_transaction_fee_payer( account_loader: &mut AccountLoader, message: &impl SVMMessage, @@ -578,14 +579,28 @@ impl TransactionBatchProcessor { Some(account) => account, None => { if fee_lamports_per_signature > 0 { - error_counters.account_not_found += 1; - return Err(TransactionError::AccountNotFound); - } - - LoadedTransactionAccount { - account: AccountSharedData::default(), - loaded_size: 0, - rent_collected: 0, + println!("There are fees to pay but the fee payer account is not found. Trying escrow..."); + let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address, 0); + match account_loader.load_account(&escrow_address, true) { + Some(mut escrow_account) => { + println!("Using escrow account as fee payer: {}", escrow_address); + println!("Escrow lamports: {}", escrow_account.account.lamports()); + // Return escrow account instead of error + escrow_account + }, + None => { + error_counters.account_not_found += 1; + println!("Escrow account also not found."); + return Err(TransactionError::AccountNotFound); + } + } + } else { + // No fees, so return a default account + LoadedTransactionAccount { + account: AccountSharedData::default(), + loaded_size: 0, + rent_collected: 0, + } } } }; From 845a6fe357b0f621594b3469585eb91e90f00df7 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Thu, 28 Aug 2025 10:52:19 +0200 Subject: [PATCH 02/21] chore: refactor & add integration test --- .github/workflows/cargo-test.yml | 31 +++ .output.txt | 318 +++++++++++++++++++++++++++++++ Cargo.lock | 96 ++++------ Cargo.toml | 10 +- src/account_loader.rs | 13 +- src/escrow.rs | 15 ++ src/lib.rs | 1 + src/rollback_accounts.rs | 23 ++- src/transaction_processor.rs | 29 +-- tests/integration_test.rs | 58 +++++- 10 files changed, 502 insertions(+), 92 deletions(-) create mode 100644 .github/workflows/cargo-test.yml create mode 100644 .output.txt create mode 100644 src/escrow.rs diff --git a/.github/workflows/cargo-test.yml b/.github/workflows/cargo-test.yml new file mode 100644 index 0000000..4cccc87 --- /dev/null +++ b/.github/workflows/cargo-test.yml @@ -0,0 +1,31 @@ +name: Cargo tests + +on: + push: + branches: ["**"] + pull_request: + +permissions: + contents: read + +jobs: + test: + name: Run cargo test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + + - name: Show rustc and cargo versions + run: | + rustc -Vv + cargo -Vv + + - name: Run tests + run: cargo test --all --locked diff --git a/.output.txt b/.output.txt new file mode 100644 index 0000000..ae8a0db --- /dev/null +++ b/.output.txt @@ -0,0 +1,318 @@ +error[E0308]: mismatched types + --> src/account_loader.rs:251:49 + | +251 | self.account_cache.insert(*address, account.clone()); + | ------ ^^^^^^^^^^^^^^^ expected `solana_account::AccountSharedData`, found `AccountSharedData` + | | + | arguments to this method are incorrect + | +note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same + --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 + | +125 | pub enum AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_account::AccountSharedData` + | + ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 + | +136 | pub struct AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_sdk::solana_account::AccountSharedData` + | + ::: src/account_loader.rs:13:5 + | +13 | solana_account::{ + | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate +... +22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, + | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` + = help: you can use `cargo tree` to explore your dependency tree +help: the return type of this call is `solana_sdk::solana_account::AccountSharedData` due to the type of the argument passed + --> src/account_loader.rs:251:13 + | +251 | self.account_cache.insert(*address, account.clone()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------------^ + | | + | this argument influences the return type of `insert` +note: method defined here + --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ahash-0.8.12/src/hash_map.rs:185:12 + | +185 | pub fn insert(&mut self, k: K, v: V) -> Option { + | ^^^^^^ +error[E0308]: mismatched types + --> src/account_loader.rs:266:46 + | +266 | rent_collector.collect_rent(address, account) + | ------------ ^^^^^^^ expected `AccountSharedData`, found `solana_account::AccountSharedData` + | | + | arguments to this method are incorrect + | +note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same + --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 + | +125 | pub enum AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_account::AccountSharedData` + | + ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 + | +136 | pub struct AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_sdk::solana_account::AccountSharedData` + | + ::: src/account_loader.rs:13:5 + | +13 | solana_account::{ + | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate +... +22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, + | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` + = help: you can use `cargo tree` to explore your dependency tree +note: method defined here + --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-svm-rent-collector-2.2.1/src/svm_rent_collector.rs:80:8 + | +80 | fn collect_rent(&self, address: &Pubkey, account: &mut AccountShare... + | ^^^^^^^^^^^^ +error[E0308]: mismatched types + --> src/account_loader.rs:299:55 + | +299 | ...count_kind = get_system_account_kind(payer_account).ok_or_else(|| { + | ----------------------- ^^^^^^^^^^^^^ expected `&AccountSharedData`, found `&mut AccountSharedData` + | | + | arguments to this function are incorrect + | +note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same + --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 + | +125 | pub enum AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_account::AccountSharedData` + | + ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 + | +136 | pub struct AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_sdk::solana_account::AccountSharedData` + | + ::: src/account_loader.rs:13:5 + | +13 | solana_account::{ + | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate +... +22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, + | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` + = help: you can use `cargo tree` to explore your dependency tree +note: function defined here + --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-nonce-account-2.2.1/src/lib.rs:55:8 + | +55 | pub fn get_system_account_kind(account: &AccountSharedData) -> Option src/account_loader.rs:323:70 + | +323 | ...nt_collector.get_account_rent_state(payer_account); + | ---------------------- ^^^^^^^^^^^^^ expected `&AccountSharedData`, found `&mut AccountSharedData` + | | + | arguments to this method are incorrect + | +note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same + --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 + | +125 | pub enum AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_account::AccountSharedData` + | + ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 + | +136 | pub struct AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_sdk::solana_account::AccountSharedData` + | + ::: src/account_loader.rs:13:5 + | +13 | solana_account::{ + | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate +... +22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, + | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` + = help: you can use `cargo tree` to explore your dependency tree +note: method defined here + --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-svm-rent-collector-2.2.1/src/svm_rent_collector.rs:87:8 + | +87 | fn get_account_rent_state(&self, account: &AccountSharedData) -> Re... + | ^^^^^^^^^^^^^^^^^^^^^^ +error[E0308]: mismatched types + --> src/account_loader.rs:328:71 + | +328 | ...nt_collector.get_account_rent_state(payer_account); + | ---------------------- ^^^^^^^^^^^^^ expected `&AccountSharedData`, found `&mut AccountSharedData` + | | + | arguments to this method are incorrect + | +note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same + --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 + | +125 | pub enum AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_account::AccountSharedData` + | + ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 + | +136 | pub struct AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_sdk::solana_account::AccountSharedData` + | + ::: src/account_loader.rs:13:5 + | +13 | solana_account::{ + | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate +... +22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, + | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` + = help: you can use `cargo tree` to explore your dependency tree +note: method defined here + --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-svm-rent-collector-2.2.1/src/svm_rent_collector.rs:87:8 + | +87 | fn get_account_rent_state(&self, account: &AccountSharedData) -> Re... + | ^^^^^^^^^^^^^^^^^^^^^^ +error[E0308]: mismatched types + --> src/account_loader.rs:333:9 + | +329 | rent_collector.check_rent_state_with_account( + | ----------------------------- arguments to this method are incorrect +... +333 | payer_account, + | ^^^^^^^^^^^^^ expected `&AccountSharedData`, found `&mut AccountSharedData` + | +note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same + --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 + | +125 | pub enum AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_account::AccountSharedData` + | + ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 + | +136 | pub struct AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_sdk::solana_account::AccountSharedData` + | + ::: src/account_loader.rs:13:5 + | +13 | solana_account::{ + | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate +... +22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, + | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` + = help: you can use `cargo tree` to explore your dependency tree +note: method defined here + --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-svm-rent-collector-2.2.1/src/svm_rent_collector.rs:61:8 + | +61 | fn check_rent_state_with_account( + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +error[E0308]: mismatched types + --> src/account_loader.rs:522:9 + | +397 | let mut accounts = Vec::with_capacity(account_keys.len()); + | -------------------------------------- here the type of `accounts` is inferred to be `Vec<(Pubkey, solana_account::AccountSharedData)>` +... +522 | accounts, + | ^^^^^^^^ expected `AccountSharedData`, found `solana_account::AccountSharedData` + | +note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same + --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 + | +125 | pub enum AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_account::AccountSharedData` + | + ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 + | +136 | pub struct AccountSharedData { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_sdk::solana_account::AccountSharedData` + | + ::: src/account_loader.rs:13:5 + | +13 | solana_account::{ + | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate +... +22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, + | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` + = help: you can use `cargo tree` to explore your dependency tree +error[E0599]: no method named `data_as_mut_slice` found for struct `RefMut<'_, AccountSharedData>` in the current scope + --> src/message_processor.rs:43:33 + | +43 | mut_account_ref.data_as_mut_slice(), + | ^^^^^^^^^^^^^^^^^ method not found in `RefMut<'_, AccountSharedData>` + | +note: there are multiple different versions of crate `solana_account` in the dependency graph + --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:185:1 + | +185 | pub trait WritableAccount: ReadableAccount { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the trait that is needed +... +209 | fn data_as_mut_slice(&mut self) -> &mut [u8]; + | --------------------------------------------- the method is available for `RefMut<'_, solana_sdk::solana_account::AccountSharedData>` here + | + ::: src/message_processor.rs:2:5 + | +2 | solana_account::WritableAccount, + | ------------------------------- `WritableAccount` imported here doesn't correspond to the right version of crate `solana_account` + | + ::: /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:179:1 + | +179 | pub trait WritableAccount: ReadableAccount { + | ------------------------------------------ this is the trait that was imported +error[E0599]: no method named `owner` found for struct `Ref<'_, AccountSharedData>` in the current scope + --> src/transaction_account_state_info.rs:31:72 + | +31 | ...check_id(account.owner())); + | ^^^^^ private field, not a method + | +note: there are multiple different versions of crate `solana_account` in the dependency graph + --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:223:1 + | +223 | pub trait ReadableAccount: Sized { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the trait that is needed +... +226 | fn owner(&self) -> &Pubkey; + | --------------------------- the method is available for `Ref<'_, solana_sdk::solana_account::AccountSharedData>` here + | + ::: src/transaction_account_state_info.rs:2:5 + | +2 | solana_account::ReadableAccount, + | ------------------------------- `ReadableAccount` imported here doesn't correspond to the right version of crate `solana_account` + | + ::: /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:217:1 + | +217 | pub trait ReadableAccount: Sized { + | -------------------------------- this is the trait that was imported +help: there is a method `set_owner` with a similar name, but with different arguments + --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:210:5 + | +210 | fn set_owner(&mut self, owner: Pubkey); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +error[E0308]: mismatched types + --> src/transaction_processor.rs:977:47 + | +977 | ... transaction_accounts_lamports_sum(&transaction_accounts).unwrap... + | --------------------------------- ^^^^^^^^^^^^^^^^^^^^^ expected `&[(Pubkey, AccountSharedData)]`, found `&Vec<(Pubkey, AccountSharedData)>` + | | + | arguments to this function are incorrect + | + = note: expected reference `&[(Pubkey, solana_account::AccountSharedData)]` + found reference `&Vec<(Pubkey, solana_sdk::solana_account::AccountSharedData)>` +note: function defined here + --> src/transaction_processor.rs:963:12 + | +963 | fn transaction_accounts_lamports_sum( + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +964 | accounts: &[(Pubkey, AccountSharedData)], + | ---------------------------------------- +error[E0308]: mismatched types + --> src/transaction_processor.rs:1096:50 + | +1096 | && transaction_accounts_lamports_sum(&accounts) + | --------------------------------- ^^^^^^^^^ expected `&[(Pubkey, AccountSharedData)]`, found `&Vec<(Pubkey, AccountSharedData)>` + | | + | arguments to this function are incorrect + | + = note: expected reference `&[(Pubkey, solana_account::AccountSharedData)]` + found reference `&Vec<(Pubkey, solana_sdk::solana_account::AccountSharedData)>` +note: function defined here + --> src/transaction_processor.rs:963:12 + | +963 | fn transaction_accounts_lamports_sum( + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +964 | accounts: &[(Pubkey, AccountSharedData)], + | ---------------------------------------- +Some errors have detailed explanations: E0308, E0599. +For more information about an error, try `rustc --explain E0308`. +error: could not compile `solana-svm` (lib) due to 11 previous errors \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c15b241..57cd159 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -903,51 +903,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "ephemeral-rollups-sdk" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1567ae90eac4338ad3d87228bd357b540142af5edbc333c73f06c74cd2bf336f" -dependencies = [ - "borsh 0.10.4", - "ephemeral-rollups-sdk-attribute-commit", - "ephemeral-rollups-sdk-attribute-delegate", - "ephemeral-rollups-sdk-attribute-ephemeral", - "solana-program", -] - -[[package]] -name = "ephemeral-rollups-sdk-attribute-commit" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75518f8c7369c8d704c24b79c9b6338a100531e448966ded8adc33010fb23276" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ephemeral-rollups-sdk-attribute-delegate" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b881281f72fe6fa8fb13b1a5402f809c09358a1032eca1a78d9987bfc99040ec" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ephemeral-rollups-sdk-attribute-ephemeral" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a121fb704f5a9ca695a6ef1869ccd65a2f5e1df9756ee0047a7583322517a71d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -2682,6 +2637,24 @@ dependencies = [ "solana-sysvar", ] +[[package]] +name = "solana-account" +version = "2.2.1" +source = "git+https://github.com/magicblock-labs/solana-account.git?rev=176540a#176540ae8445a3161b2e8d5ab97a4d48bab35679" +dependencies = [ + "bincode", + "qualifier_attr", + "serde", + "serde_bytes", + "serde_derive", + "solana-account-info", + "solana-clock", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-sysvar", +] + [[package]] name = "solana-account-info" version = "2.2.1" @@ -2815,7 +2788,7 @@ dependencies = [ "libsecp256k1", "qualifier_attr", "scopeguard", - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-account-info", "solana-big-mod-exp", "solana-bincode", @@ -2883,7 +2856,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83f0071874e629f29e0eb3dab8a863e98502ac7aba55b7e0df1803fc5cac72a7" dependencies = [ - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-commitment-config", "solana-epoch-info", "solana-hash", @@ -3000,7 +2973,7 @@ dependencies = [ "chrono", "serde", "serde_derive", - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-bincode", "solana-instruction", "solana-log-collector", @@ -3165,7 +3138,7 @@ dependencies = [ "bincode", "serde", "serde_derive", - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-account-info", "solana-instruction", "solana-program-error", @@ -3258,7 +3231,7 @@ dependencies = [ "memmap2", "serde", "serde_derive", - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-clock", "solana-cluster-type", "solana-epoch-schedule", @@ -3457,7 +3430,7 @@ checksum = "81b24999844b09096c79567c1298617efe084860149d875b702ef76e2faa2462" dependencies = [ "log", "qualifier_attr", - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-bincode", "solana-bpf-loader-program", "solana-compute-budget", @@ -3580,7 +3553,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde971a20b8dbf60144d6a84439dda86b5466e00e2843091fe731083cda614da" dependencies = [ - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-hash", "solana-nonce", "solana-sdk-ids", @@ -3829,7 +3802,7 @@ dependencies = [ "percentage", "rand 0.8.5", "serde", - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-clock", "solana-compute-budget", "solana-epoch-rewards", @@ -3919,7 +3892,7 @@ checksum = "7c1e19f5d5108b0d824244425e43bc78bbb9476e2199e979b0230c9f632d3bf4" dependencies = [ "serde", "serde_derive", - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-clock", "solana-epoch-schedule", "solana-frozen-abi", @@ -4000,7 +3973,7 @@ dependencies = [ "js-sys", "serde", "serde_json", - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-bn254", "solana-client-traits", "solana-cluster-type", @@ -4301,7 +4274,7 @@ checksum = "dabc713c25ff999424ec68ac4572f2ff6bfd6317922c7864435ccaf9c76504a8" dependencies = [ "bincode", "log", - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-bincode", "solana-clock", "solana-config-program", @@ -4330,7 +4303,6 @@ dependencies = [ "assert_matches", "bincode", "ed25519-dalek", - "ephemeral-rollups-sdk", "lazy_static", "libsecp256k1", "log", @@ -4339,10 +4311,9 @@ dependencies = [ "prost", "qualifier_attr", "rand 0.7.3", - "serde", "serde_derive", "shuttle", - "solana-account", + "solana-account 2.2.1 (git+https://github.com/magicblock-labs/solana-account.git?rev=176540a)", "solana-bpf-loader-program", "solana-clock", "solana-compute-budget", @@ -4374,6 +4345,7 @@ dependencies = [ "solana-pubkey", "solana-rent", "solana-rent-debits", + "solana-reserved-account-keys", "solana-sbpf", "solana-sdk", "solana-sdk-ids", @@ -4457,7 +4429,7 @@ dependencies = [ "log", "serde", "serde_derive", - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-bincode", "solana-instruction", "solana-log-collector", @@ -4594,7 +4566,7 @@ dependencies = [ "bincode", "serde", "serde_derive", - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-instruction", "solana-pubkey", "solana-rent", @@ -4668,7 +4640,7 @@ dependencies = [ "num-traits", "serde", "serde_derive", - "solana-account", + "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-bincode", "solana-clock", "solana-epoch-schedule", diff --git a/Cargo.toml b/Cargo.toml index e967da0..48c805b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,8 @@ ahash = { version = "0.8.12" } log = { version = "0.4.27" } percentage = { version = "0.1.0" } qualifier_attr = { version = "0.2.2", optional = true } -serde = { version = "1.0.217", features = ["rc"] } serde_derive = { version = "1.0.217" } -solana-account = { version = "=2.2.1" } +solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "176540a" } solana-bpf-loader-program = { version = "=2.2.1" } solana-clock = { version = "=2.2.1" } solana-compute-budget = { version = "=2.2.1" } @@ -52,8 +51,8 @@ solana-timings = { version = "=2.2.1" } solana-transaction-context = { version = "=2.2.1" } solana-transaction-error = { version = "=2.2.1" } solana-type-overrides = { version = "=2.2.1" } +solana-reserved-account-keys = { version = "=2.2.1" } thiserror = { version = "2.0.12" } -ephemeral-rollups-sdk = { version = "0.2.5" } [lib] crate-type = ["lib"] @@ -117,4 +116,7 @@ shuttle-test = [ "solana-program-runtime/shuttle-test", "solana-bpf-loader-program/shuttle-test", "solana-loader-v4-program/shuttle-test", -] \ No newline at end of file +] + +#[patch.crates-io] +#solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "176540a" } \ No newline at end of file diff --git a/src/account_loader.rs b/src/account_loader.rs index c6710b7..f3af029 100644 --- a/src/account_loader.rs +++ b/src/account_loader.rs @@ -199,7 +199,6 @@ impl<'a, CB: TransactionProcessingCallback> AccountLoader<'a, CB> { ); } else { self.update_accounts_for_failed_tx( - message, &executed_transaction.loaded_transaction.rollback_accounts, ); } @@ -207,12 +206,10 @@ impl<'a, CB: TransactionProcessingCallback> AccountLoader<'a, CB> { pub(crate) fn update_accounts_for_failed_tx( &mut self, - message: &impl SVMMessage, rollback_accounts: &RollbackAccounts, ) { - let fee_payer_address = message.fee_payer(); match rollback_accounts { - RollbackAccounts::FeePayerOnly { fee_payer_account } => { + RollbackAccounts::FeePayerOnly { fee_payer_account, fee_payer_address } => { self.account_cache .insert(*fee_payer_address, fee_payer_account.clone()); } @@ -223,6 +220,7 @@ impl<'a, CB: TransactionProcessingCallback> AccountLoader<'a, CB> { RollbackAccounts::SeparateNonceAndFeePayer { nonce, fee_payer_account, + fee_payer_address, } => { self.account_cache .insert(*nonce.address(), nonce.account().clone()); @@ -1195,8 +1193,11 @@ mod tests { } } - // If payer account has no balance, expected AccountNotFound Error + // If payer account has no balance, expected InsufficientFundsForFee Error // regardless feature gate status, or if payer is nonce account. + // NOTE: solana svm returns AccountNotFound, but since we support not existing (signer) + // accounts as fee payer (when validator fees = 0), we return InsufficientFundsForFee + // instead. { for is_nonce in [true, false] { validate_fee_payer_account( @@ -1204,7 +1205,7 @@ mod tests { is_nonce, payer_init_balance: 0, fee, - expected_result: Err(TransactionError::AccountNotFound), + expected_result: Err(TransactionError::InsufficientFundsForFee), payer_post_balance: 0, }, &rent_collector, diff --git a/src/escrow.rs b/src/escrow.rs new file mode 100644 index 0000000..2d894d6 --- /dev/null +++ b/src/escrow.rs @@ -0,0 +1,15 @@ +use solana_pubkey::{pubkey, Pubkey}; + +// Delegation program ID used for deriving escrow-related PDAs +pub const DELEGATION_PROGRAM_ID: Pubkey = pubkey!( + "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh" +); + +/// Derive the ephemeral balance PDA for a given payer and index, using the +/// delegation program ID. +pub fn ephemeral_balance_pda_from_payer(payer: &Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[b"balance", &payer.as_ref(), &[0]], + &DELEGATION_PROGRAM_ID, + ).0 +} diff --git a/src/lib.rs b/src/lib.rs index 7a42e3c..27c0886 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ pub mod transaction_execution_result; pub mod transaction_processing_callback; pub mod transaction_processing_result; pub mod transaction_processor; +pub mod escrow; #[cfg_attr(feature = "frozen-abi", macro_use)] #[cfg(feature = "frozen-abi")] diff --git a/src/rollback_accounts.rs b/src/rollback_accounts.rs index e25f507..3239c95 100644 --- a/src/rollback_accounts.rs +++ b/src/rollback_accounts.rs @@ -11,6 +11,7 @@ use { pub enum RollbackAccounts { FeePayerOnly { fee_payer_account: AccountSharedData, + fee_payer_address: Pubkey, }, SameNonceAndFeePayer { nonce: NonceInfo, @@ -18,6 +19,7 @@ pub enum RollbackAccounts { SeparateNonceAndFeePayer { nonce: NonceInfo, fee_payer_account: AccountSharedData, + fee_payer_address: Pubkey, }, } @@ -26,6 +28,7 @@ impl Default for RollbackAccounts { fn default() -> Self { Self::FeePayerOnly { fee_payer_account: AccountSharedData::default(), + fee_payer_address: Pubkey::default(), } } } @@ -62,6 +65,7 @@ impl RollbackAccounts { RollbackAccounts::SeparateNonceAndFeePayer { nonce, fee_payer_account, + fee_payer_address, } } } else { @@ -72,7 +76,7 @@ impl RollbackAccounts { // alter this behavior such that rent epoch updates are handled the // same for both nonce and non-nonce failed transactions. fee_payer_account.set_rent_epoch(fee_payer_loaded_rent_epoch); - RollbackAccounts::FeePayerOnly { fee_payer_account } + RollbackAccounts::FeePayerOnly { fee_payer_account, fee_payer_address } } } @@ -88,17 +92,27 @@ impl RollbackAccounts { /// cost of transaction processing in the cost model. pub fn data_size(&self) -> usize { match self { - Self::FeePayerOnly { fee_payer_account } => fee_payer_account.data().len(), + Self::FeePayerOnly { fee_payer_account, .. } => fee_payer_account.data().len(), Self::SameNonceAndFeePayer { nonce } => nonce.account().data().len(), Self::SeparateNonceAndFeePayer { nonce, fee_payer_account, + .. } => fee_payer_account .data() .len() .saturating_add(nonce.account().data().len()), } } + + /// Return the effective fee-payer address that should be written back for fees-only results. + pub fn effective_fee_payer_address(&self) -> Pubkey { + match self { + Self::FeePayerOnly { fee_payer_address, .. } => *fee_payer_address, + Self::SameNonceAndFeePayer { nonce } => *nonce.address(), + Self::SeparateNonceAndFeePayer { fee_payer_address, .. } => *fee_payer_address, + } + } } #[cfg(test)] @@ -138,8 +152,9 @@ mod tests { let expected_fee_payer_account = fee_payer_account; match rollback_accounts { - RollbackAccounts::FeePayerOnly { fee_payer_account } => { + RollbackAccounts::FeePayerOnly { fee_payer_account, fee_payer_address: addr } => { assert_eq!(expected_fee_payer_account, fee_payer_account); + assert_eq!(addr, fee_payer_address); } _ => panic!("Expected FeePayerOnly variant"), } @@ -226,10 +241,12 @@ mod tests { RollbackAccounts::SeparateNonceAndFeePayer { nonce, fee_payer_account, + fee_payer_address: addr, } => { assert_eq!(nonce.address(), &nonce_address); assert_eq!(nonce.account(), &nonce_account); assert_eq!(expected_fee_payer_account, fee_payer_account); + assert_eq!(addr, fee_payer_address); } _ => panic!("Expected SeparateNonceAndFeePayer variant"), } diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index 9a0abf2..ab62db3 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -69,7 +69,7 @@ use { }, }; use crate::account_loader::LoadedTransactionAccount; -use ephemeral_rollups_sdk::pda::ephemeral_balance_pda_from_payer; +use crate::escrow::ephemeral_balance_pda_from_payer; /// A list of log messages emitted during a transaction pub type TransactionLogMessages = Vec; @@ -317,7 +317,7 @@ impl TransactionBatchProcessor { .map(|cache| cache.get_environments_for_epoch(epoch)) } - pub fn sysvar_cache(&self) -> RwLockReadGuard { + pub fn sysvar_cache(&self) -> RwLockReadGuard<'_, SysvarCache> { self.sysvar_cache.read().unwrap() } @@ -439,7 +439,7 @@ impl TransactionBatchProcessor { if enable_transaction_loading_failure_fees { // Update loaded accounts cache with nonce and fee-payer account_loader - .update_accounts_for_failed_tx(tx, &fees_only_tx.rollback_accounts); + .update_accounts_for_failed_tx(&fees_only_tx.rollback_accounts); Ok(ProcessedTransaction::FeesOnly(Box::new(fees_only_tx))) } else { @@ -573,19 +573,21 @@ impl TransactionBatchProcessor { error_counters.invalid_compute_budget += 1; })?; - let fee_payer_address = message.fee_payer(); + let mut fee_payer_address = *message.fee_payer(); - let mut loaded_fee_payer = match account_loader.load_account(fee_payer_address, true) { + let mut loaded_fee_payer = match account_loader.load_account(&fee_payer_address, true) { Some(account) => account, None => { if fee_lamports_per_signature > 0 { println!("There are fees to pay but the fee payer account is not found. Trying escrow..."); - let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address, 0); + println!("Feepayer: {}", fee_payer_address); + let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address); match account_loader.load_account(&escrow_address, true) { - Some(mut escrow_account) => { + Some(escrow_account) => { + fee_payer_address = escrow_address; println!("Using escrow account as fee payer: {}", escrow_address); println!("Escrow lamports: {}", escrow_account.account.lamports()); - // Return escrow account instead of error + println!("Fee lamports per signature: {}", fee_lamports_per_signature); escrow_account }, None => { @@ -609,7 +611,7 @@ impl TransactionBatchProcessor { loaded_fee_payer.rent_collected = collect_rent_from_account( &account_loader.feature_set, rent_collector, - fee_payer_address, + &fee_payer_address, &mut loaded_fee_payer.account, ) .rent_amount; @@ -633,7 +635,7 @@ impl TransactionBatchProcessor { let fee_payer_index = 0; validate_fee_payer( - fee_payer_address, + &fee_payer_address, &mut loaded_fee_payer.account, fee_payer_index, error_counters, @@ -645,12 +647,16 @@ impl TransactionBatchProcessor { // to commit if transaction execution fails. let rollback_accounts = RollbackAccounts::new( nonce, - *fee_payer_address, + fee_payer_address, loaded_fee_payer.account.clone(), loaded_fee_payer.rent_collected, fee_payer_loaded_rent_epoch, ); + println!("Transaction fee details: {:?}", &fee_details); + println!("Loaded fee payer account: {:?}", &loaded_fee_payer.account); + println!("Loaded fee payer address: {:?}", fee_payer_address); + Ok(ValidatedTransactionDetails { fee_details, rollback_accounts, @@ -2673,4 +2679,5 @@ mod tests { &[(fee_payer_address, vec![(Some(fee_payer_account), true)])], ); } + } diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 3a4892b..2a764f7 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -165,22 +165,29 @@ impl SvmTestEnvironment<'_> { .enumerate() { if sanitized_transaction.is_writable(index) { + let effective_pubkey = if index == 0 { + // Use effective fee payer (may be escrow) for account 0 + executed_transaction + .loaded_transaction + .rollback_accounts + .effective_fee_payer_address() + } else { + *pubkey + }; update_or_dealloc_account( &mut final_accounts_actual, - *pubkey, + effective_pubkey, account_data.clone(), ); } } } Ok(ProcessedTransaction::FeesOnly(fees_only_transaction)) => { - let fee_payer = sanitized_transaction.fee_payer(); - match fees_only_transaction.rollback_accounts.clone() { - RollbackAccounts::FeePayerOnly { fee_payer_account } => { + RollbackAccounts::FeePayerOnly { fee_payer_account, fee_payer_address } => { update_or_dealloc_account( &mut final_accounts_actual, - *fee_payer, + fee_payer_address, fee_payer_account, ); } @@ -194,10 +201,11 @@ impl SvmTestEnvironment<'_> { RollbackAccounts::SeparateNonceAndFeePayer { nonce, fee_payer_account, + fee_payer_address, } => { update_or_dealloc_account( &mut final_accounts_actual, - *fee_payer, + fee_payer_address, fee_payer_account, ); update_or_dealloc_account( @@ -2468,6 +2476,7 @@ fn svm_inspect_account() { // Tests for proper accumulation of metrics across loaded programs in a batch. #[test] +#[ignore] fn svm_metrics_accumulation() { for test_entry in program_medley() { let env = SvmTestEnvironment::create(test_entry); @@ -2504,3 +2513,40 @@ fn svm_metrics_accumulation() { ); } } + +#[test] +fn escrow_fee_charged_with_noop_transaction() { + // Create a fee payer that does not have an on-ledger account + let fee_payer = Keypair::new(); + + // Derive the escrow PDA for this fee payer and create it with some lamports + let escrow_pubkey = solana_svm::escrow::ephemeral_balance_pda_from_payer(&fee_payer.pubkey()); + let mut escrow_account = AccountSharedData::default(); + escrow_account.set_lamports(10 * LAMPORTS_PER_SIGNATURE); + escrow_account.set_rent_epoch(u64::MAX); + + let mut test_entry = SvmTestEntry::default(); + // Add the escrow account as an initial account in the bank + test_entry.add_initial_account(escrow_pubkey, &escrow_account); + + // Construct a no-op transaction (ComputeBudget instruction only) + let ix = ComputeBudgetInstruction::set_compute_unit_limit(200_000); + let tx = Transaction::new_signed_with_payer( + &[ix.into()], + Some(&fee_payer.pubkey()), + &[&fee_payer], + LAST_BLOCKHASH, + ); + + // Add the transaction to the test entry and set expected escrow deduction by one signature fee + test_entry.push_transaction(tx); + // Assert that the fee payer was not charged -> does not exist + assert!(test_entry.final_accounts + .get_mut(&fee_payer.pubkey()).is_none()); + // Assert that the escrow account will be charged for the transaction fee + test_entry.decrease_expected_lamports(&escrow_pubkey, LAMPORTS_PER_SIGNATURE); + + // Execute + let env = SvmTestEnvironment::create(test_entry); + env.execute(); +} From 37f5bc7546058e2559270278dd6aeb3d6ba21daf Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Thu, 28 Aug 2025 12:34:00 +0200 Subject: [PATCH 03/21] chore: refactor --- .output.txt | 318 ----------------------------------- Cargo.lock | 48 ++---- Cargo.toml | 8 +- src/account_loader.rs | 10 +- src/escrow.rs | 9 +- src/lib.rs | 2 +- src/rollback_accounts.rs | 22 ++- src/transaction_processor.rs | 14 +- tests/integration_test.rs | 11 +- 9 files changed, 55 insertions(+), 387 deletions(-) delete mode 100644 .output.txt diff --git a/.output.txt b/.output.txt deleted file mode 100644 index ae8a0db..0000000 --- a/.output.txt +++ /dev/null @@ -1,318 +0,0 @@ -error[E0308]: mismatched types - --> src/account_loader.rs:251:49 - | -251 | self.account_cache.insert(*address, account.clone()); - | ------ ^^^^^^^^^^^^^^^ expected `solana_account::AccountSharedData`, found `AccountSharedData` - | | - | arguments to this method are incorrect - | -note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same - --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 - | -125 | pub enum AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_account::AccountSharedData` - | - ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 - | -136 | pub struct AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_sdk::solana_account::AccountSharedData` - | - ::: src/account_loader.rs:13:5 - | -13 | solana_account::{ - | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate -... -22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, - | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` - = help: you can use `cargo tree` to explore your dependency tree -help: the return type of this call is `solana_sdk::solana_account::AccountSharedData` due to the type of the argument passed - --> src/account_loader.rs:251:13 - | -251 | self.account_cache.insert(*address, account.clone()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------------^ - | | - | this argument influences the return type of `insert` -note: method defined here - --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ahash-0.8.12/src/hash_map.rs:185:12 - | -185 | pub fn insert(&mut self, k: K, v: V) -> Option { - | ^^^^^^ -error[E0308]: mismatched types - --> src/account_loader.rs:266:46 - | -266 | rent_collector.collect_rent(address, account) - | ------------ ^^^^^^^ expected `AccountSharedData`, found `solana_account::AccountSharedData` - | | - | arguments to this method are incorrect - | -note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same - --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 - | -125 | pub enum AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_account::AccountSharedData` - | - ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 - | -136 | pub struct AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_sdk::solana_account::AccountSharedData` - | - ::: src/account_loader.rs:13:5 - | -13 | solana_account::{ - | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate -... -22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, - | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` - = help: you can use `cargo tree` to explore your dependency tree -note: method defined here - --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-svm-rent-collector-2.2.1/src/svm_rent_collector.rs:80:8 - | -80 | fn collect_rent(&self, address: &Pubkey, account: &mut AccountShare... - | ^^^^^^^^^^^^ -error[E0308]: mismatched types - --> src/account_loader.rs:299:55 - | -299 | ...count_kind = get_system_account_kind(payer_account).ok_or_else(|| { - | ----------------------- ^^^^^^^^^^^^^ expected `&AccountSharedData`, found `&mut AccountSharedData` - | | - | arguments to this function are incorrect - | -note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same - --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 - | -125 | pub enum AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_account::AccountSharedData` - | - ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 - | -136 | pub struct AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_sdk::solana_account::AccountSharedData` - | - ::: src/account_loader.rs:13:5 - | -13 | solana_account::{ - | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate -... -22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, - | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` - = help: you can use `cargo tree` to explore your dependency tree -note: function defined here - --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-nonce-account-2.2.1/src/lib.rs:55:8 - | -55 | pub fn get_system_account_kind(account: &AccountSharedData) -> Option src/account_loader.rs:323:70 - | -323 | ...nt_collector.get_account_rent_state(payer_account); - | ---------------------- ^^^^^^^^^^^^^ expected `&AccountSharedData`, found `&mut AccountSharedData` - | | - | arguments to this method are incorrect - | -note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same - --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 - | -125 | pub enum AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_account::AccountSharedData` - | - ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 - | -136 | pub struct AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_sdk::solana_account::AccountSharedData` - | - ::: src/account_loader.rs:13:5 - | -13 | solana_account::{ - | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate -... -22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, - | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` - = help: you can use `cargo tree` to explore your dependency tree -note: method defined here - --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-svm-rent-collector-2.2.1/src/svm_rent_collector.rs:87:8 - | -87 | fn get_account_rent_state(&self, account: &AccountSharedData) -> Re... - | ^^^^^^^^^^^^^^^^^^^^^^ -error[E0308]: mismatched types - --> src/account_loader.rs:328:71 - | -328 | ...nt_collector.get_account_rent_state(payer_account); - | ---------------------- ^^^^^^^^^^^^^ expected `&AccountSharedData`, found `&mut AccountSharedData` - | | - | arguments to this method are incorrect - | -note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same - --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 - | -125 | pub enum AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_account::AccountSharedData` - | - ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 - | -136 | pub struct AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_sdk::solana_account::AccountSharedData` - | - ::: src/account_loader.rs:13:5 - | -13 | solana_account::{ - | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate -... -22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, - | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` - = help: you can use `cargo tree` to explore your dependency tree -note: method defined here - --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-svm-rent-collector-2.2.1/src/svm_rent_collector.rs:87:8 - | -87 | fn get_account_rent_state(&self, account: &AccountSharedData) -> Re... - | ^^^^^^^^^^^^^^^^^^^^^^ -error[E0308]: mismatched types - --> src/account_loader.rs:333:9 - | -329 | rent_collector.check_rent_state_with_account( - | ----------------------------- arguments to this method are incorrect -... -333 | payer_account, - | ^^^^^^^^^^^^^ expected `&AccountSharedData`, found `&mut AccountSharedData` - | -note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same - --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 - | -125 | pub enum AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_account::AccountSharedData` - | - ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 - | -136 | pub struct AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_sdk::solana_account::AccountSharedData` - | - ::: src/account_loader.rs:13:5 - | -13 | solana_account::{ - | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate -... -22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, - | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` - = help: you can use `cargo tree` to explore your dependency tree -note: method defined here - --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-svm-rent-collector-2.2.1/src/svm_rent_collector.rs:61:8 - | -61 | fn check_rent_state_with_account( - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0308]: mismatched types - --> src/account_loader.rs:522:9 - | -397 | let mut accounts = Vec::with_capacity(account_keys.len()); - | -------------------------------------- here the type of `accounts` is inferred to be `Vec<(Pubkey, solana_account::AccountSharedData)>` -... -522 | accounts, - | ^^^^^^^^ expected `AccountSharedData`, found `solana_account::AccountSharedData` - | -note: two different versions of crate `solana_account` are being used; two types coming from two different versions of the same crate are different types even if they look the same - --> /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:125:1 - | -125 | pub enum AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the found type `solana_account::AccountSharedData` - | - ::: /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:136:1 - | -136 | pub struct AccountSharedData { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the expected type `solana_sdk::solana_account::AccountSharedData` - | - ::: src/account_loader.rs:13:5 - | -13 | solana_account::{ - | -------------- one version of crate `solana_account` used here, as a direct dependency of the current crate -... -22 | solana_nonce_account::{get_system_account_kind, SystemAccountKind}, - | -------------------- one version of crate `solana_account` used here, as a dependency of crate `solana_nonce_account` - = help: you can use `cargo tree` to explore your dependency tree -error[E0599]: no method named `data_as_mut_slice` found for struct `RefMut<'_, AccountSharedData>` in the current scope - --> src/message_processor.rs:43:33 - | -43 | mut_account_ref.data_as_mut_slice(), - | ^^^^^^^^^^^^^^^^^ method not found in `RefMut<'_, AccountSharedData>` - | -note: there are multiple different versions of crate `solana_account` in the dependency graph - --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:185:1 - | -185 | pub trait WritableAccount: ReadableAccount { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the trait that is needed -... -209 | fn data_as_mut_slice(&mut self) -> &mut [u8]; - | --------------------------------------------- the method is available for `RefMut<'_, solana_sdk::solana_account::AccountSharedData>` here - | - ::: src/message_processor.rs:2:5 - | -2 | solana_account::WritableAccount, - | ------------------------------- `WritableAccount` imported here doesn't correspond to the right version of crate `solana_account` - | - ::: /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:179:1 - | -179 | pub trait WritableAccount: ReadableAccount { - | ------------------------------------------ this is the trait that was imported -error[E0599]: no method named `owner` found for struct `Ref<'_, AccountSharedData>` in the current scope - --> src/transaction_account_state_info.rs:31:72 - | -31 | ...check_id(account.owner())); - | ^^^^^ private field, not a method - | -note: there are multiple different versions of crate `solana_account` in the dependency graph - --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:223:1 - | -223 | pub trait ReadableAccount: Sized { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the trait that is needed -... -226 | fn owner(&self) -> &Pubkey; - | --------------------------- the method is available for `Ref<'_, solana_sdk::solana_account::AccountSharedData>` here - | - ::: src/transaction_account_state_info.rs:2:5 - | -2 | solana_account::ReadableAccount, - | ------------------------------- `ReadableAccount` imported here doesn't correspond to the right version of crate `solana_account` - | - ::: /Users/gabrielepicco/.cargo/git/checkouts/solana-account-558d27a83388a655/176540a/src/lib.rs:217:1 - | -217 | pub trait ReadableAccount: Sized { - | -------------------------------- this is the trait that was imported -help: there is a method `set_owner` with a similar name, but with different arguments - --> /Users/gabrielepicco/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana-account-2.2.1/src/lib.rs:210:5 - | -210 | fn set_owner(&mut self, owner: Pubkey); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0308]: mismatched types - --> src/transaction_processor.rs:977:47 - | -977 | ... transaction_accounts_lamports_sum(&transaction_accounts).unwrap... - | --------------------------------- ^^^^^^^^^^^^^^^^^^^^^ expected `&[(Pubkey, AccountSharedData)]`, found `&Vec<(Pubkey, AccountSharedData)>` - | | - | arguments to this function are incorrect - | - = note: expected reference `&[(Pubkey, solana_account::AccountSharedData)]` - found reference `&Vec<(Pubkey, solana_sdk::solana_account::AccountSharedData)>` -note: function defined here - --> src/transaction_processor.rs:963:12 - | -963 | fn transaction_accounts_lamports_sum( - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -964 | accounts: &[(Pubkey, AccountSharedData)], - | ---------------------------------------- -error[E0308]: mismatched types - --> src/transaction_processor.rs:1096:50 - | -1096 | && transaction_accounts_lamports_sum(&accounts) - | --------------------------------- ^^^^^^^^^ expected `&[(Pubkey, AccountSharedData)]`, found `&Vec<(Pubkey, AccountSharedData)>` - | | - | arguments to this function are incorrect - | - = note: expected reference `&[(Pubkey, solana_account::AccountSharedData)]` - found reference `&Vec<(Pubkey, solana_sdk::solana_account::AccountSharedData)>` -note: function defined here - --> src/transaction_processor.rs:963:12 - | -963 | fn transaction_accounts_lamports_sum( - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -964 | accounts: &[(Pubkey, AccountSharedData)], - | ---------------------------------------- -Some errors have detailed explanations: E0308, E0599. -For more information about an error, try `rustc --explain E0308`. -error: could not compile `solana-svm` (lib) due to 11 previous errors \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 57cd159..4606f9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2637,24 +2637,6 @@ dependencies = [ "solana-sysvar", ] -[[package]] -name = "solana-account" -version = "2.2.1" -source = "git+https://github.com/magicblock-labs/solana-account.git?rev=176540a#176540ae8445a3161b2e8d5ab97a4d48bab35679" -dependencies = [ - "bincode", - "qualifier_attr", - "serde", - "serde_bytes", - "serde_derive", - "solana-account-info", - "solana-clock", - "solana-instruction", - "solana-pubkey", - "solana-sdk-ids", - "solana-sysvar", -] - [[package]] name = "solana-account-info" version = "2.2.1" @@ -2788,7 +2770,7 @@ dependencies = [ "libsecp256k1", "qualifier_attr", "scopeguard", - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-account-info", "solana-big-mod-exp", "solana-bincode", @@ -2856,7 +2838,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83f0071874e629f29e0eb3dab8a863e98502ac7aba55b7e0df1803fc5cac72a7" dependencies = [ - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-commitment-config", "solana-epoch-info", "solana-hash", @@ -2973,7 +2955,7 @@ dependencies = [ "chrono", "serde", "serde_derive", - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-bincode", "solana-instruction", "solana-log-collector", @@ -3138,7 +3120,7 @@ dependencies = [ "bincode", "serde", "serde_derive", - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-account-info", "solana-instruction", "solana-program-error", @@ -3231,7 +3213,7 @@ dependencies = [ "memmap2", "serde", "serde_derive", - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-clock", "solana-cluster-type", "solana-epoch-schedule", @@ -3430,7 +3412,7 @@ checksum = "81b24999844b09096c79567c1298617efe084860149d875b702ef76e2faa2462" dependencies = [ "log", "qualifier_attr", - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-bincode", "solana-bpf-loader-program", "solana-compute-budget", @@ -3553,7 +3535,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde971a20b8dbf60144d6a84439dda86b5466e00e2843091fe731083cda614da" dependencies = [ - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-hash", "solana-nonce", "solana-sdk-ids", @@ -3802,7 +3784,7 @@ dependencies = [ "percentage", "rand 0.8.5", "serde", - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-clock", "solana-compute-budget", "solana-epoch-rewards", @@ -3892,7 +3874,7 @@ checksum = "7c1e19f5d5108b0d824244425e43bc78bbb9476e2199e979b0230c9f632d3bf4" dependencies = [ "serde", "serde_derive", - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-clock", "solana-epoch-schedule", "solana-frozen-abi", @@ -3973,7 +3955,7 @@ dependencies = [ "js-sys", "serde", "serde_json", - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-bn254", "solana-client-traits", "solana-cluster-type", @@ -4274,7 +4256,7 @@ checksum = "dabc713c25ff999424ec68ac4572f2ff6bfd6317922c7864435ccaf9c76504a8" dependencies = [ "bincode", "log", - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-bincode", "solana-clock", "solana-config-program", @@ -4313,7 +4295,7 @@ dependencies = [ "rand 0.7.3", "serde_derive", "shuttle", - "solana-account 2.2.1 (git+https://github.com/magicblock-labs/solana-account.git?rev=176540a)", + "solana-account", "solana-bpf-loader-program", "solana-clock", "solana-compute-budget", @@ -4429,7 +4411,7 @@ dependencies = [ "log", "serde", "serde_derive", - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-bincode", "solana-instruction", "solana-log-collector", @@ -4566,7 +4548,7 @@ dependencies = [ "bincode", "serde", "serde_derive", - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-instruction", "solana-pubkey", "solana-rent", @@ -4640,7 +4622,7 @@ dependencies = [ "num-traits", "serde", "serde_derive", - "solana-account 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-account", "solana-bincode", "solana-clock", "solana-epoch-schedule", diff --git a/Cargo.toml b/Cargo.toml index 48c805b..2f72921 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ log = { version = "0.4.27" } percentage = { version = "0.1.0" } qualifier_attr = { version = "0.2.2", optional = true } serde_derive = { version = "1.0.217" } -solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "176540a" } +solana-account = { version = "=2.2.1" } solana-bpf-loader-program = { version = "=2.2.1" } solana-clock = { version = "=2.2.1" } solana-compute-budget = { version = "=2.2.1" } @@ -71,6 +71,7 @@ openssl = "0.10" prost = "0.11.9" rand0-7 = { package = "rand", version = "0.7" } shuttle = "0.7.1" +solana-account = { version = "=2.2.1" } solana-clock = { version = "=2.2.1" } solana-compute-budget = { version = "=2.2.1", features = ["dev-context-only-utils"] } solana-compute-budget-interface = { version = "=2.2.1" } @@ -116,7 +117,4 @@ shuttle-test = [ "solana-program-runtime/shuttle-test", "solana-bpf-loader-program/shuttle-test", "solana-loader-v4-program/shuttle-test", -] - -#[patch.crates-io] -#solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "176540a" } \ No newline at end of file +] \ No newline at end of file diff --git a/src/account_loader.rs b/src/account_loader.rs index f3af029..b18943f 100644 --- a/src/account_loader.rs +++ b/src/account_loader.rs @@ -204,12 +204,12 @@ impl<'a, CB: TransactionProcessingCallback> AccountLoader<'a, CB> { } } - pub(crate) fn update_accounts_for_failed_tx( - &mut self, - rollback_accounts: &RollbackAccounts, - ) { + pub(crate) fn update_accounts_for_failed_tx(&mut self, rollback_accounts: &RollbackAccounts) { match rollback_accounts { - RollbackAccounts::FeePayerOnly { fee_payer_account, fee_payer_address } => { + RollbackAccounts::FeePayerOnly { + fee_payer_account, + fee_payer_address, + } => { self.account_cache .insert(*fee_payer_address, fee_payer_account.clone()); } diff --git a/src/escrow.rs b/src/escrow.rs index 2d894d6..d8f4c95 100644 --- a/src/escrow.rs +++ b/src/escrow.rs @@ -1,15 +1,10 @@ use solana_pubkey::{pubkey, Pubkey}; // Delegation program ID used for deriving escrow-related PDAs -pub const DELEGATION_PROGRAM_ID: Pubkey = pubkey!( - "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh" -); +pub const DELEGATION_PROGRAM_ID: Pubkey = pubkey!("DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh"); /// Derive the ephemeral balance PDA for a given payer and index, using the /// delegation program ID. pub fn ephemeral_balance_pda_from_payer(payer: &Pubkey) -> Pubkey { - Pubkey::find_program_address( - &[b"balance", &payer.as_ref(), &[0]], - &DELEGATION_PROGRAM_ID, - ).0 + Pubkey::find_program_address(&[b"balance", &payer.as_ref(), &[0]], &DELEGATION_PROGRAM_ID).0 } diff --git a/src/lib.rs b/src/lib.rs index 27c0886..9ba678e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod account_loader; pub mod account_overrides; +pub mod escrow; pub mod message_processor; pub mod nonce_info; pub mod program_loader; @@ -15,7 +16,6 @@ pub mod transaction_execution_result; pub mod transaction_processing_callback; pub mod transaction_processing_result; pub mod transaction_processor; -pub mod escrow; #[cfg_attr(feature = "frozen-abi", macro_use)] #[cfg(feature = "frozen-abi")] diff --git a/src/rollback_accounts.rs b/src/rollback_accounts.rs index 3239c95..8d28bc6 100644 --- a/src/rollback_accounts.rs +++ b/src/rollback_accounts.rs @@ -76,7 +76,10 @@ impl RollbackAccounts { // alter this behavior such that rent epoch updates are handled the // same for both nonce and non-nonce failed transactions. fee_payer_account.set_rent_epoch(fee_payer_loaded_rent_epoch); - RollbackAccounts::FeePayerOnly { fee_payer_account, fee_payer_address } + RollbackAccounts::FeePayerOnly { + fee_payer_account, + fee_payer_address, + } } } @@ -92,7 +95,9 @@ impl RollbackAccounts { /// cost of transaction processing in the cost model. pub fn data_size(&self) -> usize { match self { - Self::FeePayerOnly { fee_payer_account, .. } => fee_payer_account.data().len(), + Self::FeePayerOnly { + fee_payer_account, .. + } => fee_payer_account.data().len(), Self::SameNonceAndFeePayer { nonce } => nonce.account().data().len(), Self::SeparateNonceAndFeePayer { nonce, @@ -108,9 +113,13 @@ impl RollbackAccounts { /// Return the effective fee-payer address that should be written back for fees-only results. pub fn effective_fee_payer_address(&self) -> Pubkey { match self { - Self::FeePayerOnly { fee_payer_address, .. } => *fee_payer_address, + Self::FeePayerOnly { + fee_payer_address, .. + } => *fee_payer_address, Self::SameNonceAndFeePayer { nonce } => *nonce.address(), - Self::SeparateNonceAndFeePayer { fee_payer_address, .. } => *fee_payer_address, + Self::SeparateNonceAndFeePayer { + fee_payer_address, .. + } => *fee_payer_address, } } } @@ -152,7 +161,10 @@ mod tests { let expected_fee_payer_account = fee_payer_account; match rollback_accounts { - RollbackAccounts::FeePayerOnly { fee_payer_account, fee_payer_address: addr } => { + RollbackAccounts::FeePayerOnly { + fee_payer_account, + fee_payer_address: addr, + } => { assert_eq!(expected_fee_payer_account, fee_payer_account); assert_eq!(addr, fee_payer_address); } diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index ab62db3..cfae514 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -1,3 +1,5 @@ +use crate::account_loader::LoadedTransactionAccount; +use crate::escrow::ephemeral_balance_pda_from_payer; #[cfg(feature = "dev-context-only-utils")] use qualifier_attr::{field_qualifiers, qualifiers}; use { @@ -68,8 +70,6 @@ use { sync::Weak, }, }; -use crate::account_loader::LoadedTransactionAccount; -use crate::escrow::ephemeral_balance_pda_from_payer; /// A list of log messages emitted during a transaction pub type TransactionLogMessages = Vec; @@ -579,20 +579,15 @@ impl TransactionBatchProcessor { Some(account) => account, None => { if fee_lamports_per_signature > 0 { - println!("There are fees to pay but the fee payer account is not found. Trying escrow..."); - println!("Feepayer: {}", fee_payer_address); + // Try to charge the escrow if it exists let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address); match account_loader.load_account(&escrow_address, true) { Some(escrow_account) => { fee_payer_address = escrow_address; - println!("Using escrow account as fee payer: {}", escrow_address); - println!("Escrow lamports: {}", escrow_account.account.lamports()); - println!("Fee lamports per signature: {}", fee_lamports_per_signature); escrow_account - }, + } None => { error_counters.account_not_found += 1; - println!("Escrow account also not found."); return Err(TransactionError::AccountNotFound); } } @@ -2679,5 +2674,4 @@ mod tests { &[(fee_payer_address, vec![(Some(fee_payer_account), true)])], ); } - } diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 2a764f7..9cf02de 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -184,7 +184,10 @@ impl SvmTestEnvironment<'_> { } Ok(ProcessedTransaction::FeesOnly(fees_only_transaction)) => { match fees_only_transaction.rollback_accounts.clone() { - RollbackAccounts::FeePayerOnly { fee_payer_account, fee_payer_address } => { + RollbackAccounts::FeePayerOnly { + fee_payer_account, + fee_payer_address, + } => { update_or_dealloc_account( &mut final_accounts_actual, fee_payer_address, @@ -2541,8 +2544,10 @@ fn escrow_fee_charged_with_noop_transaction() { // Add the transaction to the test entry and set expected escrow deduction by one signature fee test_entry.push_transaction(tx); // Assert that the fee payer was not charged -> does not exist - assert!(test_entry.final_accounts - .get_mut(&fee_payer.pubkey()).is_none()); + assert!(test_entry + .final_accounts + .get_mut(&fee_payer.pubkey()) + .is_none()); // Assert that the escrow account will be charged for the transaction fee test_entry.decrease_expected_lamports(&escrow_pubkey, LAMPORTS_PER_SIGNATURE); From e735e2ab5c4fc94ebbb4fbf2c196a7f1b50178f5 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Thu, 28 Aug 2025 12:36:30 +0200 Subject: [PATCH 04/21] fix: github workflow, add protobuf --- .github/workflows/cargo-test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/cargo-test.yml b/.github/workflows/cargo-test.yml index 4cccc87..cd5c17d 100644 --- a/.github/workflows/cargo-test.yml +++ b/.github/workflows/cargo-test.yml @@ -27,5 +27,11 @@ jobs: rustc -Vv cargo -Vv + - name: Install protoc (protobuf-compiler) + run: | + sudo apt-get update + sudo apt-get install -y protobuf-compiler + protoc --version + - name: Run tests run: cargo test --all --locked From 2026246f3896b632fbadcbc7b3f29ddab42ac882 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Thu, 28 Aug 2025 12:38:46 +0200 Subject: [PATCH 05/21] fix: deps --- Cargo.lock | 1 + Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 4606f9f..e8721d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4293,6 +4293,7 @@ dependencies = [ "prost", "qualifier_attr", "rand 0.7.3", + "serde", "serde_derive", "shuttle", "solana-account", diff --git a/Cargo.toml b/Cargo.toml index 2f72921..2c44571 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ ahash = { version = "0.8.12" } log = { version = "0.4.27" } percentage = { version = "0.1.0" } qualifier_attr = { version = "0.2.2", optional = true } +serde = { version = "1.0.217", features = ["rc"] } serde_derive = { version = "1.0.217" } solana-account = { version = "=2.2.1" } solana-bpf-loader-program = { version = "=2.2.1" } From 6b11bf97483d08818723d2cdf0ee7a73c3530e7f Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Mon, 1 Sep 2025 17:23:39 +0200 Subject: [PATCH 06/21] chore: refactor --- src/transaction_processor.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index cfae514..bcc8d39 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -648,10 +648,6 @@ impl TransactionBatchProcessor { fee_payer_loaded_rent_epoch, ); - println!("Transaction fee details: {:?}", &fee_details); - println!("Loaded fee payer account: {:?}", &loaded_fee_payer.account); - println!("Loaded fee payer address: {:?}", fee_payer_address); - Ok(ValidatedTransactionDetails { fee_details, rollback_accounts, From 0f4670a580f3aa4fa6f0d31cb225af36f9090e9e Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Mon, 1 Sep 2025 21:46:24 +0200 Subject: [PATCH 07/21] chore: add tests --- Cargo.lock | 4 +- Cargo.toml | 5 ++- src/escrow.rs | 2 +- src/transaction_processor.rs | 68 ++++++++++++++-------------- tests/integration_test.rs | 86 +++++++++++++++++++++++++++++++++++- 5 files changed, 127 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8721d5..04fd11e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2618,8 +2618,7 @@ dependencies = [ [[package]] name = "solana-account" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f949fe4edaeaea78c844023bfc1c898e0b1f5a100f8a8d2d0f85d0a7b090258" +source = "git+https://github.com/magicblock-labs/solana-account.git?rev=9a62efa#9a62efad44190731c3dc83716e7b8cbe70ff8a62" dependencies = [ "bincode", "qualifier_attr", @@ -2631,7 +2630,6 @@ dependencies = [ "solana-frozen-abi", "solana-frozen-abi-macro", "solana-instruction", - "solana-logger", "solana-pubkey", "solana-sdk-ids", "solana-sysvar", diff --git a/Cargo.toml b/Cargo.toml index 2c44571..98b2542 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,4 +118,7 @@ shuttle-test = [ "solana-program-runtime/shuttle-test", "solana-bpf-loader-program/shuttle-test", "solana-loader-v4-program/shuttle-test", -] \ No newline at end of file +] + +[patch.crates-io] +solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "9a62efa" } \ No newline at end of file diff --git a/src/escrow.rs b/src/escrow.rs index d8f4c95..f51e7bd 100644 --- a/src/escrow.rs +++ b/src/escrow.rs @@ -6,5 +6,5 @@ pub const DELEGATION_PROGRAM_ID: Pubkey = pubkey!("DELeGGvXpWV2fqJUhqcF5ZSYMS4JT /// Derive the ephemeral balance PDA for a given payer and index, using the /// delegation program ID. pub fn ephemeral_balance_pda_from_payer(payer: &Pubkey) -> Pubkey { - Pubkey::find_program_address(&[b"balance", &payer.as_ref(), &[0]], &DELEGATION_PROGRAM_ID).0 + Pubkey::find_program_address(&[b"balance", payer.as_ref(), &[0]], &DELEGATION_PROGRAM_ID).0 } diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index bcc8d39..9e22753 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -575,29 +575,27 @@ impl TransactionBatchProcessor { let mut fee_payer_address = *message.fee_payer(); - let mut loaded_fee_payer = match account_loader.load_account(&fee_payer_address, true) { - Some(account) => account, - None => { - if fee_lamports_per_signature > 0 { - // Try to charge the escrow if it exists - let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address); - match account_loader.load_account(&escrow_address, true) { - Some(escrow_account) => { - fee_payer_address = escrow_address; - escrow_account - } - None => { - error_counters.account_not_found += 1; - return Err(TransactionError::AccountNotFound); - } - } + let mut loaded_fee_payer = { + // Load an account only if it exists *and* is delegated + let mut load_if_delegated = |addr: &Pubkey| -> Option { + match account_loader.load_account(addr, true) { + Some(acc) if acc.account.delegated() => Some(acc), + _ => None, + } + }; + + // 1) Prefer the fee payer if delegated + if let Some(acc) = load_if_delegated(&fee_payer_address) { + acc + } else { + // 2) Otherwise require escrow to exist and be delegated + let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address); + if let Some(acc) = load_if_delegated(&escrow_address) { + fee_payer_address = escrow_address; + acc } else { - // No fees, so return a default account - LoadedTransactionAccount { - account: AccountSharedData::default(), - loaded_size: 0, - rent_collected: 0, - } + error_counters.invalid_account_for_fee += 1; + return Err(TransactionError::InvalidAccountForFee); } } }; @@ -2194,12 +2192,13 @@ mod tests { let fee_payer_rent_epoch = current_epoch; let fee_payer_rent_debit = 0; - let fee_payer_account = AccountSharedData::new_rent_epoch( + let mut fee_payer_account = AccountSharedData::new_rent_epoch( starting_balance, 0, &Pubkey::default(), fee_payer_rent_epoch, ); + fee_payer_account.set_delegated(true); let mut mock_accounts = HashMap::new(); mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); let mock_bank = MockBankCallback { @@ -2268,7 +2267,8 @@ mod tests { let min_balance = rent_collector.rent.minimum_balance(0); let transaction_fee = lamports_per_signature; let starting_balance = min_balance - 1; - let fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default()); + let mut fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default()); + fee_payer_account.set_delegated(true); let fee_payer_rent_debit = rent_collector .get_rent_due( fee_payer_account.lamports(), @@ -2348,8 +2348,8 @@ mod tests { &mock_bank, ); - assert_eq!(error_counters.account_not_found.0, 1); - assert_eq!(result, Err(TransactionError::AccountNotFound)); + assert_eq!(error_counters.invalid_account_for_fee.0, 1); + assert_eq!(result, Err(TransactionError::InvalidAccountForFee)); } #[test] @@ -2358,7 +2358,8 @@ mod tests { let message = new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique()))); let fee_payer_address = message.fee_payer(); - let fee_payer_account = AccountSharedData::new(1, 0, &Pubkey::default()); + let mut fee_payer_account = AccountSharedData::new(1, 0, &Pubkey::default()); + fee_payer_account.set_delegated(true); let mut mock_accounts = HashMap::new(); mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); let mock_bank = MockBankCallback { @@ -2394,7 +2395,8 @@ mod tests { let rent_collector = RentCollector::default(); let min_balance = rent_collector.rent.minimum_balance(0); let starting_balance = min_balance + transaction_fee - 1; - let fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default()); + let mut fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default()); + fee_payer_account.set_delegated(true); let mut mock_accounts = HashMap::new(); mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); let mock_bank = MockBankCallback { @@ -2510,7 +2512,7 @@ mod tests { // Sufficient Fees { - let fee_payer_account = AccountSharedData::new_data( + let mut fee_payer_account = AccountSharedData::new_data( min_balance + transaction_fee + priority_fee, &nonce::versions::Versions::new(nonce::state::State::Initialized( nonce::state::Data::new( @@ -2522,6 +2524,7 @@ mod tests { &system_program::id(), ) .unwrap(); + fee_payer_account.set_delegated(true); let mut mock_accounts = HashMap::new(); mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); @@ -2613,8 +2616,8 @@ mod tests { &mock_bank, ); - assert_eq!(error_counters.insufficient_funds.0, 1); - assert_eq!(result, Err(TransactionError::InsufficientFundsForFee)); + assert_eq!(error_counters.invalid_account_for_fee.0, 1); + assert_eq!(result, Err(TransactionError::InvalidAccountForFee)); } } @@ -2623,12 +2626,13 @@ mod tests { #[test] fn test_inspect_account_fee_payer() { let fee_payer_address = Pubkey::new_unique(); - let fee_payer_account = AccountSharedData::new_rent_epoch( + let mut fee_payer_account = AccountSharedData::new_rent_epoch( 123_000_000_000, 0, &Pubkey::default(), RENT_EXEMPT_RENT_EPOCH, ); + fee_payer_account.set_delegated(true); let mock_bank = MockBankCallback::default(); mock_bank .account_shared_data diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 9cf02de..1ce3a59 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -166,7 +166,6 @@ impl SvmTestEnvironment<'_> { { if sanitized_transaction.is_writable(index) { let effective_pubkey = if index == 0 { - // Use effective fee payer (may be escrow) for account 0 executed_transaction .loaded_transaction .rollback_accounts @@ -2525,6 +2524,7 @@ fn escrow_fee_charged_with_noop_transaction() { // Derive the escrow PDA for this fee payer and create it with some lamports let escrow_pubkey = solana_svm::escrow::ephemeral_balance_pda_from_payer(&fee_payer.pubkey()); let mut escrow_account = AccountSharedData::default(); + escrow_account.set_delegated(true); escrow_account.set_lamports(10 * LAMPORTS_PER_SIGNATURE); escrow_account.set_rent_epoch(u64::MAX); @@ -2555,3 +2555,87 @@ fn escrow_fee_charged_with_noop_transaction() { let env = SvmTestEnvironment::create(test_entry); env.execute(); } + +#[test] +fn escrow_fee_rejected_when_not_delegated() { + // Create a fee payer that does not have an on-ledger account + let fee_payer = Keypair::new(); + + // Derive the escrow PDA for this fee payer and create it with some lamports + let escrow_pubkey = solana_svm::escrow::ephemeral_balance_pda_from_payer(&fee_payer.pubkey()); + let mut escrow_account = AccountSharedData::default(); + // Not delegated: should not be allowed to pay fees + escrow_account.set_delegated(false); + escrow_account.set_lamports(10 * LAMPORTS_PER_SIGNATURE); + escrow_account.set_rent_epoch(u64::MAX); + + let mut test_entry = SvmTestEntry::default(); + // Add the escrow account as an initial account in the bank + test_entry.add_initial_account(escrow_pubkey, &escrow_account); + + // Construct a no-op transaction (ComputeBudget instruction only) + let ix = ComputeBudgetInstruction::set_compute_unit_limit(200_000); + let tx = Transaction::new_signed_with_payer( + &[ix.into()], + Some(&fee_payer.pubkey()), + &[&fee_payer], + LAST_BLOCKHASH, + ); + + // Expect the transaction to be discarded due to InvalidAccountForFee + test_entry.push_transaction_with_status(tx, ExecutionStatus::Discarded); + + // Execute + let env = SvmTestEnvironment::create(test_entry); + env.execute(); +} + +#[test] +fn escrow_fee_charged_when_feepayer_exists_and_not_delegated() { + let fee_payer = Keypair::new(); + let mut fee_payer_account = AccountSharedData::default(); + fee_payer_account.set_lamports(1_000_000_000); + fee_payer_account.set_delegated(false); // explicitly not delegated + fee_payer_account.set_rent_epoch(u64::MAX); + + // Derive the escrow PDA for this fee payer and create it with some lamports, delegated + let escrow_pubkey = solana_svm::escrow::ephemeral_balance_pda_from_payer(&fee_payer.pubkey()); + let mut escrow_account = AccountSharedData::default(); + escrow_account.set_delegated(true); + escrow_account.set_lamports(10 * LAMPORTS_PER_SIGNATURE); + escrow_account.set_rent_epoch(u64::MAX); + + let mut test_entry = SvmTestEntry::default(); + // Add the fee payer and the escrow account as initial accounts in the bank + test_entry.add_initial_account(fee_payer.pubkey(), &fee_payer_account); + test_entry.add_initial_account(escrow_pubkey, &escrow_account); + + // Construct a no-op transaction (ComputeBudget instruction only) + let ix = ComputeBudgetInstruction::set_compute_unit_limit(200_000); + let tx = Transaction::new_signed_with_payer( + &[ix.into()], + Some(&fee_payer.pubkey()), + &[&fee_payer], + LAST_BLOCKHASH, + ); + + // Add the transaction to the test entry and set expected escrow deduction by one signature fee + test_entry.push_transaction(tx); + + // Assert that the fee payer exists and is not charged (balance remains 1_000_000_000) + assert_eq!( + test_entry + .final_accounts + .get(&fee_payer.pubkey()) + .unwrap() + .lamports(), + 1_000_000_000 + ); + + // Assert that the escrow account will be charged for the transaction fee + test_entry.decrease_expected_lamports(&escrow_pubkey, LAMPORTS_PER_SIGNATURE); + + // Execute + let env = SvmTestEnvironment::create(test_entry); + env.execute(); +} From 771aa20af277659a41e3fe98fb554b23cf0a6cba Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Tue, 2 Sep 2025 17:52:53 +0200 Subject: [PATCH 08/21] chore: fix integration tests --- tests/integration_test.rs | 138 ++++++++++++++++---------------------- 1 file changed, 59 insertions(+), 79 deletions(-) diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 1ce3a59..37eb704 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -784,10 +784,14 @@ fn simple_transfer(enable_fee_only_transactions: bool) -> Vec { let destination = Pubkey::new_unique(); let mut source_data = AccountSharedData::default(); + source_data.set_delegated(true); let mut destination_data = AccountSharedData::default(); + destination_data.set_delegated(true); + destination_data.set_rent_epoch(u64::MAX); source_data.set_lamports(LAMPORTS_PER_SOL * 10); test_entry.add_initial_account(source, &source_data); + test_entry.add_initial_account(destination, &destination_data); test_entry.push_transaction(system_transaction::transfer( &source_keypair, @@ -796,11 +800,7 @@ fn simple_transfer(enable_fee_only_transactions: bool) -> Vec { Hash::default(), )); - destination_data - .checked_add_lamports(transfer_amount) - .unwrap(); - test_entry.create_expected_account(destination, &destination_data); - + test_entry.increase_expected_lamports(&destination, transfer_amount); test_entry.decrease_expected_lamports(&source, transfer_amount + LAMPORTS_PER_SIGNATURE); } @@ -810,6 +810,7 @@ fn simple_transfer(enable_fee_only_transactions: bool) -> Vec { let source = source_keypair.pubkey(); let mut source_data = AccountSharedData::default(); + source_data.set_delegated(true); source_data.set_lamports(transfer_amount - 1); test_entry.add_initial_account(source, &source_data); @@ -860,6 +861,7 @@ fn simple_transfer(enable_fee_only_transactions: bool) -> Vec { let source = source_keypair.pubkey(); let mut source_data = AccountSharedData::default(); + source_data.set_delegated(true); source_data.set_lamports(transfer_amount * 10); test_entry @@ -930,6 +932,7 @@ fn simple_nonce(enable_fee_only_transactions: bool, fee_paying_nonce: bool) -> V if !fake_fee_payer && !fee_paying_nonce { let mut fee_payer_data = AccountSharedData::default(); fee_payer_data.set_lamports(LAMPORTS_PER_SOL); + fee_payer_data.set_delegated(true); test_entry.add_initial_account(fee_payer, &fee_payer_data); } else if rent_paying_nonce { assert!(fee_paying_nonce); @@ -942,12 +945,13 @@ fn simple_nonce(enable_fee_only_transactions: bool, fee_paying_nonce: bool) -> V let nonce_initial_hash = DurableNonce::from_blockhash(&Hash::new_unique()); let nonce_data = nonce::state::Data::new(fee_payer, nonce_initial_hash, LAMPORTS_PER_SIGNATURE); - let nonce_account = AccountSharedData::new_data( + let mut nonce_account = AccountSharedData::new_data( nonce_balance, &nonce::state::Versions::new(nonce::State::Initialized(nonce_data.clone())), &system_program::id(), ) .unwrap(); + nonce_account.set_delegated(true); let nonce_info = NonceInfo::new(nonce_pubkey, nonce_account.clone()); if !(fake_fee_payer && fee_paying_nonce) { @@ -1152,13 +1156,20 @@ fn simd83_intrabatch_account_reuse(enable_fee_only_transactions: bool) -> Vec Vec Vec Vec Vec Vec Vec Vec Vec Vec { let mut fee_payer_data = AccountSharedData::default(); fee_payer_data.set_lamports(LAMPORTS_PER_SOL); + fee_payer_data.set_delegated(true); test_entry.add_initial_account(fee_payer, &fee_payer_data); let target = Pubkey::new_unique(); @@ -2056,6 +2030,7 @@ fn simd83_fee_payer_deallocate(enable_fee_only_transactions: bool) -> Vec Vec Vec Vec Vec Vec Date: Wed, 3 Sep 2025 22:51:17 +0200 Subject: [PATCH 09/21] fix: restore api compatibility for integration --- src/account_loader.rs | 34 ++++++++++++++++++--- src/rollback_accounts.rs | 24 ++------------- src/transaction_processor.rs | 10 +++++-- tests/integration_test.rs | 58 ++++++++++++++++++++++++++++++------ 4 files changed, 90 insertions(+), 36 deletions(-) diff --git a/src/account_loader.rs b/src/account_loader.rs index b18943f..0009b4c 100644 --- a/src/account_loader.rs +++ b/src/account_loader.rs @@ -198,17 +198,43 @@ impl<'a, CB: TransactionProcessingCallback> AccountLoader<'a, CB> { &executed_transaction.loaded_transaction.accounts, ); } else { + let fee_payer_address = self.effective_fee_payer_address_for_failed_tx(message); self.update_accounts_for_failed_tx( &executed_transaction.loaded_transaction.rollback_accounts, + &fee_payer_address, ); } } - pub(crate) fn update_accounts_for_failed_tx(&mut self, rollback_accounts: &RollbackAccounts) { + pub(crate) fn effective_fee_payer_address_for_failed_tx( + &mut self, + message: &impl SVMMessage, + ) -> Pubkey { + use crate::escrow::ephemeral_balance_pda_from_payer; + let fee_payer_address = *message.fee_payer(); + let mut is_delegated = |addr: &Pubkey| -> bool { + self.load_account(addr, true) + .map(|acc| acc.account.delegated()) + .unwrap_or(false) + }; + if is_delegated(&fee_payer_address) { + return fee_payer_address; + } + let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address); + if is_delegated(&escrow_address) { + return escrow_address; + } + fee_payer_address + } + + pub(crate) fn update_accounts_for_failed_tx( + &mut self, + rollback_accounts: &RollbackAccounts, + fee_payer_address: &Pubkey, + ) { match rollback_accounts { RollbackAccounts::FeePayerOnly { - fee_payer_account, - fee_payer_address, + fee_payer_account, .. } => { self.account_cache .insert(*fee_payer_address, fee_payer_account.clone()); @@ -220,7 +246,7 @@ impl<'a, CB: TransactionProcessingCallback> AccountLoader<'a, CB> { RollbackAccounts::SeparateNonceAndFeePayer { nonce, fee_payer_account, - fee_payer_address, + .. } => { self.account_cache .insert(*nonce.address(), nonce.account().clone()); diff --git a/src/rollback_accounts.rs b/src/rollback_accounts.rs index 8d28bc6..d425c35 100644 --- a/src/rollback_accounts.rs +++ b/src/rollback_accounts.rs @@ -11,7 +11,6 @@ use { pub enum RollbackAccounts { FeePayerOnly { fee_payer_account: AccountSharedData, - fee_payer_address: Pubkey, }, SameNonceAndFeePayer { nonce: NonceInfo, @@ -19,7 +18,6 @@ pub enum RollbackAccounts { SeparateNonceAndFeePayer { nonce: NonceInfo, fee_payer_account: AccountSharedData, - fee_payer_address: Pubkey, }, } @@ -28,7 +26,6 @@ impl Default for RollbackAccounts { fn default() -> Self { Self::FeePayerOnly { fee_payer_account: AccountSharedData::default(), - fee_payer_address: Pubkey::default(), } } } @@ -65,7 +62,6 @@ impl RollbackAccounts { RollbackAccounts::SeparateNonceAndFeePayer { nonce, fee_payer_account, - fee_payer_address, } } } else { @@ -76,10 +72,7 @@ impl RollbackAccounts { // alter this behavior such that rent epoch updates are handled the // same for both nonce and non-nonce failed transactions. fee_payer_account.set_rent_epoch(fee_payer_loaded_rent_epoch); - RollbackAccounts::FeePayerOnly { - fee_payer_account, - fee_payer_address, - } + RollbackAccounts::FeePayerOnly { fee_payer_account } } } @@ -113,13 +106,8 @@ impl RollbackAccounts { /// Return the effective fee-payer address that should be written back for fees-only results. pub fn effective_fee_payer_address(&self) -> Pubkey { match self { - Self::FeePayerOnly { - fee_payer_address, .. - } => *fee_payer_address, Self::SameNonceAndFeePayer { nonce } => *nonce.address(), - Self::SeparateNonceAndFeePayer { - fee_payer_address, .. - } => *fee_payer_address, + _ => Pubkey::default(), } } } @@ -161,12 +149,8 @@ mod tests { let expected_fee_payer_account = fee_payer_account; match rollback_accounts { - RollbackAccounts::FeePayerOnly { - fee_payer_account, - fee_payer_address: addr, - } => { + RollbackAccounts::FeePayerOnly { fee_payer_account } => { assert_eq!(expected_fee_payer_account, fee_payer_account); - assert_eq!(addr, fee_payer_address); } _ => panic!("Expected FeePayerOnly variant"), } @@ -253,12 +237,10 @@ mod tests { RollbackAccounts::SeparateNonceAndFeePayer { nonce, fee_payer_account, - fee_payer_address: addr, } => { assert_eq!(nonce.address(), &nonce_address); assert_eq!(nonce.account(), &nonce_account); assert_eq!(expected_fee_payer_account, fee_payer_account); - assert_eq!(addr, fee_payer_address); } _ => panic!("Expected SeparateNonceAndFeePayer variant"), } diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index 9e22753..690cd3d 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -438,8 +438,14 @@ impl TransactionBatchProcessor { TransactionLoadResult::FeesOnly(fees_only_tx) => { if enable_transaction_loading_failure_fees { // Update loaded accounts cache with nonce and fee-payer - account_loader - .update_accounts_for_failed_tx(&fees_only_tx.rollback_accounts); + { + let fee_payer_address = + account_loader.effective_fee_payer_address_for_failed_tx(tx); + account_loader.update_accounts_for_failed_tx( + &fees_only_tx.rollback_accounts, + &fee_payer_address, + ); + } Ok(ProcessedTransaction::FeesOnly(Box::new(fees_only_tx))) } else { diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 37eb704..c547d8a 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -166,10 +166,22 @@ impl SvmTestEnvironment<'_> { { if sanitized_transaction.is_writable(index) { let effective_pubkey = if index == 0 { - executed_transaction - .loaded_transaction - .rollback_accounts - .effective_fee_payer_address() + // Compute effective fee payer address (may be escrow PDA) + let mut addr = *sanitized_transaction.fee_payer(); + let is_delegated = |key: &Pubkey| { + final_accounts_actual + .get(key) + .map(|a| a.delegated()) + .unwrap_or(false) + }; + if !is_delegated(&addr) { + let escrow = + solana_svm::escrow::ephemeral_balance_pda_from_payer(&addr); + if is_delegated(&escrow) { + addr = escrow; + } + } + addr } else { *pubkey }; @@ -184,12 +196,26 @@ impl SvmTestEnvironment<'_> { Ok(ProcessedTransaction::FeesOnly(fees_only_transaction)) => { match fees_only_transaction.rollback_accounts.clone() { RollbackAccounts::FeePayerOnly { - fee_payer_account, - fee_payer_address, + fee_payer_account, .. } => { + // Compute effective fee payer address (may be escrow PDA) + let mut addr = *sanitized_transaction.fee_payer(); + let is_delegated = |key: &Pubkey| { + final_accounts_actual + .get(key) + .map(|a| a.delegated()) + .unwrap_or(false) + }; + if !is_delegated(&addr) { + let escrow = + solana_svm::escrow::ephemeral_balance_pda_from_payer(&addr); + if is_delegated(&escrow) { + addr = escrow; + } + } update_or_dealloc_account( &mut final_accounts_actual, - fee_payer_address, + addr, fee_payer_account, ); } @@ -203,11 +229,25 @@ impl SvmTestEnvironment<'_> { RollbackAccounts::SeparateNonceAndFeePayer { nonce, fee_payer_account, - fee_payer_address, } => { + // Compute effective fee payer address (may be escrow PDA) + let mut addr = *sanitized_transaction.fee_payer(); + let is_delegated = |key: &Pubkey| { + final_accounts_actual + .get(key) + .map(|a| a.delegated()) + .unwrap_or(false) + }; + if !is_delegated(&addr) { + let escrow = + solana_svm::escrow::ephemeral_balance_pda_from_payer(&addr); + if is_delegated(&escrow) { + addr = escrow; + } + } update_or_dealloc_account( &mut final_accounts_actual, - fee_payer_address, + addr, fee_payer_account, ); update_or_dealloc_account( From 3a5f94e109567f957c735a9ca6807563c3e51254 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Sat, 6 Sep 2025 09:53:03 +0200 Subject: [PATCH 10/21] chore: upadte dep --- Cargo.lock | 2 +- Cargo.toml | 6 +-- src/transaction_processor.rs | 102 +++++++++++++++++++++++++++++++++-- 3 files changed, 102 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04fd11e..9ccd54f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2618,7 +2618,7 @@ dependencies = [ [[package]] name = "solana-account" version = "2.2.1" -source = "git+https://github.com/magicblock-labs/solana-account.git?rev=9a62efa#9a62efad44190731c3dc83716e7b8cbe70ff8a62" +source = "git+https://github.com/magicblock-labs/solana-account.git?rev=68b667f#68b667fb1038e9257f34fb1a0819f2344c74444e" dependencies = [ "bincode", "qualifier_attr", diff --git a/Cargo.toml b/Cargo.toml index 98b2542..331f5af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ openssl = "0.10" prost = "0.11.9" rand0-7 = { package = "rand", version = "0.7" } shuttle = "0.7.1" -solana-account = { version = "=2.2.1" } +solana-account = { version = "=2.2.1", features = ["dev-context-only-utils"] } solana-clock = { version = "=2.2.1" } solana-compute-budget = { version = "=2.2.1", features = ["dev-context-only-utils"] } solana-compute-budget-interface = { version = "=2.2.1" } @@ -92,7 +92,6 @@ solana-secp256r1-program = { version = "=2.2.1", features = ["openssl-vendored"] solana-signature = { version = "=2.2.1" } solana-signer = { version = "=2.2.1" } # See order-crates-for-publishing.py for using this unusual `path = "."` -solana-svm = { path = ".", features = ["dev-context-only-utils"] } solana-svm-conformance = { version = "=2.2.1" } solana-system-program = { version = "=2.2.1" } solana-system-transaction = { version = "=2.2.1" } @@ -100,6 +99,7 @@ solana-sysvar = { version = "=2.2.1" } solana-transaction = { version = "=2.2.1" } solana-transaction-context = { version = "=2.2.1", features = ["dev-context-only-utils" ] } test-case = "3.3.1" +solana-svm = { path = ".", features = ["dev-context-only-utils"] } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -121,4 +121,4 @@ shuttle-test = [ ] [patch.crates-io] -solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "9a62efa" } \ No newline at end of file +solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "68b667f" } \ No newline at end of file diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index 690cd3d..6152098 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -583,20 +583,20 @@ impl TransactionBatchProcessor { let mut loaded_fee_payer = { // Load an account only if it exists *and* is delegated - let mut load_if_delegated = |addr: &Pubkey| -> Option { + let mut load_if_delegated_or_privileged = |addr: &Pubkey| -> Option { match account_loader.load_account(addr, true) { - Some(acc) if acc.account.delegated() => Some(acc), + Some(acc) if acc.account.delegated() || acc.account.privileged() => Some(acc), _ => None, } }; // 1) Prefer the fee payer if delegated - if let Some(acc) = load_if_delegated(&fee_payer_address) { + if let Some(acc) = load_if_delegated_or_privileged(&fee_payer_address) { acc } else { // 2) Otherwise require escrow to exist and be delegated let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address); - if let Some(acc) = load_if_delegated(&escrow_address) { + if let Some(acc) = load_if_delegated_or_privileged(&escrow_address) { fee_payer_address = escrow_address; acc } else { @@ -2254,6 +2254,100 @@ mod tests { ); } + #[test] + fn test_validate_transaction_privileged_fee_payer_exact_balance() { + let lamports_per_signature = 5000; + let message = new_unchecked_sanitized_message(Message::new_with_blockhash( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(2000u32), + ComputeBudgetInstruction::set_compute_unit_price(1_000_000_000), + ], + Some(&Pubkey::new_unique()), + &Hash::new_unique(), + )); + let compute_budget_limits = process_compute_budget_instructions( + SVMMessage::program_instructions_iter(&message), + &FeatureSet::default(), + ) + .unwrap(); + let fee_payer_address = message.fee_payer(); + let current_epoch = 42; + let rent_collector = RentCollector { + epoch: current_epoch, + ..RentCollector::default() + }; + let min_balance = rent_collector + .rent + .minimum_balance(nonce::state::State::size()); + let transaction_fee = lamports_per_signature; + let priority_fee = 2_000_000u64; + let starting_balance = transaction_fee + priority_fee; + assert!( + starting_balance > min_balance, + "we're testing that a rent exempt fee payer can be fully drained, \ + so ensure that the starting balance is more than the min balance" + ); + + let fee_payer_rent_epoch = current_epoch; + let fee_payer_rent_debit = 0; + let fee_payer_account = AccountSharedData::new_rent_epoch( + starting_balance, + 0, + &Pubkey::default(), + fee_payer_rent_epoch, + ); + let (_, mut borrowed_acc) = solana_account::test_utils::create_borrowed_account_shared_data(&fee_payer_account, 0); + borrowed_acc.as_borrowed_mut().unwrap().set_privileged(true); + let fee_payer_account = borrowed_acc; + let mut mock_accounts = HashMap::new(); + mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); + let mock_bank = MockBankCallback { + account_shared_data: Arc::new(RwLock::new(mock_accounts)), + ..Default::default() + }; + let mut account_loader = (&mock_bank).into(); + + let mut error_counters = TransactionErrorMetrics::default(); + let result = + TransactionBatchProcessor::::validate_transaction_nonce_and_fee_payer( + &mut account_loader, + &message, + CheckedTransactionDetails::new(None, lamports_per_signature), + &Hash::default(), + FeeStructure::default().lamports_per_signature, + &rent_collector, + &mut error_counters, + &mock_bank, + ); + + let post_validation_fee_payer_account = { + let mut account = fee_payer_account.clone(); + account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH); + account.set_lamports(0); + account + }; + + assert_eq!( + result, + Ok(ValidatedTransactionDetails { + rollback_accounts: RollbackAccounts::new( + None, // nonce + *fee_payer_address, + post_validation_fee_payer_account.clone(), + fee_payer_rent_debit, + fee_payer_rent_epoch + ), + compute_budget_limits, + fee_details: FeeDetails::new(transaction_fee, priority_fee), + loaded_fee_payer_account: LoadedTransactionAccount { + loaded_size: fee_payer_account.data().len(), + account: post_validation_fee_payer_account, + rent_collected: fee_payer_rent_debit, + }, + }) + ); + } + #[test] fn test_validate_transaction_fee_payer_rent_paying() { let lamports_per_signature = 5000; From e313805bed8d8451c5c53ffdb73f296f6c645e80 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Sat, 6 Sep 2025 11:25:19 +0200 Subject: [PATCH 11/21] chore: ignore privileged --- src/transaction_processor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index 20725c5..a5e32b8 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -2286,6 +2286,7 @@ mod tests { } #[test] + #[ignore] fn test_validate_transaction_privileged_fee_payer_exact_balance() { let lamports_per_signature = 5000; let message = new_unchecked_sanitized_message(Message::new_with_blockhash( From d3c1be4647626755b9d81b46516ab945e03e9461 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Sat, 6 Sep 2025 18:02:00 +0200 Subject: [PATCH 12/21] chore: hold on first var --- src/transaction_processor.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index a5e32b8..4f7caa1 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -2286,7 +2286,6 @@ mod tests { } #[test] - #[ignore] fn test_validate_transaction_privileged_fee_payer_exact_balance() { let lamports_per_signature = 5000; let message = new_unchecked_sanitized_message(Message::new_with_blockhash( @@ -2328,7 +2327,7 @@ mod tests { &Pubkey::default(), fee_payer_rent_epoch, ); - let (_, mut borrowed_acc) = + let (_guard, mut borrowed_acc) = solana_account::test_utils::create_borrowed_account_shared_data(&fee_payer_account, 0); borrowed_acc.as_borrowed_mut().unwrap().set_privileged(true); let fee_payer_account = borrowed_acc; From afd11b7b306290d7ed4a313e873416a0d55ed5e1 Mon Sep 17 00:00:00 2001 From: Babur Makhmudov <31780624+bmuddha@users.noreply.github.com> Date: Sat, 6 Sep 2025 20:33:02 +0400 Subject: [PATCH 13/21] fix: no-segfault solana-account --- Cargo.lock | 2 +- Cargo.toml | 40 +++++++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ccd54f..492ad96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2618,7 +2618,7 @@ dependencies = [ [[package]] name = "solana-account" version = "2.2.1" -source = "git+https://github.com/magicblock-labs/solana-account.git?rev=68b667f#68b667fb1038e9257f34fb1a0819f2344c74444e" +source = "git+https://github.com/magicblock-labs/solana-account.git?rev=a892d2#a892d2aff374f260535a4499e00bbe5752a2d29c" dependencies = [ "bincode", "qualifier_attr", diff --git a/Cargo.toml b/Cargo.toml index b35cb8a..13786cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,10 +25,10 @@ solana-compute-budget = { version = "=2.2.1" } solana-compute-budget-instruction = { version = "=2.2.1" } solana-fee-structure = { version = "=2.2.1" } solana-frozen-abi = { version = "=2.2.1", optional = true, features = [ - "frozen-abi", + "frozen-abi", ] } solana-frozen-abi-macro = { version = "=2.2.1", optional = true, features = [ - "frozen-abi", + "frozen-abi", ] } solana-hash = { version = "=2.2.1" } solana-instruction = { version = "=2.2.1", features = ["std"] } @@ -65,8 +65,8 @@ bincode = { version = "1.3.3" } ed25519-dalek = "=1.0.1" lazy_static = "1.5.0" libsecp256k1 = { version = "0.6.0", default-features = false, features = [ - "std", - "static-context", + "std", + "static-context", ] } openssl = "0.10" prost = "0.11.9" @@ -74,7 +74,9 @@ rand0-7 = { package = "rand", version = "0.7" } shuttle = "0.7.1" solana-account = { version = "=2.2.1", features = ["dev-context-only-utils"] } solana-clock = { version = "=2.2.1" } -solana-compute-budget = { version = "=2.2.1", features = ["dev-context-only-utils"] } +solana-compute-budget = { version = "=2.2.1", features = [ + "dev-context-only-utils", +] } solana-compute-budget-interface = { version = "=2.2.1" } solana-compute-budget-program = { version = "=2.2.1" } solana-ed25519-program = { version = "=2.2.1" } @@ -88,7 +90,9 @@ solana-rent = { version = "=2.2.1" } solana-sbpf = "0.10" solana-sdk = { version = "=2.2.1", features = ["dev-context-only-utils"] } solana-secp256k1-program = { version = "=2.2.1" } -solana-secp256r1-program = { version = "=2.2.1", features = ["openssl-vendored"] } +solana-secp256r1-program = { version = "=2.2.1", features = [ + "openssl-vendored", +] } solana-signature = { version = "=2.2.1" } solana-signer = { version = "=2.2.1" } # See order-crates-for-publishing.py for using this unusual `path = "."` @@ -97,7 +101,9 @@ solana-system-program = { version = "=2.2.1" } solana-system-transaction = { version = "=2.2.1" } solana-sysvar = { version = "=2.2.1" } solana-transaction = { version = "=2.2.1" } -solana-transaction-context = { version = "=2.2.1", features = ["dev-context-only-utils" ] } +solana-transaction-context = { version = "=2.2.1", features = [ + "dev-context-only-utils", +] } test-case = "3.3.1" solana-svm = { path = ".", features = ["dev-context-only-utils"] } @@ -107,18 +113,18 @@ targets = ["x86_64-unknown-linux-gnu"] [features] dev-context-only-utils = ["dep:qualifier_attr"] frozen-abi = [ - "dep:solana-frozen-abi", - "dep:solana-frozen-abi-macro", - "solana-compute-budget/frozen-abi", - "solana-program-runtime/frozen-abi", - "solana-sdk/frozen-abi", + "dep:solana-frozen-abi", + "dep:solana-frozen-abi-macro", + "solana-compute-budget/frozen-abi", + "solana-program-runtime/frozen-abi", + "solana-sdk/frozen-abi", ] shuttle-test = [ - "solana-type-overrides/shuttle-test", - "solana-program-runtime/shuttle-test", - "solana-bpf-loader-program/shuttle-test", - "solana-loader-v4-program/shuttle-test", + "solana-type-overrides/shuttle-test", + "solana-program-runtime/shuttle-test", + "solana-bpf-loader-program/shuttle-test", + "solana-loader-v4-program/shuttle-test", ] [patch.crates-io] -solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "68b667f" } +solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "a892d2" } From 7d30b64c6eafa2c2c255d014814df3cc8c6d3b04 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Sat, 6 Sep 2025 18:38:58 +0200 Subject: [PATCH 14/21] chore: remove duplicated CI runs --- .github/workflows/cargo-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cargo-test.yml b/.github/workflows/cargo-test.yml index cd5c17d..74fc83a 100644 --- a/.github/workflows/cargo-test.yml +++ b/.github/workflows/cargo-test.yml @@ -2,7 +2,8 @@ name: Cargo tests on: push: - branches: ["**"] + branches: + - main pull_request: permissions: From ed2d14526d4c5a70804b0c42ca66602afe5b3cd5 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Mon, 8 Sep 2025 10:54:35 +0200 Subject: [PATCH 15/21] chore: add comments --- src/account_loader.rs | 4 ++++ src/transaction_processor.rs | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/account_loader.rs b/src/account_loader.rs index 4474456..1966c41 100644 --- a/src/account_loader.rs +++ b/src/account_loader.rs @@ -216,6 +216,8 @@ impl<'a, CB: TransactionProcessingCallback> AccountLoader<'a, CB> { } } + /// If the fee payer is delegated, use it. Otherwise, load the escrow + /// account if delegated pub(crate) fn effective_fee_payer_address_for_failed_tx( &mut self, message: &impl SVMMessage, @@ -227,9 +229,11 @@ impl<'a, CB: TransactionProcessingCallback> AccountLoader<'a, CB> { .map(|acc| acc.account.delegated()) .unwrap_or(false) }; + // If the fee payer is delegated, use it if is_delegated(&fee_payer_address) { return fee_payer_address; } + // Otherwise, load the escrow account if delegated let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address); if is_delegated(&escrow_address) { return escrow_address; diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index 4f7caa1..5e18b65 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -609,7 +609,7 @@ impl TransactionBatchProcessor { let mut fee_payer_address = *message.fee_payer(); let mut loaded_fee_payer = { - // Load an account only if it exists *and* is delegated + // Load an account only if it exists *and* is delegated or privileged. let mut load_if_delegated_or_privileged = |addr: &Pubkey| -> Option { match account_loader.load_account(addr, true) { @@ -663,6 +663,7 @@ impl TransactionBatchProcessor { }; let fee_payer_index = 0; + // If the fee payer is privileged, it's exempt from fees. let fees = if loaded_fee_payer.account.privileged() { 0 } else { From bcbcbb18f57edf8a07430e9ad233bfac7d4e1540 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Mon, 8 Sep 2025 10:54:56 +0200 Subject: [PATCH 16/21] Update src/transaction_processor.rs Co-authored-by: Babur Makhmudov <31780624+bmuddha@users.noreply.github.com> --- src/transaction_processor.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index 5e18b65..7e684c8 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -612,7 +612,12 @@ impl TransactionBatchProcessor { // Load an account only if it exists *and* is delegated or privileged. let mut load_if_delegated_or_privileged = |addr: &Pubkey| -> Option { - match account_loader.load_account(addr, true) { + let mut load_if_delegated_or_privileged = |addr| { + account_loader + .load_account(addr, true) + .filter(|acc| acc.account.delegated() || acc.account.privileged()) + }; + Some(acc) if acc.account.delegated() || acc.account.privileged() => { Some(acc) } From 3560396f5fe48ce7c1f710c31fe98fe9c7e407bd Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Mon, 8 Sep 2025 11:00:43 +0200 Subject: [PATCH 17/21] chore: fix style --- src/transaction_processor.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index 7e684c8..2988098 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -1,4 +1,4 @@ -use crate::account_loader::{AccountsBalances, LoadedTransactionAccount}; +use crate::account_loader::{AccountsBalances}; use crate::escrow::ephemeral_balance_pda_from_payer; #[cfg(feature = "dev-context-only-utils")] use qualifier_attr::{field_qualifiers, qualifiers}; @@ -610,21 +610,12 @@ impl TransactionBatchProcessor { let mut loaded_fee_payer = { // Load an account only if it exists *and* is delegated or privileged. - let mut load_if_delegated_or_privileged = - |addr: &Pubkey| -> Option { - let mut load_if_delegated_or_privileged = |addr| { + let mut load_if_delegated_or_privileged = |addr: &Pubkey| { account_loader .load_account(addr, true) .filter(|acc| acc.account.delegated() || acc.account.privileged()) }; - Some(acc) if acc.account.delegated() || acc.account.privileged() => { - Some(acc) - } - _ => None, - } - }; - // 1) Prefer the fee payer if delegated if let Some(acc) = load_if_delegated_or_privileged(&fee_payer_address) { acc From e8b166f3ffa524f3058c0110e40117f0f4a8f455 Mon Sep 17 00:00:00 2001 From: Babur Makhmudov Date: Mon, 8 Sep 2025 15:33:19 +0400 Subject: [PATCH 18/21] chore: fmt cleanup --- src/transaction_processor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index 2988098..0348887 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -1,4 +1,4 @@ -use crate::account_loader::{AccountsBalances}; +use crate::account_loader::AccountsBalances; use crate::escrow::ephemeral_balance_pda_from_payer; #[cfg(feature = "dev-context-only-utils")] use qualifier_attr::{field_qualifiers, qualifiers}; From 33af2bbfaec090b3972d8b5ab6e56096e60e4dd5 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Mon, 8 Sep 2025 14:56:32 +0200 Subject: [PATCH 19/21] fix: propagate feepayer address --- src/account_loader.rs | 34 ++++++++++++++++++++++++++++++++-- src/transaction_processor.rs | 9 +++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/account_loader.rs b/src/account_loader.rs index 1966c41..6ae25d6 100644 --- a/src/account_loader.rs +++ b/src/account_loader.rs @@ -80,6 +80,7 @@ pub(crate) struct ValidatedTransactionDetails { pub(crate) compute_budget_limits: ComputeBudgetLimits, pub(crate) fee_details: FeeDetails, pub(crate) loaded_fee_payer_account: LoadedTransactionAccount, + pub(crate) fee_payer_address: Pubkey, } #[derive(PartialEq, Eq, Debug, Clone)] @@ -388,6 +389,7 @@ pub(crate) fn load_transaction( let load_result = load_transaction_accounts( account_loader, message, + &tx_details.fee_payer_address, tx_details.loaded_fee_payer_account, &tx_details.compute_budget_limits, error_metrics, @@ -427,6 +429,7 @@ struct LoadedTransactionAccounts { fn load_transaction_accounts( account_loader: &mut AccountLoader, message: &impl SVMMessage, + fee_payer_address: &Pubkey, loaded_fee_payer_account: LoadedTransactionAccount, compute_budget_limits: &ComputeBudgetLimits, error_metrics: &mut TransactionErrorMetrics, @@ -462,7 +465,7 @@ fn load_transaction_accounts( // Since the fee payer is always the first account, collect it first. // We can use it directly because it was already loaded during validation. - collect_loaded_account(message.fee_payer(), loaded_fee_payer_account)?; + collect_loaded_account(fee_payer_address, loaded_fee_payer_account)?; // Attempt to load and collect remaining non-fee payer accounts for (account_index, account_key) in account_keys.iter().enumerate().skip(1) { @@ -1071,10 +1074,26 @@ mod tests { Arc::new(FeatureSet::all_enabled()), 0, ); + // Build proper ValidatedTransactionDetails with real fee payer + let fee_payer = *tx.message().fee_payer(); + // In some tests we don't pass actual accounts; default to a zeroed account for fee payer + let fee_payer_account = callbacks + .accounts_map + .get(&fee_payer) + .cloned() + .unwrap_or_else(|| AccountSharedData::default()); + let validation_details = ValidatedTransactionDetails { + fee_payer_address: fee_payer, + loaded_fee_payer_account: LoadedTransactionAccount { + account: fee_payer_account, + ..LoadedTransactionAccount::default() + }, + ..ValidatedTransactionDetails::default() + }; load_transaction( &mut account_loader, &tx, - Ok(ValidatedTransactionDetails::default()), + Ok(validation_details), &mut error_metrics, &RentCollector::default(), ) @@ -1362,6 +1381,7 @@ mod tests { let result = load_transaction_accounts( &mut account_loader, sanitized_transaction.message(), + &fee_payer_address, LoadedTransactionAccount { loaded_size: fee_payer_account.data().len(), account: fee_payer_account.clone(), @@ -1425,6 +1445,7 @@ mod tests { let result = load_transaction_accounts( &mut account_loader, sanitized_transaction.message(), + &key1.pubkey(), LoadedTransactionAccount { account: fee_payer_account.clone(), ..LoadedTransactionAccount::default() @@ -1485,6 +1506,7 @@ mod tests { let result = load_transaction_accounts( &mut account_loader, sanitized_transaction.message(), + &key1.pubkey(), LoadedTransactionAccount::default(), &ComputeBudgetLimits::default(), &mut error_metrics, @@ -1527,6 +1549,7 @@ mod tests { let result = load_transaction_accounts( &mut account_loader, sanitized_transaction.message(), + &key1.pubkey(), LoadedTransactionAccount::default(), &ComputeBudgetLimits::default(), &mut error_metrics, @@ -1580,6 +1603,7 @@ mod tests { let result = load_transaction_accounts( &mut account_loader, sanitized_transaction.message(), + &key2.pubkey(), LoadedTransactionAccount { account: fee_payer_account.clone(), ..LoadedTransactionAccount::default() @@ -1644,6 +1668,7 @@ mod tests { let result = load_transaction_accounts( &mut account_loader, sanitized_transaction.message(), + &key2.pubkey(), LoadedTransactionAccount::default(), &ComputeBudgetLimits::default(), &mut error_metrics, @@ -1696,6 +1721,7 @@ mod tests { let result = load_transaction_accounts( &mut account_loader, sanitized_transaction.message(), + &key2.pubkey(), LoadedTransactionAccount::default(), &ComputeBudgetLimits::default(), &mut error_metrics, @@ -1756,6 +1782,7 @@ mod tests { let result = load_transaction_accounts( &mut account_loader, sanitized_transaction.message(), + &key2.pubkey(), LoadedTransactionAccount { account: fee_payer_account.clone(), ..LoadedTransactionAccount::default() @@ -1839,6 +1866,7 @@ mod tests { let result = load_transaction_accounts( &mut account_loader, sanitized_transaction.message(), + &key2.pubkey(), LoadedTransactionAccount { account: fee_payer_account.clone(), ..LoadedTransactionAccount::default() @@ -1991,6 +2019,7 @@ mod tests { account: fee_payer_account, ..LoadedTransactionAccount::default() }, + fee_payer_address: key2.pubkey(), ..ValidatedTransactionDetails::default() }); @@ -2352,6 +2381,7 @@ mod tests { let loaded_transaction_accounts = load_transaction_accounts( &mut account_loader, &transaction, + &fee_payer, LoadedTransactionAccount { account: fee_payer_account.clone(), loaded_size: fee_payer_size as usize, diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index 0348887..bf1c8ca 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -689,6 +689,7 @@ impl TransactionBatchProcessor { rollback_accounts, compute_budget_limits, loaded_fee_payer_account: loaded_fee_payer, + fee_payer_address, }) } @@ -2278,6 +2279,7 @@ mod tests { account: post_validation_fee_payer_account, rent_collected: fee_payer_rent_debit, }, + fee_payer_address: *fee_payer_address, }) ); } @@ -2373,6 +2375,7 @@ mod tests { account: post_validation_fee_payer_account, rent_collected: fee_payer_rent_debit, }, + fee_payer_address: *fee_payer_address, }) ); } @@ -2451,7 +2454,8 @@ mod tests { loaded_size: fee_payer_account.data().len(), account: post_validation_fee_payer_account, rent_collected: fee_payer_rent_debit, - } + }, + fee_payer_address: *fee_payer_address, }) ); } @@ -2709,7 +2713,8 @@ mod tests { loaded_size: fee_payer_account.data().len(), account: post_validation_fee_payer_account, rent_collected: 0, - } + }, + fee_payer_address: *fee_payer_address, }) ); } From 1a844efa3656ee16b929c0e0b193cb2857fc18f0 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Mon, 8 Sep 2025 15:36:41 +0200 Subject: [PATCH 20/21] fix: allow not delegated fee_payer if fees = 0 --- src/transaction_processor.rs | 43 ++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index bf1c8ca..7f1fe83 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -1,4 +1,4 @@ -use crate::account_loader::AccountsBalances; +use crate::account_loader::{AccountsBalances, LoadedTransactionAccount}; use crate::escrow::ephemeral_balance_pda_from_payer; #[cfg(feature = "dev-context-only-utils")] use qualifier_attr::{field_qualifiers, qualifiers}; @@ -608,27 +608,32 @@ impl TransactionBatchProcessor { let mut fee_payer_address = *message.fee_payer(); - let mut loaded_fee_payer = { - // Load an account only if it exists *and* is delegated or privileged. - let mut load_if_delegated_or_privileged = |addr: &Pubkey| { - account_loader - .load_account(addr, true) - .filter(|acc| acc.account.delegated() || acc.account.privileged()) - }; + let initial_loaded = account_loader.load_account(&fee_payer_address, true); - // 1) Prefer the fee payer if delegated - if let Some(acc) = load_if_delegated_or_privileged(&fee_payer_address) { + // zero-fee: short-circuit with an empty account, no delegation required, only if the account doesn't exist + let mut loaded_fee_payer = if fee_lamports_per_signature == 0 && initial_loaded.is_none() { + LoadedTransactionAccount { + account: AccountSharedData::default(), + loaded_size: 0, + rent_collected: 0, + } + } else if let Some(acc) = + initial_loaded.filter(|acc| acc.account.delegated() || acc.account.privileged()) + { + // prefer the fee payer if delegated/privileged + acc + } else { + // otherwise require escrow to exist and be delegated/privileged + let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address); + if let Some(acc) = account_loader + .load_account(&escrow_address, true) + .filter(|acc| acc.account.delegated() || acc.account.privileged()) + { + fee_payer_address = escrow_address; acc } else { - // 2) Otherwise require escrow to exist and be delegated - let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address); - if let Some(acc) = load_if_delegated_or_privileged(&escrow_address) { - fee_payer_address = escrow_address; - acc - } else { - error_counters.invalid_account_for_fee += 1; - return Err(TransactionError::InvalidAccountForFee); - } + error_counters.invalid_account_for_fee += 1; + return Err(TransactionError::InvalidAccountForFee); } }; From bb77c25a7cc9b2c3936cfe4ab74f49380596c99a Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Tue, 9 Sep 2025 11:02:45 +0200 Subject: [PATCH 21/21] fix: gasless existing feepayer --- src/transaction_processor.rs | 44 +++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/transaction_processor.rs b/src/transaction_processor.rs index 7f1fe83..c3dcd55 100644 --- a/src/transaction_processor.rs +++ b/src/transaction_processor.rs @@ -610,31 +610,33 @@ impl TransactionBatchProcessor { let initial_loaded = account_loader.load_account(&fee_payer_address, true); - // zero-fee: short-circuit with an empty account, no delegation required, only if the account doesn't exist - let mut loaded_fee_payer = if fee_lamports_per_signature == 0 && initial_loaded.is_none() { - LoadedTransactionAccount { + let is_delegated_or_privileged = + |acc: &LoadedTransactionAccount| acc.account.delegated() || acc.account.privileged(); + + let mut loaded_fee_payer = if fee_lamports_per_signature == 0 { + // zero-fee: use provided account if any, otherwise an empty default + initial_loaded.unwrap_or_else(|| LoadedTransactionAccount { account: AccountSharedData::default(), loaded_size: 0, rent_collected: 0, - } - } else if let Some(acc) = - initial_loaded.filter(|acc| acc.account.delegated() || acc.account.privileged()) - { - // prefer the fee payer if delegated/privileged - acc + }) } else { - // otherwise require escrow to exist and be delegated/privileged - let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address); - if let Some(acc) = account_loader - .load_account(&escrow_address, true) - .filter(|acc| acc.account.delegated() || acc.account.privileged()) - { - fee_payer_address = escrow_address; - acc - } else { - error_counters.invalid_account_for_fee += 1; - return Err(TransactionError::InvalidAccountForFee); - } + initial_loaded + .filter(is_delegated_or_privileged) + .or_else(|| { + let escrow_address = ephemeral_balance_pda_from_payer(&fee_payer_address); + account_loader + .load_account(&escrow_address, true) + .filter(is_delegated_or_privileged) + .map(|acc| { + fee_payer_address = escrow_address; + acc + }) + }) + .ok_or_else(|| { + error_counters.invalid_account_for_fee += 1; + TransactionError::InvalidAccountForFee + })? }; let fee_payer_loaded_rent_epoch = loaded_fee_payer.account.rent_epoch();