Skip to content

Commit 635d9d3

Browse files
committed
add test, cleanup
1 parent 09b243e commit 635d9d3

File tree

2 files changed

+181
-17
lines changed

2 files changed

+181
-17
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ check-cfg = [
180180
]
181181

182182
[workspace.lints.rust]
183-
# XXX HANA warnings = "deny"
183+
warnings = "deny"
184184

185185
# Clippy lint configuration that can not be applied in clippy.toml
186186
[workspace.lints.clippy]

svm/src/account_loader.rs

Lines changed: 180 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -779,22 +779,6 @@ fn load_transaction_accounts_old<CB: TransactionProcessingCallback>(
779779
}
780780

781781
if !validated_loaders.contains(owner_id) {
782-
// NOTE there are several feature gate activations that affect this code:
783-
// * `remove_accounts_executable_flag_checks`: this implicitly makes system, vote, stake, et al valid loaders
784-
// it is impossible to mark an account executable and also have it be owned by one of them
785-
// so, with the feature disabled, we always fail the executable check if they are a program id owner
786-
// however, with the feature enabled, any account owned by an account owned by native loader is a "program"
787-
// this is benign (any such transaction will fail at execution) but it affects which transactions pay fees
788-
// * `enable_transaction_loading_failure_fees`: loading failures behave the same as execution failures
789-
// at this point we can restrict valid loaders to those contained in `PROGRAM_OWNERS`
790-
// since any other pseudo-loader owner is destined to fail at execution
791-
// * SIMD-186: explicitly defines a sensible transaction data size algorithm
792-
// at this point we stop counting loaders toward transaction data size entirely
793-
//
794-
// when _all three_ of `remove_accounts_executable_flag_checks`, `enable_transaction_loading_failure_fees`,
795-
// and SIMD-186 are active, we do not need to load loaders at all to comply with consensus rules
796-
// we may verify program ids are owned by `PROGRAM_OWNERS` purely as an optimization
797-
// this could even be done before loading the rest of the accounts for a transaction
798782
if let Some(owner_account) = account_loader.load_account(owner_id) {
799783
if !native_loader::check_id(owner_account.owner())
800784
|| (!account_loader
@@ -938,6 +922,7 @@ mod tests {
938922
super::*,
939923
crate::transaction_account_state_info::TransactionAccountStateInfo,
940924
agave_reserved_account_keys::ReservedAccountKeys,
925+
rand0_7::prelude::*,
941926
solana_account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
942927
solana_epoch_schedule::EpochSchedule,
943928
solana_hash::Hash,
@@ -2979,4 +2964,183 @@ mod tests {
29792964
assert_eq!(account_loader.load_account(&fee_payer), None);
29802965
assert_eq!(account_loader.get_account_shared_data(&fee_payer), None);
29812966
}
2967+
2968+
// note all magic numbers (how many accounts, how many instructions, how big to size buffers) are arbitrary
2969+
// other than trying not to swamp programs with blank accounts and keep transaction size below the 64mb limit
2970+
#[test_case(false; "executable_mandatory")]
2971+
#[test_case(true; "executable_optional")]
2972+
fn test_load_transaction_accounts_data_sizes_simd186(
2973+
remove_accounts_executable_flag_checks: bool,
2974+
) {
2975+
let mut rng = rand0_7::thread_rng();
2976+
let mut mock_bank = TestCallbacks::default();
2977+
mock_bank.feature_set.remove_accounts_executable_flag_checks =
2978+
remove_accounts_executable_flag_checks;
2979+
2980+
// arbitrary accounts
2981+
for _ in 0..128 {
2982+
let account = AccountSharedData::create(
2983+
1,
2984+
vec![0; rng.gen_range(0, 128)],
2985+
Pubkey::new_unique(),
2986+
rng.gen(),
2987+
u64::MAX,
2988+
);
2989+
mock_bank.accounts_map.insert(Pubkey::new_unique(), account);
2990+
}
2991+
2992+
// fee-payers
2993+
let mut fee_payers = vec![];
2994+
for _ in 0..8 {
2995+
let fee_payer = Pubkey::new_unique();
2996+
let account = AccountSharedData::create(
2997+
LAMPORTS_PER_SOL,
2998+
vec![0; rng.gen_range(0, 32)],
2999+
system_program::id(),
3000+
rng.gen(),
3001+
u64::MAX,
3002+
);
3003+
mock_bank.accounts_map.insert(fee_payer, account);
3004+
fee_payers.push(fee_payer);
3005+
}
3006+
3007+
// programs
3008+
let mut loader_owned_accounts = vec![];
3009+
let mut programdata_tracker = AHashMap::new();
3010+
for loader in PROGRAM_OWNERS {
3011+
for _ in 0..16 {
3012+
let program_id = Pubkey::new_unique();
3013+
let mut account = AccountSharedData::create(
3014+
1,
3015+
vec![0; rng.gen_range(0, 512)],
3016+
*loader,
3017+
!remove_accounts_executable_flag_checks || rng.gen(),
3018+
u64::MAX,
3019+
);
3020+
3021+
// give half loaderv3 accounts (if theyre long enough) a valid programdata
3022+
// a quarter a dead pointer and a quarter nothing
3023+
// we set executable like a program because after the flag is disabled...
3024+
// ...programdata and buffer accounts can be used as program ids without aborting loading
3025+
// this will always fail at execution but we are merely testing the data size accounting here
3026+
if *loader == bpf_loader_upgradeable::id() && account.data().len() >= 64 {
3027+
let programdata_address = Pubkey::new_unique();
3028+
let has_programdata = rng.gen();
3029+
3030+
if has_programdata {
3031+
let programdata_account = AccountSharedData::create(
3032+
1,
3033+
vec![0; rng.gen_range(0, 512)],
3034+
*loader,
3035+
!remove_accounts_executable_flag_checks || rng.gen(),
3036+
u64::MAX,
3037+
);
3038+
programdata_tracker.insert(
3039+
program_id,
3040+
(programdata_address, programdata_account.data().len()),
3041+
);
3042+
mock_bank
3043+
.accounts_map
3044+
.insert(programdata_address, programdata_account);
3045+
loader_owned_accounts.push(programdata_address);
3046+
}
3047+
3048+
if has_programdata || rng.gen() {
3049+
account
3050+
.set_state(&UpgradeableLoaderState::Program {
3051+
programdata_address,
3052+
})
3053+
.unwrap();
3054+
}
3055+
}
3056+
3057+
mock_bank.accounts_map.insert(program_id, account);
3058+
loader_owned_accounts.push(program_id);
3059+
}
3060+
}
3061+
3062+
let mut all_accounts = mock_bank.accounts_map.keys().copied().collect::<Vec<_>>();
3063+
let mut account_loader = (&mock_bank).into();
3064+
3065+
// now generate arbitrary transactions using this accounts
3066+
// we ensure valid fee-payers and that all program ids are loader-owned
3067+
// otherwise any account can appear anywhere
3068+
// some edge cases we hope to hit (not necessarily all in every run):
3069+
// * programs used multiple times as program ids and/or normal accounts are counted once
3070+
// * loaderv3 programdata used explicitly zero one or multiple times is counted once
3071+
// * loaderv3 programs with missing programdata are allowed through
3072+
// * loaderv3 programdata used as program id does nothing weird
3073+
// * loaderv3 programdata used as a regular account does nothing weird
3074+
// * the programdata conditions hold regardless of ordering
3075+
for _ in 0..1024 {
3076+
let mut instructions = vec![];
3077+
for _ in 0..rng.gen_range(1, 8) {
3078+
let mut accounts = vec![];
3079+
for _ in 0..rng.gen_range(1, 16) {
3080+
all_accounts.shuffle(&mut rng);
3081+
let pubkey = all_accounts[0];
3082+
3083+
accounts.push(AccountMeta {
3084+
pubkey,
3085+
is_writable: rng.gen(),
3086+
is_signer: rng.gen() && rng.gen(),
3087+
});
3088+
}
3089+
3090+
loader_owned_accounts.shuffle(&mut rng);
3091+
let program_id = loader_owned_accounts[0];
3092+
instructions.push(Instruction {
3093+
accounts,
3094+
program_id,
3095+
data: vec![],
3096+
});
3097+
}
3098+
3099+
fee_payers.shuffle(&mut rng);
3100+
let fee_payer = fee_payers[0];
3101+
let fee_payer_account = mock_bank.accounts_map.get(&fee_payer).cloned().unwrap();
3102+
3103+
let transaction = SanitizedTransaction::from_transaction_for_tests(
3104+
Transaction::new_with_payer(&instructions, Some(&fee_payer)),
3105+
);
3106+
3107+
let mut counted_programdatas: AHashSet<_> =
3108+
transaction.account_keys().iter().copied().collect();
3109+
let mut expected_size = 0;
3110+
for pubkey in transaction.account_keys().iter() {
3111+
let account = mock_bank.accounts_map.get(pubkey).unwrap();
3112+
expected_size += TRANSACTION_ACCOUNT_BASE_SIZE + account.data().len();
3113+
3114+
if let Some((programdata_address, programdata_size)) =
3115+
programdata_tracker.get(pubkey)
3116+
{
3117+
if counted_programdatas.get(programdata_address).is_none() {
3118+
expected_size += TRANSACTION_ACCOUNT_BASE_SIZE + programdata_size;
3119+
counted_programdatas.insert(*programdata_address);
3120+
}
3121+
}
3122+
}
3123+
3124+
assert!(expected_size <= MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get() as usize);
3125+
3126+
let loaded_transaction_accounts = load_transaction_accounts(
3127+
&mut account_loader,
3128+
&transaction,
3129+
LoadedTransactionAccount {
3130+
loaded_size: TRANSACTION_ACCOUNT_BASE_SIZE + fee_payer_account.data().len(),
3131+
account: fee_payer_account,
3132+
rent_collected: 0,
3133+
},
3134+
MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
3135+
&mut TransactionErrorMetrics::default(),
3136+
&RentCollector::default(),
3137+
)
3138+
.unwrap();
3139+
3140+
assert_eq!(
3141+
loaded_transaction_accounts.loaded_accounts_data_size,
3142+
expected_size as u32,
3143+
);
3144+
}
3145+
}
29823146
}

0 commit comments

Comments
 (0)