diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b70c0927dbe14..916d801e4ae443 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,19 @@ or tools-version = "1.43" ``` The order of precedence for the chosen tools version goes: `--tools-version` argument, package version, workspace version, and finally default version. + * `package-metadata`: specify a program's id in Cargo.toml for easy consumption by downstream users and tools using `solana-package-metadata` (#1806). For example: +```toml +[package.metadata.solana] +program-id = "MyProgram1111111111111111111111111111111111" +``` +Can be consumed in the program crate: +```rust +solana_package_metadata::declare_id_with_package_metadata!("solana.program-id"); +``` +This is equivalent to writing: +```rust +solana_pubkey::declare_id!("MyProgram1111111111111111111111111111111111"); +``` * `agave-validator`: Update PoH speed check to compare against current hash rate from a Bank (#2447) * `solana-test-validator`: Add `--clone-feature-set` flag to mimic features from a target cluster (#2480) * `solana-genesis`: the `--cluster-type` parameter now clones the feature set from the target cluster (#2587) diff --git a/Cargo.lock b/Cargo.lock index fab981300a2ece..3d26e8d5cd9359 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5652,8 +5652,10 @@ dependencies = [ "solana-account", "solana-frozen-abi", "solana-frozen-abi-macro", + "solana-instruction", "solana-logger", "solana-program", + "solana-pubkey", ] [[package]] @@ -6659,9 +6661,13 @@ name = "solana-feature-set" version = "2.1.0" dependencies = [ "lazy_static", + "solana-clock", + "solana-epoch-schedule", "solana-frozen-abi", "solana-frozen-abi-macro", - "solana-program", + "solana-hash", + "solana-pubkey", + "solana-sha256-hasher", ] [[package]] @@ -6843,7 +6849,7 @@ name = "solana-inline-spl" version = "2.1.0" dependencies = [ "bytemuck", - "solana-program", + "solana-pubkey", ] [[package]] @@ -6873,6 +6879,7 @@ dependencies = [ "clap 3.2.23", "dirs-next", "num_cpus", + "serde_json", "solana-clap-v3-utils", "solana-cli-config", "solana-derivation-path", @@ -7038,6 +7045,7 @@ dependencies = [ "byte-unit", "clap 3.2.23", "serde", + "serde_derive", "serde_json", "solana-logger", "solana-version", @@ -7113,6 +7121,10 @@ dependencies = [ "solana-define-syscall", ] +[[package]] +name = "solana-native-token" +version = "2.1.0" + [[package]] name = "solana-net-shaper" version = "2.1.0" @@ -7161,6 +7173,14 @@ dependencies = [ "solana-sdk", ] +[[package]] +name = "solana-package-metadata" +version = "2.1.0" +dependencies = [ + "solana-package-metadata-macro", + "solana-pubkey", +] + [[package]] name = "solana-package-metadata-macro" version = "2.1.0" @@ -7336,6 +7356,8 @@ dependencies = [ "solana-instruction", "solana-logger", "solana-msg", + "solana-native-token", + "solana-program-entrypoint", "solana-program-error", "solana-program-memory", "solana-program-option", @@ -7356,6 +7378,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "solana-program-entrypoint" +version = "2.1.0" +dependencies = [ + "solana-account-info", + "solana-msg", + "solana-program-error", + "solana-pubkey", +] + [[package]] name = "solana-program-error" version = "2.1.0" @@ -7915,6 +7947,7 @@ dependencies = [ "solana-frozen-abi", "solana-frozen-abi-macro", "solana-logger", + "solana-native-token", "solana-program", "solana-program-memory", "solana-pubkey", @@ -8772,6 +8805,7 @@ dependencies = [ "bytemuck_derive", "curve25519-dalek 4.1.3", "itertools 0.12.1", + "js-sys", "lazy_static", "merlin", "num-derive", @@ -8787,6 +8821,7 @@ dependencies = [ "subtle", "thiserror", "tiny-bip39", + "wasm-bindgen", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index 468cc969cfff88..4eb33cc728d281 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,7 +100,6 @@ members = [ "rpc-test", "runtime", "runtime-transaction", - "sanitize", "sdk", "sdk/account", "sdk/account-info", @@ -118,14 +117,18 @@ members = [ "sdk/instruction", "sdk/macro", "sdk/msg", + "sdk/native-token", + "sdk/package-metadata", "sdk/package-metadata-macro", "sdk/program", + "sdk/program-entrypoint", "sdk/program-error", "sdk/program-memory", "sdk/program-option", "sdk/program-pack", "sdk/pubkey", "sdk/rent", + "sdk/sanitize", "sdk/serde-varint", "sdk/serialize-utils", "sdk/sha256-hasher", @@ -407,6 +410,7 @@ solana-define-syscall = { path = "define-syscall", version = "=2.1.0" } solana-derivation-path = { path = "sdk/derivation-path", version = "=2.1.0" } solana-download-utils = { path = "download-utils", version = "=2.1.0" } solana-entry = { path = "entry", version = "=2.1.0" } +solana-program-entrypoint = { path = "sdk/program-entrypoint", version = "=2.1.0" } solana-epoch-schedule = { path = "sdk/epoch-schedule", version = "=2.1.0" } solana-faucet = { path = "faucet", version = "=2.1.0" } solana-feature-set = { path = "sdk/feature-set", version = "=2.1.0" } @@ -432,9 +436,11 @@ solana-measure = { path = "measure", version = "=2.1.0" } solana-merkle-tree = { path = "merkle-tree", version = "=2.1.0" } solana-metrics = { path = "metrics", version = "=2.1.0" } solana-msg = { path = "sdk/msg", version = "=2.1.0" } +solana-native-token = { path = "sdk/native-token", version = "=2.1.0" } solana-net-utils = { path = "net-utils", version = "=2.1.0" } solana-nohash-hasher = "0.2.1" solana-notifier = { path = "notifier", version = "=2.1.0" } +solana-package-metadata = { path = "sdk/package-metadata", version = "=2.1.0" } solana-package-metadata-macro = { path = "sdk/package-metadata-macro", version = "=2.1.0" } solana-perf = { path = "perf", version = "=2.1.0" } solana-poh = { path = "poh", version = "=2.1.0" } @@ -453,7 +459,7 @@ solana-quic-client = { path = "quic-client", version = "=2.1.0" } solana-rayon-threadlimit = { path = "rayon-threadlimit", version = "=2.1.0" } solana-remote-wallet = { path = "remote-wallet", version = "=2.1.0", default-features = false } solana-rent = { path = "sdk/rent", version = "=2.1.0", default-features = false } -solana-sanitize = { path = "sanitize", version = "=2.1.0" } +solana-sanitize = { path = "sdk/sanitize", version = "=2.1.0" } solana-serde-varint = { path = "sdk/serde-varint", version = "=2.1.0" } solana-serialize-utils = { path = "sdk/serialize-utils", version = "=2.1.0" } solana-sha256-hasher = { path = "sdk/sha256-hasher", version = "=2.1.0" } diff --git a/accounts-db/src/accounts.rs b/accounts-db/src/accounts.rs index 2584f900edbc49..cce35988aff69d 100644 --- a/accounts-db/src/accounts.rs +++ b/accounts-db/src/accounts.rs @@ -81,12 +81,14 @@ impl Accounts { } } + /// Return loaded addresses and the deactivation slot. + /// If the table hasn't been deactivated, the deactivation slot is `u64::MAX`. pub fn load_lookup_table_addresses( &self, ancestors: &Ancestors, address_table_lookup: SVMMessageAddressTableLookup, slot_hashes: &SlotHashes, - ) -> std::result::Result { + ) -> std::result::Result<(LoadedAddresses, Slot), AddressLookupError> { let table_account = self .accounts_db .load_with_fixed_root(ancestors, address_table_lookup.account_key) @@ -98,18 +100,21 @@ impl Accounts { let lookup_table = AddressLookupTable::deserialize(table_account.data()) .map_err(|_ix_err| AddressLookupError::InvalidAccountData)?; - Ok(LoadedAddresses { - writable: lookup_table.lookup( - current_slot, - address_table_lookup.writable_indexes, - slot_hashes, - )?, - readonly: lookup_table.lookup( - current_slot, - address_table_lookup.readonly_indexes, - slot_hashes, - )?, - }) + Ok(( + LoadedAddresses { + writable: lookup_table.lookup( + current_slot, + address_table_lookup.writable_indexes, + slot_hashes, + )?, + readonly: lookup_table.lookup( + current_slot, + address_table_lookup.readonly_indexes, + slot_hashes, + )?, + }, + lookup_table.meta.deactivation_slot, + )) } else { Err(AddressLookupError::InvalidAccountOwner) } @@ -806,10 +811,13 @@ mod tests { SVMMessageAddressTableLookup::from(&address_table_lookup), &SlotHashes::default(), ), - Ok(LoadedAddresses { - writable: vec![table_addresses[0]], - readonly: vec![table_addresses[1]], - }), + Ok(( + LoadedAddresses { + writable: vec![table_addresses[0]], + readonly: vec![table_addresses[1]], + }, + u64::MAX + )), ); } diff --git a/accounts-db/src/accounts_db.rs b/accounts-db/src/accounts_db.rs index 29582ab7e1ff7f..b16d5700157609 100644 --- a/accounts-db/src/accounts_db.rs +++ b/accounts-db/src/accounts_db.rs @@ -97,7 +97,7 @@ use { std::{ borrow::Cow, boxed::Box, - collections::{BTreeSet, HashMap, HashSet}, + collections::{BTreeSet, HashMap, HashSet, VecDeque}, fs, hash::{Hash as StdHash, Hasher as StdHasher}, io::Result as IoResult, @@ -141,6 +141,11 @@ const MAX_ITEMS_PER_CHUNK: Slot = 2_500; // This allows us to split up accounts index accesses across multiple threads. const SHRINK_COLLECT_CHUNK_SIZE: usize = 50; +/// The number of shrink candidate slots that is small enough so that +/// additional storages from ancient slots can be added to the +/// candidates for shrinking. +const SHRINK_INSERT_ANCIENT_THRESHOLD: usize = 10; + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum CreateAncientStorage { /// ancient storages are created by appending @@ -1501,6 +1506,13 @@ pub struct AccountsDb { /// Flag to indicate if the experimental accounts lattice hash is enabled. /// (For R&D only; a feature-gate also exists to turn this on and make it a part of consensus.) pub is_experimental_accumulator_hash_enabled: AtomicBool, + + /// These are the ancient storages that could be valuable to + /// shrink, sorted by amount of dead bytes. The elements + /// are sorted from the largest dead bytes to the smallest. + /// Members are Slot and capacity. If capacity is smaller, then + /// that means the storage was already shrunk. + pub(crate) best_ancient_slots_to_shrink: RwLock>, } /// results from 'split_storages_ancient' @@ -1860,6 +1872,7 @@ impl AccountsDb { is_experimental_accumulator_hash_enabled: default_accounts_db_config .enable_experimental_accumulator_hash .into(), + best_ancient_slots_to_shrink: RwLock::default(), } } @@ -4401,8 +4414,12 @@ impl AccountsDb { let shrink_candidates_slots = std::mem::take(&mut *self.shrink_candidate_slots.lock().unwrap()); + self.shrink_stats + .initial_candidates_count + .store(shrink_candidates_slots.len() as u64, Ordering::Relaxed); + let candidates_count = shrink_candidates_slots.len(); - let ((shrink_slots, shrink_slots_next_batch), select_time_us) = measure_us!({ + let ((mut shrink_slots, shrink_slots_next_batch), select_time_us) = measure_us!({ if let AccountShrinkThreshold::TotalSpace { shrink_ratio } = self.shrink_ratio { let (shrink_slots, shrink_slots_next_batch) = self.select_candidates_by_total_usage(&shrink_candidates_slots, shrink_ratio); @@ -4423,6 +4440,29 @@ impl AccountsDb { } }); + // If there are too few slots to shrink, add an ancient slot + // for shrinking. + if shrink_slots.len() < SHRINK_INSERT_ANCIENT_THRESHOLD { + let mut ancients = self.best_ancient_slots_to_shrink.write().unwrap(); + while let Some((slot, capacity)) = ancients.pop_front() { + if let Some(store) = self.storage.get_slot_storage_entry(slot) { + if !shrink_slots.contains(&slot) + && capacity == store.capacity() + && Self::is_candidate_for_shrink(self, &store) + { + let ancient_bytes_added_to_shrink = store.alive_bytes() as u64; + shrink_slots.insert(slot, store); + self.shrink_stats + .ancient_bytes_added_to_shrink + .fetch_add(ancient_bytes_added_to_shrink, Ordering::Relaxed); + self.shrink_stats + .ancient_slots_added_to_shrink + .fetch_add(1, Ordering::Relaxed); + break; + } + } + } + } if shrink_slots.is_empty() && shrink_slots_next_batch .as_ref() @@ -9252,7 +9292,9 @@ pub mod tests { accounts_hash::MERKLE_FANOUT, accounts_index::{tests::*, AccountSecondaryIndexesIncludeExclude}, ancient_append_vecs, - append_vec::{test_utils::TempFile, AppendVec, AppendVecStoredAccountMeta}, + append_vec::{ + aligned_stored_size, test_utils::TempFile, AppendVec, AppendVecStoredAccountMeta, + }, storable_accounts::AccountForStorage, }, assert_matches::assert_matches, @@ -12154,6 +12196,83 @@ pub mod tests { ); } + /// This test creates an ancient storage with three alive accounts + /// of various sizes. It then simulates killing one of the + /// accounts in a more recent (non-ancient) slot by overwriting + /// the account that has the smallest data size. The dead account + /// is expected to be deleted from its ancient storage in the + /// process of shrinking candidate slots. The capacity of the + /// storage after shrinking is expected to be the sum of alive + /// bytes of the two remaining alive ancient accounts. + #[test] + fn test_shrink_candidate_slots_with_dead_ancient_account() { + solana_logger::setup(); + let epoch_schedule = EpochSchedule::default(); + let num_ancient_slots = 3; + // Prepare 3 append vecs to combine [medium, big, small] + let account_data_sizes = vec![1000, 2000, 150]; + let (db, starting_ancient_slot) = + create_db_with_storages_and_index_with_customized_account_size_per_slot( + true, + num_ancient_slots, + account_data_sizes, + ); + db.add_root(starting_ancient_slot); + let slots_to_combine: Vec = + (starting_ancient_slot..starting_ancient_slot + num_ancient_slots as Slot).collect(); + db.combine_ancient_slots(slots_to_combine, CAN_RANDOMLY_SHRINK_FALSE); + let storage = db.get_storage_for_slot(starting_ancient_slot).unwrap(); + let ancient_accounts = db.get_unique_accounts_from_storage(&storage); + // Check that three accounts are indeed present in the combined storage. + assert_eq!(ancient_accounts.stored_accounts.len(), 3); + // Find an ancient account with smallest data length. + // This will be a dead account, overwritten in the current slot. + let modified_account_pubkey = ancient_accounts + .stored_accounts + .iter() + .min_by(|a, b| a.data_len.cmp(&b.data_len)) + .unwrap() + .pubkey; + let modified_account_owner = *AccountSharedData::default().owner(); + let modified_account = AccountSharedData::new(223, 0, &modified_account_owner); + let ancient_append_vec_offset = db.ancient_append_vec_offset.unwrap().abs(); + let current_slot = epoch_schedule.slots_per_epoch + ancient_append_vec_offset as u64 + 1; + // Simulate killing of the ancient account by overwriting it in the current slot. + db.store_for_tests( + current_slot, + &[(&modified_account_pubkey, &modified_account)], + ); + db.calculate_accounts_delta_hash(current_slot); + db.add_root_and_flush_write_cache(current_slot); + // This should remove the dead ancient account from the index. + db.clean_accounts_for_tests(); + db.shrink_ancient_slots(&epoch_schedule); + let storage = db.get_storage_for_slot(starting_ancient_slot).unwrap(); + let created_accounts = db.get_unique_accounts_from_storage(&storage); + // The dead account should still be in the ancient storage, + // because the storage wouldn't be shrunk with normal alive to + // capacity ratio. + assert_eq!(created_accounts.stored_accounts.len(), 3); + db.shrink_candidate_slots(&epoch_schedule); + let storage = db.get_storage_for_slot(starting_ancient_slot).unwrap(); + let created_accounts = db.get_unique_accounts_from_storage(&storage); + // At this point the dead ancient account should be removed + // and storage capacity shrunk to the sum of alive bytes of + // accounts it holds. This is the data lengths of the + // accounts plus the length of their metadata. + assert_eq!( + created_accounts.capacity as usize, + aligned_stored_size(1000) + aligned_stored_size(2000) + ); + // The above check works only when the AppendVec storage is + // used. More generally the pubkey of the smallest account + // shouldn't be present in the shrunk storage, which is + // validated by the following scan of the storage accounts. + storage.accounts.scan_pubkeys(|pubkey| { + assert_ne!(pubkey, &modified_account_pubkey); + }); + } + #[test] fn test_select_candidates_by_total_usage_no_candidates() { // no input candidates -- none should be selected diff --git a/accounts-db/src/ancient_append_vecs.rs b/accounts-db/src/ancient_append_vecs.rs index 9c788c8e668917..148c543b4c6302 100644 --- a/accounts-db/src/ancient_append_vecs.rs +++ b/accounts-db/src/ancient_append_vecs.rs @@ -21,7 +21,7 @@ use { solana_measure::measure_us, solana_sdk::clock::Slot, std::{ - collections::HashMap, + collections::{HashMap, VecDeque}, num::{NonZeroU64, Saturating}, sync::{atomic::Ordering, Arc, Mutex}, }, @@ -79,6 +79,9 @@ struct AncientSlotInfos { total_alive_bytes_shrink: Saturating, /// total alive bytes across all slots total_alive_bytes: Saturating, + /// slots that have dead accounts and thus the corresponding slot + /// storages can be shrunk + best_slots_to_shrink: VecDeque<(Slot, u64)>, } impl AncientSlotInfos { @@ -177,8 +180,14 @@ impl AncientSlotInfos { * tuning.percent_of_alive_shrunk_data / 100, ); + // At this point self.shrink_indexes have been sorted by the + // largest amount of dead bytes first in the corresponding + // storages. + self.best_slots_to_shrink = VecDeque::with_capacity(self.shrink_indexes.len()); for info_index in &self.shrink_indexes { let info = &mut self.all_infos[*info_index]; + self.best_slots_to_shrink + .push_back((info.slot, info.capacity)); if bytes_to_shrink_due_to_ratio.0 >= threshold_bytes { // we exceeded the amount to shrink due to alive ratio, so don't shrink this one just due to 'should_shrink' // It MAY be shrunk based on total capacity still. @@ -396,7 +405,12 @@ impl AccountsDb { self.shrink_ancient_stats .slots_considered .fetch_add(sorted_slots.len() as u64, Ordering::Relaxed); - let ancient_slot_infos = self.collect_sort_filter_ancient_slots(sorted_slots, &tuning); + let mut ancient_slot_infos = self.collect_sort_filter_ancient_slots(sorted_slots, &tuning); + + std::mem::swap( + &mut *self.best_ancient_slots_to_shrink.write().unwrap(), + &mut ancient_slot_infos.best_slots_to_shrink, + ); if ancient_slot_infos.all_infos.is_empty() { return; // nothing to do diff --git a/bucket_map/src/bucket_storage.rs b/bucket_map/src/bucket_storage.rs index a55923c142d850..95f05cfdfaa679 100644 --- a/bucket_map/src/bucket_storage.rs +++ b/bucket_map/src/bucket_storage.rs @@ -433,6 +433,10 @@ impl BucketStorage { std::env::current_dir(), ); }); + // Access to the disk bucket files are random (excluding the linear search on collisions), + // so advise the kernel to treat the mmaps as such. + #[cfg(unix)] + mmap.advise(memmap2::Advice::Random).unwrap(); measure_mmap.stop(); stats .new_file_us diff --git a/core/src/banking_stage/consume_worker.rs b/core/src/banking_stage/consume_worker.rs index b89457bfca7bb6..f6d2b6e5f086ea 100644 --- a/core/src/banking_stage/consume_worker.rs +++ b/core/src/banking_stage/consume_worker.rs @@ -107,7 +107,7 @@ impl ConsumeWorker { let output = self.consumer.process_and_record_aged_transactions( bank, &work.transactions, - &work.max_age_slots, + &work.max_ages, ); self.metrics.update_for_consume(&output); @@ -702,16 +702,33 @@ mod tests { get_tmp_ledger_path_auto_delete, leader_schedule_cache::LeaderScheduleCache, }, solana_poh::poh_recorder::{PohRecorder, WorkingBankEntry}, - solana_prio_graph_scheduler::scheduler_messages::{TransactionBatchId, TransactionId}, + solana_prio_graph_scheduler::scheduler_messages::{ + MaxAge, TransactionBatchId, TransactionId, + }, solana_runtime::{ bank_forks::BankForks, prioritization_fee_cache::PrioritizationFeeCache, vote_sender_types::ReplayVoteReceiver, }, solana_sdk::{ - genesis_config::GenesisConfig, poh_config::PohConfig, pubkey::Pubkey, - signature::Keypair, system_transaction, + address_lookup_table::AddressLookupTableAccount, + clock::{Slot, MAX_PROCESSING_AGE}, + genesis_config::GenesisConfig, + message::{ + v0::{self, LoadedAddresses}, + SimpleAddressLoader, VersionedMessage, + }, + poh_config::PohConfig, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + system_instruction, system_transaction, + transaction::{ + MessageHash, SanitizedTransaction, TransactionError, VersionedTransaction, + }, }, + solana_svm_transaction::svm_message::SVMMessage, std::{ + collections::HashSet, sync::{atomic::AtomicBool, RwLock}, thread::JoinHandle, }, @@ -742,6 +759,7 @@ mod tests { .. } = create_slow_genesis_config(10_000); let (bank, bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); + let bank = Arc::new(Bank::new_from_parent(bank, &Pubkey::new_unique(), 1)); let ledger_path = get_tmp_ledger_path_auto_delete!(); let blockstore = Blockstore::open(ledger_path.path()) @@ -820,17 +838,21 @@ mod tests { )]); let bid = TransactionBatchId::new(0); let id = TransactionId::new(0); + let max_age = MaxAge { + epoch_invalidation_slot: bank.slot(), + alt_invalidation_slot: bank.slot(), + }; let work = ConsumeWork { batch_id: bid, ids: vec![id], transactions, - max_age_slots: vec![bank.slot()], + max_ages: vec![max_age], }; consume_sender.send(work).unwrap(); let consumed = consumed_receiver.recv().unwrap(); assert_eq!(consumed.work.batch_id, bid); assert_eq!(consumed.work.ids, vec![id]); - assert_eq!(consumed.work.max_age_slots, vec![bank.slot()]); + assert_eq!(consumed.work.max_ages, vec![max_age]); assert_eq!(consumed.retryable_indexes, vec![0]); drop(test_frame); @@ -865,17 +887,21 @@ mod tests { )]); let bid = TransactionBatchId::new(0); let id = TransactionId::new(0); + let max_age = MaxAge { + epoch_invalidation_slot: bank.slot(), + alt_invalidation_slot: bank.slot(), + }; let work = ConsumeWork { batch_id: bid, ids: vec![id], transactions, - max_age_slots: vec![bank.slot()], + max_ages: vec![max_age], }; consume_sender.send(work).unwrap(); let consumed = consumed_receiver.recv().unwrap(); assert_eq!(consumed.work.batch_id, bid); assert_eq!(consumed.work.ids, vec![id]); - assert_eq!(consumed.work.max_age_slots, vec![bank.slot()]); + assert_eq!(consumed.work.max_ages, vec![max_age]); assert_eq!(consumed.retryable_indexes, Vec::::new()); drop(test_frame); @@ -911,19 +937,23 @@ mod tests { let bid = TransactionBatchId::new(0); let id1 = TransactionId::new(1); let id2 = TransactionId::new(0); + let max_age = MaxAge { + epoch_invalidation_slot: bank.slot(), + alt_invalidation_slot: bank.slot(), + }; consume_sender .send(ConsumeWork { batch_id: bid, ids: vec![id1, id2], transactions: txs, - max_age_slots: vec![bank.slot(), bank.slot()], + max_ages: vec![max_age, max_age], }) .unwrap(); let consumed = consumed_receiver.recv().unwrap(); assert_eq!(consumed.work.batch_id, bid); assert_eq!(consumed.work.ids, vec![id1, id2]); - assert_eq!(consumed.work.max_age_slots, vec![bank.slot(), bank.slot()]); + assert_eq!(consumed.work.max_ages, vec![max_age, max_age]); assert_eq!(consumed.retryable_indexes, vec![1]); // id2 is retryable since lock conflict drop(test_frame); @@ -968,12 +998,16 @@ mod tests { let bid2 = TransactionBatchId::new(1); let id1 = TransactionId::new(1); let id2 = TransactionId::new(0); + let max_age = MaxAge { + epoch_invalidation_slot: bank.slot(), + alt_invalidation_slot: bank.slot(), + }; consume_sender .send(ConsumeWork { batch_id: bid1, ids: vec![id1], transactions: txs1, - max_age_slots: vec![bank.slot()], + max_ages: vec![max_age], }) .unwrap(); @@ -982,20 +1016,183 @@ mod tests { batch_id: bid2, ids: vec![id2], transactions: txs2, - max_age_slots: vec![bank.slot()], + max_ages: vec![max_age], }) .unwrap(); let consumed = consumed_receiver.recv().unwrap(); assert_eq!(consumed.work.batch_id, bid1); assert_eq!(consumed.work.ids, vec![id1]); - assert_eq!(consumed.work.max_age_slots, vec![bank.slot()]); + assert_eq!(consumed.work.max_ages, vec![max_age]); assert_eq!(consumed.retryable_indexes, Vec::::new()); let consumed = consumed_receiver.recv().unwrap(); assert_eq!(consumed.work.batch_id, bid2); assert_eq!(consumed.work.ids, vec![id2]); - assert_eq!(consumed.work.max_age_slots, vec![bank.slot()]); + assert_eq!(consumed.work.max_ages, vec![max_age]); + assert_eq!(consumed.retryable_indexes, Vec::::new()); + + drop(test_frame); + let _ = worker_thread.join().unwrap(); + } + + #[test] + fn test_worker_ttl() { + let (test_frame, worker) = setup_test_frame(); + let TestFrame { + mint_keypair, + genesis_config, + bank, + poh_recorder, + consume_sender, + consumed_receiver, + .. + } = &test_frame; + let worker_thread = std::thread::spawn(move || worker.run()); + poh_recorder + .write() + .unwrap() + .set_bank_for_test(bank.clone()); + assert!(bank.slot() > 0); + + // No conflicts between transactions. Test 6 cases. + // 1. Epoch expiration, before slot => still succeeds due to resanitizing + // 2. Epoch expiration, on slot => succeeds normally + // 3. Epoch expiration, after slot => succeeds normally + // 4. ALT expiration, before slot => fails + // 5. ALT expiration, on slot => succeeds normally + // 6. ALT expiration, after slot => succeeds normally + let simple_transfer = || { + system_transaction::transfer( + &Keypair::new(), + &Pubkey::new_unique(), + 1, + genesis_config.hash(), + ) + }; + let simple_v0_transfer = || { + let payer = Keypair::new(); + let to_pubkey = Pubkey::new_unique(); + let loaded_addresses = LoadedAddresses { + writable: vec![to_pubkey], + readonly: vec![], + }; + let loader = SimpleAddressLoader::Enabled(loaded_addresses); + SanitizedTransaction::try_create( + VersionedTransaction::try_new( + VersionedMessage::V0( + v0::Message::try_compile( + &payer.pubkey(), + &[system_instruction::transfer(&payer.pubkey(), &to_pubkey, 1)], + &[AddressLookupTableAccount { + key: Pubkey::new_unique(), // will fail if using **bank** to lookup + addresses: vec![to_pubkey], + }], + genesis_config.hash(), + ) + .unwrap(), + ), + &[&payer], + ) + .unwrap(), + MessageHash::Compute, + None, + loader, + &HashSet::default(), + ) + .unwrap() + }; + + let mut txs = sanitize_transactions(vec![ + simple_transfer(), + simple_transfer(), + simple_transfer(), + ]); + txs.push(simple_v0_transfer()); + txs.push(simple_v0_transfer()); + txs.push(simple_v0_transfer()); + let sanitized_txs = txs.clone(); + + // Fund the keypairs. + for tx in &txs { + bank.process_transaction(&system_transaction::transfer( + mint_keypair, + &tx.account_keys()[0], + 2, + genesis_config.hash(), + )) + .unwrap(); + } + + consume_sender + .send(ConsumeWork { + batch_id: TransactionBatchId::new(1), + ids: vec![ + TransactionId::new(0), + TransactionId::new(1), + TransactionId::new(2), + TransactionId::new(3), + TransactionId::new(4), + TransactionId::new(5), + ], + transactions: txs, + max_ages: vec![ + MaxAge { + epoch_invalidation_slot: bank.slot() - 1, + alt_invalidation_slot: Slot::MAX, + }, + MaxAge { + epoch_invalidation_slot: bank.slot(), + alt_invalidation_slot: Slot::MAX, + }, + MaxAge { + epoch_invalidation_slot: bank.slot() + 1, + alt_invalidation_slot: Slot::MAX, + }, + MaxAge { + epoch_invalidation_slot: u64::MAX, + alt_invalidation_slot: bank.slot() - 1, + }, + MaxAge { + epoch_invalidation_slot: u64::MAX, + alt_invalidation_slot: bank.slot(), + }, + MaxAge { + epoch_invalidation_slot: u64::MAX, + alt_invalidation_slot: bank.slot() + 1, + }, + ], + }) + .unwrap(); + + let consumed = consumed_receiver.recv().unwrap(); assert_eq!(consumed.retryable_indexes, Vec::::new()); + // all but one succeed. 6 for initial funding + assert_eq!(bank.transaction_count(), 6 + 5); + + let already_processed_results = bank + .check_transactions( + &sanitized_txs, + &vec![Ok(()); sanitized_txs.len()], + MAX_PROCESSING_AGE, + &mut TransactionErrorMetrics::default(), + ) + .into_iter() + .map(|r| match r { + Ok(_) => Ok(()), + Err(err) => Err(err), + }) + .collect::>(); + assert_eq!( + already_processed_results, + vec![ + Err(TransactionError::AlreadyProcessed), + Err(TransactionError::AlreadyProcessed), + Err(TransactionError::AlreadyProcessed), + Ok(()), // <--- this transaction was not processed + Err(TransactionError::AlreadyProcessed), + Err(TransactionError::AlreadyProcessed) + ] + ); drop(test_frame); let _ = worker_thread.join().unwrap(); diff --git a/core/src/banking_stage/consumer.rs b/core/src/banking_stage/consumer.rs index 57f1b1958b152c..45007ff0a0e86f 100644 --- a/core/src/banking_stage/consumer.rs +++ b/core/src/banking_stage/consumer.rs @@ -18,6 +18,7 @@ use { BankStart, PohRecorderError, RecordTransactionsSummary, RecordTransactionsTimings, TransactionRecorder, }, + solana_prio_graph_scheduler::scheduler_messages::MaxAge, solana_runtime::{ bank::{Bank, LoadAndExecuteTransactionsOutput}, transaction_batch::TransactionBatch, @@ -25,12 +26,12 @@ use { }, solana_runtime_transaction::instructions_processor::process_compute_budget_instructions, solana_sdk::{ - clock::{Slot, FORWARD_TRANSACTIONS_TO_LEADER_AT_SLOT_OFFSET, MAX_PROCESSING_AGE}, + clock::{FORWARD_TRANSACTIONS_TO_LEADER_AT_SLOT_OFFSET, MAX_PROCESSING_AGE}, fee::FeeBudgetLimits, message::SanitizedMessage, saturating_add_assign, timing::timestamp, - transaction::{self, AddressLoader, SanitizedTransaction, TransactionError}, + transaction::{self, SanitizedTransaction, TransactionError}, }, solana_svm::{ account_loader::{validate_fee_payer, TransactionCheckResult}, @@ -429,7 +430,7 @@ impl Consumer { &self, bank: &Arc, txs: &[SanitizedTransaction], - max_slot_ages: &[Slot], + max_ages: &[MaxAge], ) -> ProcessTransactionBatchOutput { let move_precompile_verification_to_svm = bank .feature_set @@ -438,8 +439,9 @@ impl Consumer { // Need to filter out transactions since they were sanitized earlier. // This means that the transaction may cross and epoch boundary (not allowed), // or account lookup tables may have been closed. - let pre_results = txs.iter().zip(max_slot_ages).map(|(tx, max_slot_age)| { - if *max_slot_age < bank.slot() { + let pre_results = txs.iter().zip(max_ages).map(|(tx, max_age)| { + if bank.slot() > max_age.epoch_invalidation_slot { + // Epoch has rolled over. Need to fully re-verify the transaction. // Pre-compiles are verified here. // Attempt re-sanitization after epoch-cross. // Re-sanitized transaction should be equal to the original transaction, @@ -451,18 +453,24 @@ impl Consumer { return Err(TransactionError::ResanitizationNeeded); } } else { + if bank.slot() > max_age.alt_invalidation_slot { + // The address table lookup **may** have expired, but the + // expiration is not guaranteed since there may have been + // skipped slot. + // If the addresses still resolve here, then the transaction is still + // valid, and we can continue with processing. + // If they do not, then the ATL has expired and the transaction + // can be dropped. + let (_addresses, _deactivation_slot) = + bank.load_addresses_from_ref(tx.message_address_table_lookups())?; + } + // Verify pre-compiles. if !move_precompile_verification_to_svm { verify_precompiles(tx, &bank.feature_set)?; } - - // Any transaction executed between sanitization time and now may have closed the lookup table(s). - // Above re-sanitization already loads addresses, so don't need to re-check in that case. - let lookup_tables = tx.message().message_address_table_lookups(); - if !lookup_tables.is_empty() { - bank.load_addresses(lookup_tables)?; - } } + Ok(()) }); self.process_and_record_transactions_with_pre_results(bank, txs, 0, pre_results) diff --git a/core/src/banking_stage/immutable_deserialized_packet.rs b/core/src/banking_stage/immutable_deserialized_packet.rs index fe0f27e5aa9599..d7eb62b649627f 100644 --- a/core/src/banking_stage/immutable_deserialized_packet.rs +++ b/core/src/banking_stage/immutable_deserialized_packet.rs @@ -3,20 +3,21 @@ use { solana_compute_budget::compute_budget_limits::ComputeBudgetLimits, solana_perf::packet::Packet, solana_prio_graph_scheduler::deserializable_packet::DeserializableTxPacket, + solana_runtime::bank::Bank, solana_runtime_transaction::instructions_processor::process_compute_budget_instructions, solana_sanitize::SanitizeError, solana_sdk::{ + clock::Slot, hash::Hash, - message::Message, + message::{v0::LoadedAddresses, AddressLoaderError, Message, SimpleAddressLoader}, pubkey::Pubkey, signature::Signature, - transaction::{ - AddressLoader, SanitizedTransaction, SanitizedVersionedTransaction, - VersionedTransaction, - }, + transaction::{SanitizedTransaction, SanitizedVersionedTransaction, VersionedTransaction}, }, solana_short_vec::decode_shortu16_len, - solana_svm_transaction::instruction::SVMInstruction, + solana_svm_transaction::{ + instruction::SVMInstruction, message_address_table_lookup::SVMMessageAddressTableLookup, + }, std::{cmp::Ordering, collections::HashSet, mem::size_of}, thiserror::Error, }; @@ -113,15 +114,22 @@ impl DeserializableTxPacket for ImmutableDeserializedPacket { // This function deserializes packets into transactions, computes the blake3 hash of transaction // messages. + // Additionally, this returns the minimum deactivation slot of the resolved addresses. fn build_sanitized_transaction( &self, votes_only: bool, - address_loader: impl AddressLoader, + bank: &Bank, reserved_account_keys: &HashSet, - ) -> Option { + ) -> Option<(SanitizedTransaction, Slot)> { if votes_only && !self.is_simple_vote() { return None; } + + // Resolve the lookup addresses and retrieve the min deactivation slot + let (loaded_addresses, deactivation_slot) = + Self::resolve_addresses_with_deactivation(self.transaction(), bank).ok()?; + let address_loader = SimpleAddressLoader::Enabled(loaded_addresses); + let tx = SanitizedTransaction::try_new( self.transaction().clone(), *self.message_hash(), @@ -130,7 +138,25 @@ impl DeserializableTxPacket for ImmutableDeserializedPacket { reserved_account_keys, ) .ok()?; - Some(tx) + Some((tx, deactivation_slot)) + } +} + +impl ImmutableDeserializedPacket { + fn resolve_addresses_with_deactivation( + transaction: &SanitizedVersionedTransaction, + bank: &Bank, + ) -> Result<(LoadedAddresses, Slot), AddressLoaderError> { + let Some(address_table_lookups) = transaction.get_message().message.address_table_lookups() + else { + return Ok((LoadedAddresses::default(), Slot::MAX)); + }; + + bank.load_addresses_from_ref( + address_table_lookups + .iter() + .map(SVMMessageAddressTableLookup::from), + ) } } diff --git a/core/src/banking_stage/latest_unprocessed_votes.rs b/core/src/banking_stage/latest_unprocessed_votes.rs index 63e3893893ad95..f2c33aa6d9aeb4 100644 --- a/core/src/banking_stage/latest_unprocessed_votes.rs +++ b/core/src/banking_stage/latest_unprocessed_votes.rs @@ -2,7 +2,13 @@ use { super::{ forward_packet_batches_by_accounts::ForwardPacketBatchesByAccounts, immutable_deserialized_packet::{DeserializedPacketError, ImmutableDeserializedPacket}, - }, itertools::Itertools, rand::{thread_rng, Rng}, solana_perf::packet::Packet, solana_prio_graph_scheduler::deserializable_packet::DeserializableTxPacket, solana_runtime::{bank::Bank, epoch_stakes::EpochStakes}, solana_sdk::{ + }, + itertools::Itertools, + rand::{thread_rng, Rng}, + solana_perf::packet::Packet, + solana_prio_graph_scheduler::deserializable_packet::DeserializableTxPacket, + solana_runtime::{bank::Bank, epoch_stakes::EpochStakes}, + solana_sdk::{ account::from_account, clock::{Slot, UnixTimestamp}, feature_set::{self}, @@ -11,7 +17,9 @@ use { pubkey::Pubkey, slot_hashes::SlotHashes, sysvar, - }, solana_vote_program::vote_instruction::VoteInstruction, std::{ + }, + solana_vote_program::vote_instruction::VoteInstruction, + std::{ cmp, collections::HashMap, ops::DerefMut, @@ -19,7 +27,7 @@ use { atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}, Arc, RwLock, }, - } + }, }; #[derive(PartialEq, Eq, Debug, Copy, Clone)] @@ -413,7 +421,7 @@ impl LatestUnprocessedVotes { } let deserialized_vote_packet = vote.vote.as_ref().unwrap().clone(); - let Some(sanitized_vote_transaction) = deserialized_vote_packet + let Some((sanitized_vote_transaction, _deactivation_slot)) = deserialized_vote_packet .build_sanitized_transaction( bank.vote_only_bank(), bank.as_ref(), diff --git a/core/src/banking_stage/scheduler_controller.rs b/core/src/banking_stage/scheduler_controller.rs index cd5651d069fc23..3fe901c721ef45 100644 --- a/core/src/banking_stage/scheduler_controller.rs +++ b/core/src/banking_stage/scheduler_controller.rs @@ -21,6 +21,7 @@ use { id_generator::IdGenerator, prio_graph_scheduler::PrioGraphScheduler, scheduler_error::SchedulerError, + scheduler_messages::MaxAge, scheduler_metrics::{ SchedulerCountMetrics, SchedulerLeaderDetectionMetrics, SchedulerTimingMetrics, }, @@ -31,7 +32,8 @@ use { solana_runtime_transaction::instructions_processor::process_compute_budget_instructions, solana_sdk::{ self, - clock::{FORWARD_TRANSACTIONS_TO_LEADER_AT_SLOT_OFFSET, MAX_PROCESSING_AGE}, + address_lookup_table::state::estimate_last_valid_slot, + clock::{Slot, FORWARD_TRANSACTIONS_TO_LEADER_AT_SLOT_OFFSET, MAX_PROCESSING_AGE}, fee::FeeBudgetLimits, saturating_add_assign, transaction::SanitizedTransaction, @@ -503,16 +505,25 @@ impl SchedulerController { // Convert to Arcs let packets: Vec<_> = packets.into_iter().map(Arc::new).collect(); // Sanitize packets, generate IDs, and insert into the container. - let bank = self.bank_forks.read().unwrap().working_bank(); - let last_slot_in_epoch = bank.epoch_schedule().get_last_slot_in_epoch(bank.epoch()); - let transaction_account_lock_limit = bank.get_transaction_account_lock_limit(); - let vote_only = bank.vote_only_bank(); + let (root_bank, working_bank) = { + let bank_forks = self.bank_forks.read().unwrap(); + let root_bank = bank_forks.root_bank(); + let working_bank = bank_forks.working_bank(); + (root_bank, working_bank) + }; + let alt_resolved_slot = root_bank.slot(); + let last_slot_in_epoch = working_bank + .epoch_schedule() + .get_last_slot_in_epoch(working_bank.epoch()); + let transaction_account_lock_limit = working_bank.get_transaction_account_lock_limit(); + let vote_only = working_bank.vote_only_bank(); const CHUNK_SIZE: usize = 128; let lock_results: [_; CHUNK_SIZE] = core::array::from_fn(|_| Ok(())); let mut arc_packets = ArrayVec::<_, CHUNK_SIZE>::new(); let mut transactions = ArrayVec::<_, CHUNK_SIZE>::new(); + let mut max_ages = ArrayVec::<_, CHUNK_SIZE>::new(); let mut fee_budget_limits_vec = ArrayVec::<_, CHUNK_SIZE>::new(); let mut error_counts = TransactionErrorMetrics::default(); @@ -524,31 +535,43 @@ impl SchedulerController { packet .build_sanitized_transaction( vote_only, - bank.as_ref(), - bank.get_reserved_account_keys(), + root_bank.as_ref(), + working_bank.get_reserved_account_keys(), ) - .map(|tx| (packet.clone(), tx)) + .map(|(tx, deactivation_slot)| (packet.clone(), tx, deactivation_slot)) }) .inspect(|_| saturating_add_assign!(post_sanitization_count, 1)) - .filter(|(_packet, tx)| { + .filter(|(_packet, tx, _deactivation_slot)| { validate_account_locks( tx.message().account_keys(), transaction_account_lock_limit, ) .is_ok() }) - .filter_map(|(packet, tx)| { + .filter_map(|(packet, tx, deactivation_slot)| { process_compute_budget_instructions(SVMMessage::program_instructions_iter(&tx)) - .map(|compute_budget| (packet, tx, compute_budget.into())) + .map(|compute_budget| { + (packet, tx, deactivation_slot, compute_budget.into()) + }) .ok() }) - .for_each(|(packet, tx, fee_budget_limits)| { + .for_each(|(packet, tx, deactivation_slot, fee_budget_limits)| { arc_packets.push(packet); transactions.push(tx); + max_ages.push(calculate_max_age( + last_slot_in_epoch, + deactivation_slot, + alt_resolved_slot, + )); fee_budget_limits_vec.push(fee_budget_limits); }); - let check_results = bank.check_transactions( + let check_results: Vec< + Result< + solana_svm::account_loader::CheckedTransactionDetails, + solana_sdk::transaction::TransactionError, + >, + > = working_bank.check_transactions( &transactions, &lock_results[..transactions.len()], MAX_PROCESSING_AGE, @@ -559,21 +582,26 @@ impl SchedulerController { let mut post_transaction_check_count: usize = 0; let mut num_dropped_on_capacity: usize = 0; let mut num_buffered: usize = 0; - for (((packet, transaction), fee_budget_limits), _check_result) in arc_packets - .drain(..) - .zip(transactions.drain(..)) - .zip(fee_budget_limits_vec.drain(..)) - .zip(check_results) - .filter(|(_, check_result)| check_result.is_ok()) + for ((((packet, transaction), max_age), fee_budget_limits), _check_result) in + arc_packets + .drain(..) + .zip(transactions.drain(..)) + .zip(max_ages.drain(..)) + .zip(fee_budget_limits_vec.drain(..)) + .zip(check_results) + .filter(|(_, check_result)| check_result.is_ok()) { saturating_add_assign!(post_transaction_check_count, 1); let transaction_id = self.transaction_id_generator.next(); - let (priority, cost) = - Self::calculate_priority_and_cost(&transaction, &fee_budget_limits, &bank); + let (priority, cost) = Self::calculate_priority_and_cost( + &transaction, + &fee_budget_limits, + &working_bank, + ); let transaction_ttl = SanitizedTransactionTTL { transaction, - max_age_slot: last_slot_in_epoch, + max_age, }; if self.container.insert_new_transaction( @@ -658,6 +686,34 @@ impl SchedulerController { } } +/// Given the last slot in the epoch, the minimum deactivation slot, +/// and the current slot, return the `MaxAge` that should be used for +/// the transaction. This is used to determine the maximum slot that a +/// transaction will be considered valid for, without re-resolving addresses +/// or resanitizing. +/// +/// This function considers the deactivation period of Address Table +/// accounts. If the deactivation period runs past the end of the epoch, +/// then the transaction is considered valid until the end of the epoch. +/// Otherwise, the transaction is considered valid until the deactivation +/// period. +/// +/// Since the deactivation period technically uses blocks rather than +/// slots, the value used here is the lower-bound on the deactivation +/// period, i.e. the transaction's address lookups are valid until +/// AT LEAST this slot. +fn calculate_max_age( + last_slot_in_epoch: Slot, + deactivation_slot: Slot, + current_slot: Slot, +) -> MaxAge { + let alt_min_expire_slot = estimate_last_valid_slot(deactivation_slot.min(current_slot)); + MaxAge { + epoch_invalidation_slot: last_slot_in_epoch, + alt_invalidation_slot: alt_min_expire_slot, + } +} + #[cfg(test)] mod tests { use { @@ -831,7 +887,7 @@ mod tests { batch_id: TransactionBatchId::new(0), ids: vec![], transactions: vec![], - max_age_slots: vec![], + max_ages: vec![], }, retryable_indexes: vec![], }) @@ -1162,4 +1218,29 @@ mod tests { .collect_vec(); assert_eq!(message_hashes, vec![&tx1_hash]); } + + #[test] + fn test_calculate_max_age() { + let current_slot = 100; + let last_slot_in_epoch = 1000; + + // ALT deactivation slot is delayed + assert_eq!( + calculate_max_age(last_slot_in_epoch, current_slot - 1, current_slot), + MaxAge { + epoch_invalidation_slot: last_slot_in_epoch, + alt_invalidation_slot: current_slot - 1 + + solana_sdk::slot_hashes::get_entries() as u64, + } + ); + + // no deactivation slot + assert_eq!( + calculate_max_age(last_slot_in_epoch, u64::MAX, current_slot), + MaxAge { + epoch_invalidation_slot: last_slot_in_epoch, + alt_invalidation_slot: current_slot + solana_sdk::slot_hashes::get_entries() as u64, + } + ); + } } diff --git a/core/src/banking_stage/unprocessed_packet_batches.rs b/core/src/banking_stage/unprocessed_packet_batches.rs index c2616867f3f0be..c19309fb41cc99 100644 --- a/core/src/banking_stage/unprocessed_packet_batches.rs +++ b/core/src/banking_stage/unprocessed_packet_batches.rs @@ -1,9 +1,14 @@ use { - super::immutable_deserialized_packet::{DeserializedPacketError, ImmutableDeserializedPacket}, min_max_heap::MinMaxHeap, solana_perf::packet::Packet, solana_prio_graph_scheduler::deserializable_packet::DeserializableTxPacket, solana_sdk::hash::Hash, std::{ + super::immutable_deserialized_packet::{DeserializedPacketError, ImmutableDeserializedPacket}, + min_max_heap::MinMaxHeap, + solana_perf::packet::Packet, + solana_prio_graph_scheduler::deserializable_packet::DeserializableTxPacket, + solana_sdk::hash::Hash, + std::{ cmp::Ordering, collections::{hash_map::Entry, HashMap}, sync::Arc, - } + }, }; /// Holds deserialized messages, as well as computed message_hash and other things needed to create @@ -303,13 +308,14 @@ mod tests { use { super::*, solana_perf::packet::PacketFlags, + solana_runtime::bank::Bank, solana_sdk::{ compute_budget::ComputeBudgetInstruction, message::Message, reserved_account_keys::ReservedAccountKeys, signature::{Keypair, Signer}, system_instruction, system_transaction, - transaction::{SimpleAddressLoader, Transaction}, + transaction::Transaction, }, solana_vote_program::{vote_state::TowerSync, vote_transaction}, }; @@ -471,6 +477,7 @@ mod tests { &keypair, None, ); + let bank = Bank::default_for_tests(); // packets with no votes { @@ -482,7 +489,7 @@ mod tests { let txs = packet_vector.iter().filter_map(|tx| { tx.immutable_section().build_sanitized_transaction( votes_only, - SimpleAddressLoader::Disabled, + &bank, &ReservedAccountKeys::empty_key_set(), ) }); @@ -492,7 +499,7 @@ mod tests { let txs = packet_vector.iter().filter_map(|tx| { tx.immutable_section().build_sanitized_transaction( votes_only, - SimpleAddressLoader::Disabled, + &bank, &ReservedAccountKeys::empty_key_set(), ) }); @@ -511,7 +518,7 @@ mod tests { let txs = packet_vector.iter().filter_map(|tx| { tx.immutable_section().build_sanitized_transaction( votes_only, - SimpleAddressLoader::Disabled, + &bank, &ReservedAccountKeys::empty_key_set(), ) }); @@ -521,7 +528,7 @@ mod tests { let txs = packet_vector.iter().filter_map(|tx| { tx.immutable_section().build_sanitized_transaction( votes_only, - SimpleAddressLoader::Disabled, + &bank, &ReservedAccountKeys::empty_key_set(), ) }); @@ -540,7 +547,7 @@ mod tests { let txs = packet_vector.iter().filter_map(|tx| { tx.immutable_section().build_sanitized_transaction( votes_only, - SimpleAddressLoader::Disabled, + &bank, &ReservedAccountKeys::empty_key_set(), ) }); @@ -550,7 +557,7 @@ mod tests { let txs = packet_vector.iter().filter_map(|tx| { tx.immutable_section().build_sanitized_transaction( votes_only, - SimpleAddressLoader::Disabled, + &bank, &ReservedAccountKeys::empty_key_set(), ) }); diff --git a/core/src/banking_stage/unprocessed_transaction_storage.rs b/core/src/banking_stage/unprocessed_transaction_storage.rs index 5eeaea5d082e62..aac5844efe0663 100644 --- a/core/src/banking_stage/unprocessed_transaction_storage.rs +++ b/core/src/banking_stage/unprocessed_transaction_storage.rs @@ -155,13 +155,15 @@ fn consume_scan_should_process_packet( return ProcessingDecision::Now; } - // Try to sanitize the packet + // Try to sanitize the packet. Ignore deactivation slot since we are + // immediately attempting to process the transaction. let (maybe_sanitized_transaction, sanitization_time_us) = measure_us!(packet .build_sanitized_transaction( bank.vote_only_bank(), bank, bank.get_reserved_account_keys(), - )); + ) + .map(|(tx, _deactivation_slot)| tx)); payload .slot_metrics_tracker @@ -800,7 +802,7 @@ impl ThreadLocalUnprocessedPackets { bank, bank.get_reserved_account_keys(), ) - .map(|transaction| (transaction, packet_index)) + .map(|(transaction, _deactivation_slot)| (transaction, packet_index)) }) .unzip(); diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index db0ee5aff30d53..a10017c3dcd1c6 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -54,6 +54,7 @@ use { solana_rpc::{ optimistically_confirmed_bank_tracker::{BankNotification, BankNotificationSenderConfig}, rpc_subscriptions::RpcSubscriptions, + slot_status_notifier::SlotStatusNotifier, }, solana_rpc_client_api::response::SlotUpdate, solana_runtime::{ @@ -251,6 +252,7 @@ pub struct ReplayStageConfig { pub authorized_voter_keypairs: Arc>>>, pub exit: Arc, pub rpc_subscriptions: Arc, + pub slot_status_notifier: Option, pub leader_schedule_cache: Arc, pub accounts_background_request_sender: AbsRequestSender, pub block_commitment_cache: Arc>, @@ -537,6 +539,7 @@ impl ReplayStage { authorized_voter_keypairs, exit, rpc_subscriptions, + slot_status_notifier, leader_schedule_cache, accounts_background_request_sender, block_commitment_cache, @@ -668,6 +671,7 @@ impl ReplayStage { &bank_forks, &leader_schedule_cache, &rpc_subscriptions, + &slot_status_notifier, &mut progress, &mut replay_timing, ); @@ -1122,6 +1126,7 @@ impl ReplayStage { &poh_recorder, &leader_schedule_cache, &rpc_subscriptions, + &slot_status_notifier, &mut progress, &retransmit_slots_sender, &mut skipped_slots_info, @@ -2052,6 +2057,7 @@ impl ReplayStage { poh_recorder: &Arc>, leader_schedule_cache: &Arc, rpc_subscriptions: &Arc, + slot_status_notifier: &Option, progress_map: &mut ProgressMap, retransmit_slots_sender: &Sender, skipped_slots_info: &mut SkippedSlotsInfo, @@ -2181,6 +2187,7 @@ impl ReplayStage { root_slot, my_pubkey, rpc_subscriptions, + slot_status_notifier, NewBankOptions { vote_only_bank }, ); // make sure parent is frozen for finalized hashes via the above @@ -3960,6 +3967,7 @@ impl ReplayStage { bank_forks: &RwLock, leader_schedule_cache: &Arc, rpc_subscriptions: &Arc, + slot_status_notifier: &Option, progress: &mut ProgressMap, replay_timing: &mut ReplayLoopTiming, ) { @@ -4014,6 +4022,7 @@ impl ReplayStage { forks.root(), &leader, rpc_subscriptions, + slot_status_notifier, NewBankOptions::default(), ); let empty: Vec = vec![]; @@ -4061,9 +4070,16 @@ impl ReplayStage { root_slot: u64, leader: &Pubkey, rpc_subscriptions: &Arc, + slot_status_notifier: &Option, new_bank_options: NewBankOptions, ) -> Bank { rpc_subscriptions.notify_slot(slot, parent.slot(), root_slot); + if let Some(slot_status_notifier) = slot_status_notifier { + slot_status_notifier + .read() + .unwrap() + .notify_created_bank(slot, parent.slot()); + } Bank::new_from_parent_with_options(parent, leader, slot, new_bank_options) } @@ -4410,6 +4426,7 @@ pub(crate) mod tests { &bank_forks, &leader_schedule_cache, &rpc_subscriptions, + &None, &mut progress, &mut replay_timing, ); @@ -4438,6 +4455,7 @@ pub(crate) mod tests { &bank_forks, &leader_schedule_cache, &rpc_subscriptions, + &None, &mut progress, &mut replay_timing, ); @@ -6307,6 +6325,7 @@ pub(crate) mod tests { &bank_forks, &leader_schedule_cache, &rpc_subscriptions, + &None, &mut progress, &mut replay_timing, ); @@ -6336,6 +6355,7 @@ pub(crate) mod tests { &bank_forks, &leader_schedule_cache, &rpc_subscriptions, + &None, &mut progress, &mut replay_timing, ); @@ -6366,6 +6386,7 @@ pub(crate) mod tests { &bank_forks, &leader_schedule_cache, &rpc_subscriptions, + &None, &mut progress, &mut replay_timing, ); @@ -6395,6 +6416,7 @@ pub(crate) mod tests { &bank_forks, &leader_schedule_cache, &rpc_subscriptions, + &None, &mut progress, &mut replay_timing, ); @@ -8329,6 +8351,7 @@ pub(crate) mod tests { &poh_recorder, &leader_schedule_cache, &rpc_subscriptions, + &None, &mut progress, &retransmit_slots_sender, &mut SkippedSlotsInfo::default(), @@ -8997,6 +9020,7 @@ pub(crate) mod tests { &poh_recorder, &leader_schedule_cache, &rpc_subscriptions, + &None, &mut progress, &retransmit_slots_sender, &mut SkippedSlotsInfo::default(), @@ -9023,6 +9047,7 @@ pub(crate) mod tests { &poh_recorder, &leader_schedule_cache, &rpc_subscriptions, + &None, &mut progress, &retransmit_slots_sender, &mut SkippedSlotsInfo::default(), diff --git a/core/src/tvu.rs b/core/src/tvu.rs index 23e5d9b6562451..68b7cf38023505 100644 --- a/core/src/tvu.rs +++ b/core/src/tvu.rs @@ -211,7 +211,7 @@ impl Tvu { retransmit_receiver, max_slots.clone(), Some(rpc_subscriptions.clone()), - slot_status_notifier, + slot_status_notifier.clone(), ); let (ancestor_duplicate_slots_sender, ancestor_duplicate_slots_receiver) = unbounded(); @@ -274,6 +274,7 @@ impl Tvu { authorized_voter_keypairs, exit: exit.clone(), rpc_subscriptions: rpc_subscriptions.clone(), + slot_status_notifier, leader_schedule_cache: leader_schedule_cache.clone(), accounts_background_request_sender, block_commitment_cache, diff --git a/fetch-spl.sh b/fetch-spl.sh index 97fb1c50aa52eb..17f3c68a86df78 100755 --- a/fetch-spl.sh +++ b/fetch-spl.sh @@ -45,7 +45,7 @@ fetch_program() { } fetch_program token 3.5.0 TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA BPFLoader2111111111111111111111111111111111 -fetch_program token-2022 1.0.0 TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb BPFLoaderUpgradeab1e11111111111111111111111 +fetch_program token-2022 5.0.2 TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb BPFLoaderUpgradeab1e11111111111111111111111 fetch_program memo 1.0.0 Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo BPFLoader1111111111111111111111111111111111 fetch_program memo 3.0.0 MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr BPFLoader2111111111111111111111111111111111 fetch_program associated-token-account 1.1.2 ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL BPFLoader2111111111111111111111111111111111 diff --git a/geyser-plugin-interface/src/geyser_plugin_interface.rs b/geyser-plugin-interface/src/geyser_plugin_interface.rs index 97271310a99f5f..3f4394a84bae8e 100644 --- a/geyser-plugin-interface/src/geyser_plugin_interface.rs +++ b/geyser-plugin-interface/src/geyser_plugin_interface.rs @@ -325,6 +325,9 @@ pub enum SlotStatus { /// All shreds for the slot have been received. Completed, + + /// A new bank fork is created with the slot + CreatedBank, } impl SlotStatus { @@ -335,6 +338,7 @@ impl SlotStatus { SlotStatus::Rooted => "rooted", SlotStatus::FirstShredReceived => "first_shread_received", SlotStatus::Completed => "completed", + SlotStatus::CreatedBank => "created_bank", } } } diff --git a/geyser-plugin-manager/src/slot_status_notifier.rs b/geyser-plugin-manager/src/slot_status_notifier.rs index 573ed97d7787af..27dcd03dac28f3 100644 --- a/geyser-plugin-manager/src/slot_status_notifier.rs +++ b/geyser-plugin-manager/src/slot_status_notifier.rs @@ -33,6 +33,10 @@ impl SlotStatusNotifierInterface for SlotStatusNotifierImpl { fn notify_completed(&self, slot: Slot) { self.notify_slot_status(slot, None, SlotStatus::Completed); } + + fn notify_created_bank(&self, slot: Slot, parent: Slot) { + self.notify_slot_status(slot, Some(parent), SlotStatus::CreatedBank); + } } impl SlotStatusNotifierImpl { diff --git a/inline-spl/Cargo.toml b/inline-spl/Cargo.toml index 82aa5907ce0aa2..132e10f07ab533 100644 --- a/inline-spl/Cargo.toml +++ b/inline-spl/Cargo.toml @@ -11,7 +11,9 @@ edition = { workspace = true } [dependencies] bytemuck = { workspace = true } -solana-program = { workspace = true, default-features = false } +solana-pubkey = { workspace = true, default-features = false, features = [ + "bytemuck", +] } [lib] crate-type = ["lib"] diff --git a/inline-spl/src/associated_token_account.rs b/inline-spl/src/associated_token_account.rs index 2048c5b743d222..289dc0f8555e47 100644 --- a/inline-spl/src/associated_token_account.rs +++ b/inline-spl/src/associated_token_account.rs @@ -1,6 +1,6 @@ // Partial SPL Associated Token Account declarations inlined to avoid an external dependency on the spl-associated-token-account crate -solana_program::declare_id!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); +solana_pubkey::declare_id!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); pub mod program_v1_1_0 { - solana_program::declare_id!("NatA1Zyo48dJ7yuwR7cGURwhskKA8ywUyxb9GvG7mTC"); + solana_pubkey::declare_id!("NatA1Zyo48dJ7yuwR7cGURwhskKA8ywUyxb9GvG7mTC"); } diff --git a/inline-spl/src/token.rs b/inline-spl/src/token.rs index 1a495d8ca3a241..af456c3b6242a7 100644 --- a/inline-spl/src/token.rs +++ b/inline-spl/src/token.rs @@ -1,10 +1,10 @@ /// Partial SPL Token declarations inlined to avoid an external dependency on the spl-token crate -use solana_program::pubkey::{Pubkey, PUBKEY_BYTES}; +use solana_pubkey::{Pubkey, PUBKEY_BYTES}; -solana_program::declare_id!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); +solana_pubkey::declare_id!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); pub mod program_v3_4_0 { - solana_program::declare_id!("NToK4t5AQzxPNpUA84DkxgfXaVDbDQQjpHKCqsbY46B"); + solana_pubkey::declare_id!("NToK4t5AQzxPNpUA84DkxgfXaVDbDQQjpHKCqsbY46B"); } /* @@ -72,7 +72,7 @@ impl GenericTokenAccount for Account { } pub mod native_mint { - solana_program::declare_id!("So11111111111111111111111111111111111111112"); + solana_pubkey::declare_id!("So11111111111111111111111111111111111111112"); /* Mint { diff --git a/inline-spl/src/token_2022.rs b/inline-spl/src/token_2022.rs index 4b0e0d1b3c05db..fafa2b4cfce68a 100644 --- a/inline-spl/src/token_2022.rs +++ b/inline-spl/src/token_2022.rs @@ -1,7 +1,7 @@ /// Partial SPL Token declarations inlined to avoid an external dependency on the spl-token-2022 crate use crate::token::{self, GenericTokenAccount}; -solana_program::declare_id!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); +solana_pubkey::declare_id!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); // `spl_token_program_2022::extension::AccountType::Account` ordinal value pub const ACCOUNTTYPE_ACCOUNT: u8 = 2; diff --git a/keygen/Cargo.toml b/keygen/Cargo.toml index 4dd74305996883..93c5e4f2e4f064 100644 --- a/keygen/Cargo.toml +++ b/keygen/Cargo.toml @@ -14,6 +14,7 @@ bs58 = { workspace = true } clap = { version = "3.1.5", features = ["cargo"] } dirs-next = { workspace = true } num_cpus = { workspace = true } +serde_json = { workspace = true } solana-clap-v3-utils = { workspace = true } solana-cli-config = { workspace = true } solana-derivation-path = { workspace = true } diff --git a/keygen/src/keygen.rs b/keygen/src/keygen.rs index cbccb33e9a9095..6f0b9ff93adc18 100644 --- a/keygen/src/keygen.rs +++ b/keygen/src/keygen.rs @@ -30,7 +30,7 @@ use { solana_sdk::{ instruction::{AccountMeta, Instruction}, message::Message, - pubkey::{write_pubkey_file, Pubkey}, + pubkey::Pubkey, signature::{ keypair_from_seed, keypair_from_seed_and_derivation_path, write_keypair, write_keypair_file, Keypair, Signer, @@ -424,6 +424,21 @@ fn app<'a>(num_threads: &'a str, crate_version: &'a str) -> Command<'a> { ) } +fn write_pubkey_file(outfile: &str, pubkey: Pubkey) -> Result<(), Box> { + use std::io::Write; + + let printable = format!("{pubkey}"); + let serialized = serde_json::to_string(&printable)?; + + if let Some(outdir) = std::path::Path::new(&outfile).parent() { + std::fs::create_dir_all(outdir)?; + } + let mut f = std::fs::File::create(outfile)?; + f.write_all(&serialized.into_bytes())?; + + Ok(()) +} + fn main() -> Result<(), Box> { let default_num_threads = num_cpus::get().to_string(); let matches = app(&default_num_threads, solana_version::version!()) @@ -768,6 +783,14 @@ mod tests { tempfile::{tempdir, TempDir}, }; + fn read_pubkey_file(infile: &str) -> Result> { + let f = std::fs::File::open(infile)?; + let printable: String = serde_json::from_reader(f)?; + + use std::str::FromStr; + Ok(Pubkey::from_str(&printable)?) + } + fn process_test_command(args: &[&str]) -> Result<(), Box> { let default_num_threads = num_cpus::get().to_string(); let solana_version = solana_version::version!(); @@ -919,7 +942,7 @@ mod tests { ]) .unwrap(); - let result_pubkey = solana_sdk::pubkey::read_pubkey_file(&outfile_path).unwrap(); + let result_pubkey = read_pubkey_file(&outfile_path).unwrap(); assert_eq!(result_pubkey, expected_pubkey); } @@ -938,7 +961,7 @@ mod tests { ]) .unwrap(); - let result_pubkey = solana_sdk::pubkey::read_pubkey_file(&outfile_path).unwrap(); + let result_pubkey = read_pubkey_file(&outfile_path).unwrap(); assert_eq!(result_pubkey, expected_pubkey); } @@ -962,7 +985,7 @@ mod tests { ]) .unwrap(); - let result_pubkey = solana_sdk::pubkey::read_pubkey_file(&outfile_path).unwrap(); + let result_pubkey = read_pubkey_file(&outfile_path).unwrap(); assert_eq!(result_pubkey, expected_pubkey); } @@ -1129,4 +1152,15 @@ mod tests { ]) .unwrap(); } + + #[test] + fn test_read_write_pubkey() -> Result<(), std::boxed::Box> { + let filename = "test_pubkey.json"; + let pubkey = solana_sdk::pubkey::new_rand(); + write_pubkey_file(filename, pubkey)?; + let read = read_pubkey_file(filename)?; + assert_eq!(read, pubkey); + std::fs::remove_file(filename)?; + Ok(()) + } } diff --git a/ledger-tool/src/args.rs b/ledger-tool/src/args.rs index e9149f7b9cab4b..61d3208fe848aa 100644 --- a/ledger-tool/src/args.rs +++ b/ledger-tool/src/args.rs @@ -345,6 +345,8 @@ pub fn get_accounts_db_config( create_ancient_storage, storage_access, scan_filter_for_shrinking, + enable_experimental_accumulator_hash: arg_matches + .is_present("accounts_db_experimental_accumulator_hash"), ..AccountsDbConfig::default() } } diff --git a/log-analyzer/Cargo.toml b/log-analyzer/Cargo.toml index 21df3661ddd0ea..fff8c9c2d3c745 100644 --- a/log-analyzer/Cargo.toml +++ b/log-analyzer/Cargo.toml @@ -13,6 +13,7 @@ edition = { workspace = true } byte-unit = { workspace = true } clap = { version = "3.1.5", features = ["cargo"] } serde = { workspace = true } +serde_derive = { workspace = true } serde_json = { workspace = true } solana-logger = { workspace = true } solana-version = { workspace = true } diff --git a/log-analyzer/src/main.rs b/log-analyzer/src/main.rs index fcedd7c0dc46cf..32ce3fbb5a27fc 100644 --- a/log-analyzer/src/main.rs +++ b/log-analyzer/src/main.rs @@ -4,7 +4,7 @@ extern crate byte_unit; use { byte_unit::Byte, clap::{crate_description, crate_name, Arg, ArgMatches, Command}, - serde::{Deserialize, Serialize}, + serde_derive::{Deserialize, Serialize}, std::{collections::HashMap, fs, ops::Sub, path::PathBuf}, }; diff --git a/prio-graph-scheduler/src/deserializable_packet.rs b/prio-graph-scheduler/src/deserializable_packet.rs index d814351652c61b..f550984365e16a 100644 --- a/prio-graph-scheduler/src/deserializable_packet.rs +++ b/prio-graph-scheduler/src/deserializable_packet.rs @@ -1,9 +1,10 @@ -use std::collections::HashSet; +use solana_runtime::bank::Bank; +use solana_sdk::clock::Slot; use solana_sdk::hash::Hash; -use solana_sdk::message::AddressLoader; use solana_sdk::packet::Packet; use solana_sdk::pubkey::Pubkey; use solana_sdk::transaction::{SanitizedTransaction, SanitizedVersionedTransaction}; +use std::collections::HashSet; use std::error::Error; /// DeserializablePacket can be deserialized from a Packet. @@ -20,9 +21,9 @@ pub trait DeserializableTxPacket: PartialEq + PartialOrd + Eq + Sized { fn build_sanitized_transaction( &self, votes_only: bool, - address_loader: impl AddressLoader, + address_loader: &Bank, reserved_account_keys: &HashSet, - ) -> Option; + ) -> Option<(SanitizedTransaction, Slot)>; fn original_packet(&self) -> &Packet; @@ -36,4 +37,4 @@ pub trait DeserializableTxPacket: PartialEq + PartialOrd + Eq + Sized { fn compute_unit_price(&self) -> u64; fn compute_unit_limit(&self) -> u64; -} \ No newline at end of file +} diff --git a/prio-graph-scheduler/src/lib.rs b/prio-graph-scheduler/src/lib.rs index 9dbb4027c26995..a1c907bb67fd04 100644 --- a/prio-graph-scheduler/src/lib.rs +++ b/prio-graph-scheduler/src/lib.rs @@ -30,20 +30,23 @@ mod tests { crate::deserializable_packet::DeserializableTxPacket, solana_compute_budget::compute_budget_limits::ComputeBudgetLimits, solana_perf::packet::Packet, + solana_runtime::bank::Bank, solana_runtime_transaction::instructions_processor::process_compute_budget_instructions, solana_sanitize::SanitizeError, solana_sdk::{ + clock::Slot, hash::Hash, - message::Message, + message::{v0::LoadedAddresses, AddressLoaderError, Message, SimpleAddressLoader}, pubkey::Pubkey, signature::Signature, transaction::{ - AddressLoader, SanitizedTransaction, SanitizedVersionedTransaction, - VersionedTransaction, + SanitizedTransaction, SanitizedVersionedTransaction, VersionedTransaction, }, }, solana_short_vec::decode_shortu16_len, - solana_svm_transaction::instruction::SVMInstruction, + solana_svm_transaction::{ + instruction::SVMInstruction, message_address_table_lookup::SVMMessageAddressTableLookup, + }, std::{cmp::Ordering, collections::HashSet, mem::size_of}, thiserror::Error, }; @@ -140,12 +143,16 @@ mod tests { fn build_sanitized_transaction( &self, votes_only: bool, - address_loader: impl AddressLoader, + bank: &Bank, reserved_account_keys: &HashSet, - ) -> Option { + ) -> Option<(SanitizedTransaction, Slot)> { if votes_only && !self.is_simple_vote() { return None; } + // Resolve the lookup addresses and retrieve the min deactivation slot + let (loaded_addresses, deactivation_slot) = + Self::resolve_addresses_with_deactivation(self.transaction(), bank).ok()?; + let address_loader = SimpleAddressLoader::Enabled(loaded_addresses); let tx = SanitizedTransaction::try_new( self.transaction().clone(), *self.message_hash(), @@ -154,7 +161,26 @@ mod tests { reserved_account_keys, ) .ok()?; - Some(tx) + Some((tx, deactivation_slot)) + } + } + + impl MockImmutableDeserializedPacket { + fn resolve_addresses_with_deactivation( + transaction: &SanitizedVersionedTransaction, + bank: &Bank, + ) -> Result<(LoadedAddresses, Slot), AddressLoaderError> { + let Some(address_table_lookups) = + transaction.get_message().message.address_table_lookups() + else { + return Ok((LoadedAddresses::default(), Slot::MAX)); + }; + + bank.load_addresses_from_ref( + address_table_lookups + .iter() + .map(SVMMessageAddressTableLookup::from), + ) } } diff --git a/prio-graph-scheduler/src/prio_graph_scheduler.rs b/prio-graph-scheduler/src/prio_graph_scheduler.rs index bf391919ed7605..34715903da5f1d 100644 --- a/prio-graph-scheduler/src/prio_graph_scheduler.rs +++ b/prio-graph-scheduler/src/prio_graph_scheduler.rs @@ -4,7 +4,7 @@ use { in_flight_tracker::InFlightTracker, read_write_account_set::ReadWriteAccountSet, scheduler_error::SchedulerError, - scheduler_messages::{ConsumeWork, FinishedConsumeWork, TransactionBatchId, TransactionId}, + scheduler_messages::{ConsumeWork, FinishedConsumeWork, TransactionBatchId, TransactionId, MaxAge}, thread_aware_account_locks::{ThreadAwareAccountLocks, ThreadId, ThreadSet}, transaction_priority_id::TransactionPriorityId, transaction_state::{SanitizedTransactionTTL, TransactionState}, @@ -16,10 +16,7 @@ use { prio_graph::{AccessKind, PrioGraph}, solana_cost_model::block_cost_limits::MAX_BLOCK_UNITS, solana_measure::measure_us, - solana_sdk::{ - pubkey::Pubkey, saturating_add_assign, slot_history::Slot, - transaction::SanitizedTransaction, - }, + solana_sdk::{pubkey::Pubkey, saturating_add_assign, transaction::SanitizedTransaction}, }; pub struct PrioGraphScheduler { @@ -201,13 +198,13 @@ impl PrioGraphScheduler

{ Ok(TransactionSchedulingInfo { thread_id, transaction, - max_age_slot, + max_age, cost, }) => { saturating_add_assign!(num_scheduled, 1); batches.transactions[thread_id].push(transaction); batches.ids[thread_id].push(id.id); - batches.max_age_slots[thread_id].push(max_age_slot); + batches.max_ages[thread_id].push(max_age); saturating_add_assign!(batches.total_cus[thread_id], cost); // If target batch size is reached, send only this batch. @@ -308,7 +305,7 @@ impl PrioGraphScheduler

{ batch_id, ids, transactions, - max_age_slots, + max_ages, }, retryable_indexes, }) => { @@ -320,8 +317,8 @@ impl PrioGraphScheduler

{ // Retryable transactions should be inserted back into the container let mut retryable_iter = retryable_indexes.into_iter().peekable(); - for (index, (id, transaction, max_age_slot)) in - izip!(ids, transactions, max_age_slots).enumerate() + for (index, (id, transaction, max_age)) in + izip!(ids, transactions, max_ages).enumerate() { if let Some(retryable_index) = retryable_iter.peek() { if *retryable_index == index { @@ -329,7 +326,7 @@ impl PrioGraphScheduler

{ id, SanitizedTransactionTTL { transaction, - max_age_slot, + max_age, }, ); retryable_iter.next(); @@ -391,7 +388,7 @@ impl PrioGraphScheduler

{ return Ok(0); } - let (ids, transactions, max_age_slots, total_cus) = batches.take_batch(thread_index); + let (ids, transactions, max_ages, total_cus) = batches.take_batch(thread_index); let batch_id = self .in_flight_tracker @@ -402,7 +399,7 @@ impl PrioGraphScheduler

{ batch_id, ids, transactions, - max_age_slots, + max_ages, }; self.consume_work_senders[thread_index] .send(work) @@ -476,7 +473,7 @@ pub struct SchedulingSummary { struct Batches { ids: Vec>, transactions: Vec>, - max_age_slots: Vec>, + max_ages: Vec>, total_cus: Vec, } @@ -485,7 +482,7 @@ impl Batches { Self { ids: vec![Vec::with_capacity(TARGET_NUM_TRANSACTIONS_PER_BATCH); num_threads], transactions: vec![Vec::with_capacity(TARGET_NUM_TRANSACTIONS_PER_BATCH); num_threads], - max_age_slots: vec![Vec::with_capacity(TARGET_NUM_TRANSACTIONS_PER_BATCH); num_threads], + max_ages: vec![Vec::with_capacity(TARGET_NUM_TRANSACTIONS_PER_BATCH); num_threads], total_cus: vec![0; num_threads], } } @@ -496,7 +493,7 @@ impl Batches { ) -> ( Vec, Vec, - Vec, + Vec, u64, ) { ( @@ -509,7 +506,7 @@ impl Batches { Vec::with_capacity(TARGET_NUM_TRANSACTIONS_PER_BATCH), ), core::mem::replace( - &mut self.max_age_slots[thread_id], + &mut self.max_ages[thread_id], Vec::with_capacity(TARGET_NUM_TRANSACTIONS_PER_BATCH), ), core::mem::replace(&mut self.total_cus[thread_id], 0), @@ -521,7 +518,7 @@ impl Batches { struct TransactionSchedulingInfo { thread_id: ThreadId, transaction: SanitizedTransaction, - max_age_slot: Slot, + max_age: MaxAge, cost: u64, } @@ -582,7 +579,7 @@ fn try_schedule_transaction( Ok(TransactionSchedulingInfo { thread_id, transaction: sanitized_transaction_ttl.transaction, - max_age_slot: sanitized_transaction_ttl.max_age_slot, + max_age: sanitized_transaction_ttl.max_age, cost, }) } @@ -596,8 +593,8 @@ mod tests { crossbeam_channel::{unbounded, Receiver}, itertools::Itertools, solana_sdk::{ - compute_budget::ComputeBudgetInstruction, hash::Hash, message::Message, packet::Packet, - pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, + clock::Slot, compute_budget::ComputeBudgetInstruction, hash::Hash, message::Message, + packet::Packet, pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, transaction::Transaction, }, std::{borrow::Borrow, sync::Arc}, @@ -686,7 +683,10 @@ mod tests { ); let transaction_ttl = SanitizedTransactionTTL { transaction, - max_age_slot: Slot::MAX, + max_age: MaxAge { + epoch_invalidation_slot: Slot::MAX, + alt_invalidation_slot: Slot::MAX, + }, }; const TEST_TRANSACTION_COST: u64 = 5000; container.insert_new_transaction( diff --git a/prio-graph-scheduler/src/scheduler_messages.rs b/prio-graph-scheduler/src/scheduler_messages.rs index 1be56b41c4c81f..b7ecf193cfffa5 100644 --- a/prio-graph-scheduler/src/scheduler_messages.rs +++ b/prio-graph-scheduler/src/scheduler_messages.rs @@ -47,13 +47,19 @@ impl From for TransactionId { } } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct MaxAge { + pub epoch_invalidation_slot: Slot, + pub alt_invalidation_slot: Slot, +} + /// Message: [Scheduler -> Worker] /// Transactions to be consumed (i.e. executed, recorded, and committed) pub struct ConsumeWork { pub batch_id: TransactionBatchId, pub ids: Vec, pub transactions: Vec, - pub max_age_slots: Vec, + pub max_ages: Vec, } /// Message: [Worker -> Scheduler] diff --git a/prio-graph-scheduler/src/transaction_state.rs b/prio-graph-scheduler/src/transaction_state.rs index 1e7b7a645622d9..a4985c3e3f6a11 100644 --- a/prio-graph-scheduler/src/transaction_state.rs +++ b/prio-graph-scheduler/src/transaction_state.rs @@ -1,13 +1,12 @@ use { - crate::deserializable_packet::DeserializableTxPacket, - solana_sdk::{clock::Slot, transaction::SanitizedTransaction}, - std::sync::Arc, + crate::deserializable_packet::DeserializableTxPacket, crate::scheduler_messages::MaxAge, + solana_sdk::transaction::SanitizedTransaction, std::sync::Arc, }; /// Simple wrapper type to tie a sanitized transaction to max age slot. pub struct SanitizedTransactionTTL { pub transaction: SanitizedTransaction, - pub max_age_slot: Slot, + pub max_age: MaxAge, } /// TransactionState is used to track the state of a transaction in the transaction scheduler @@ -205,10 +204,13 @@ impl TransactionState

{ #[cfg(test)] mod tests { use { - super::*, crate::tests::MockImmutableDeserializedPacket, solana_sdk::{ - compute_budget::ComputeBudgetInstruction, hash::Hash, message::Message, packet::Packet, - signature::Keypair, signer::Signer, system_instruction, transaction::Transaction, - } + super::*, + crate::tests::MockImmutableDeserializedPacket, + solana_sdk::{ + clock::Slot, compute_budget::ComputeBudgetInstruction, hash::Hash, message::Message, + packet::Packet, signature::Keypair, signer::Signer, system_instruction, + transaction::Transaction, + }, }; fn create_transaction_state( @@ -227,11 +229,15 @@ mod tests { let tx = Transaction::new(&[&from_keypair], message, Hash::default()); let packet = Arc::new( - MockImmutableDeserializedPacket::new(Packet::from_data(None, tx.clone()).unwrap()).unwrap(), + MockImmutableDeserializedPacket::new(Packet::from_data(None, tx.clone()).unwrap()) + .unwrap(), ); let transaction_ttl = SanitizedTransactionTTL { transaction: SanitizedTransaction::from_transaction_for_tests(tx), - max_age_slot: Slot::MAX, + max_age: MaxAge { + epoch_invalidation_slot: Slot::MAX, + alt_invalidation_slot: Slot::MAX, + }, }; const TEST_TRANSACTION_COST: u64 = 5000; TransactionState::new( @@ -272,11 +278,11 @@ mod tests { // Manually clone `SanitizedTransactionTTL` let SanitizedTransactionTTL { transaction, - max_age_slot, + max_age, } = transaction_state.transaction_ttl(); let transaction_ttl = SanitizedTransactionTTL { transaction: transaction.clone(), - max_age_slot: *max_age_slot, + max_age: *max_age, }; transaction_state.transition_to_unprocessed(transaction_ttl); // invalid transition } @@ -322,7 +328,13 @@ mod tests { transaction_state, TransactionState::Unprocessed { .. } )); - assert_eq!(transaction_ttl.max_age_slot, Slot::MAX); + assert_eq!( + transaction_ttl.max_age, + MaxAge { + epoch_invalidation_slot: Slot::MAX, + alt_invalidation_slot: Slot::MAX, + } + ); let _ = transaction_state.transition_to_pending(); assert!(matches!( @@ -340,7 +352,13 @@ mod tests { transaction_state, TransactionState::Unprocessed { .. } )); - assert_eq!(transaction_ttl.max_age_slot, Slot::MAX); + assert_eq!( + transaction_ttl.max_age, + MaxAge { + epoch_invalidation_slot: Slot::MAX, + alt_invalidation_slot: Slot::MAX, + } + ); // ensure transaction_ttl is not lost through state transitions let transaction_ttl = transaction_state.transition_to_pending(); @@ -355,6 +373,12 @@ mod tests { transaction_state, TransactionState::Unprocessed { .. } )); - assert_eq!(transaction_ttl.max_age_slot, Slot::MAX); + assert_eq!( + transaction_ttl.max_age, + MaxAge { + epoch_invalidation_slot: Slot::MAX, + alt_invalidation_slot: Slot::MAX, + } + ); } } diff --git a/prio-graph-scheduler/src/transaction_state_container.rs b/prio-graph-scheduler/src/transaction_state_container.rs index 4e1e998ba6f751..9afcd73ae64996 100644 --- a/prio-graph-scheduler/src/transaction_state_container.rs +++ b/prio-graph-scheduler/src/transaction_state_container.rs @@ -156,7 +156,8 @@ mod tests { slot_history::Slot, system_instruction, transaction::{SanitizedTransaction, Transaction}, - } + }, + crate::scheduler_messages::MaxAge, }; /// Returns (transaction_ttl, priority, cost) @@ -191,7 +192,10 @@ mod tests { ); let transaction_ttl = SanitizedTransactionTTL { transaction: tx, - max_age_slot: Slot::MAX, + max_age: MaxAge { + epoch_invalidation_slot: Slot::MAX, + alt_invalidation_slot: Slot::MAX, + }, }; const TEST_TRANSACTION_COST: u64 = 5000; (transaction_ttl, packet, priority, TEST_TRANSACTION_COST) diff --git a/program-test/src/programs.rs b/program-test/src/programs.rs index e839b2c090097a..f4d773fbdeffb0 100644 --- a/program-test/src/programs.rs +++ b/program-test/src/programs.rs @@ -21,7 +21,7 @@ static SPL_PROGRAMS: &[(Pubkey, Pubkey, &[u8])] = &[ ( solana_inline_spl::token_2022::ID, solana_sdk::bpf_loader_upgradeable::ID, - include_bytes!("programs/spl_token_2022-1.0.0.so"), + include_bytes!("programs/spl_token_2022-5.0.2.so"), ), ( spl_memo_1_0::ID, diff --git a/program-test/src/programs/spl_token_2022-1.0.0.so b/program-test/src/programs/spl_token_2022-1.0.0.so deleted file mode 100755 index 796fafc4cc13ab..00000000000000 Binary files a/program-test/src/programs/spl_token_2022-1.0.0.so and /dev/null differ diff --git a/program-test/src/programs/spl_token_2022-5.0.2.so b/program-test/src/programs/spl_token_2022-5.0.2.so new file mode 100755 index 00000000000000..e7d37184390111 Binary files /dev/null and b/program-test/src/programs/spl_token_2022-5.0.2.so differ diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index c77621fa74892a..da726ceaeca3a8 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -4712,6 +4712,7 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", + "solana-instruction", "solana-program", ] @@ -5299,7 +5300,11 @@ name = "solana-feature-set" version = "2.1.0" dependencies = [ "lazy_static", - "solana-program", + "solana-clock", + "solana-epoch-schedule", + "solana-hash", + "solana-pubkey", + "solana-sha256-hasher", ] [[package]] @@ -5416,7 +5421,7 @@ name = "solana-inline-spl" version = "2.1.0" dependencies = [ "bytemuck", - "solana-program", + "solana-pubkey", ] [[package]] @@ -5580,6 +5585,10 @@ dependencies = [ "solana-define-syscall", ] +[[package]] +name = "solana-native-token" +version = "2.1.0" + [[package]] name = "solana-net-utils" version = "2.1.0" @@ -5697,6 +5706,8 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-msg", + "solana-native-token", + "solana-program-entrypoint", "solana-program-error", "solana-program-memory", "solana-program-option", @@ -5715,6 +5726,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "solana-program-entrypoint" +version = "2.1.0" +dependencies = [ + "solana-account-info", + "solana-msg", + "solana-program-error", + "solana-pubkey", +] + [[package]] name = "solana-program-error" version = "2.1.0" @@ -5824,6 +5845,7 @@ dependencies = [ "getrandom 0.2.10", "js-sys", "num-traits", + "rand 0.8.5", "serde", "serde_derive", "solana-atomic-u64", @@ -6653,6 +6675,7 @@ dependencies = [ "solana-decode-error", "solana-derivation-path", "solana-feature-set", + "solana-native-token", "solana-program", "solana-program-memory", "solana-pubkey", @@ -7220,6 +7243,7 @@ dependencies = [ "bytemuck_derive", "curve25519-dalek 4.1.3", "itertools 0.12.1", + "js-sys", "lazy_static", "merlin", "num-derive", @@ -7234,6 +7258,7 @@ dependencies = [ "solana-sdk", "subtle", "thiserror", + "wasm-bindgen", "zeroize", ] diff --git a/rpc/src/slot_status_notifier.rs b/rpc/src/slot_status_notifier.rs index 97a84da42f33bf..38e9bf60a6e091 100644 --- a/rpc/src/slot_status_notifier.rs +++ b/rpc/src/slot_status_notifier.rs @@ -18,6 +18,9 @@ pub trait SlotStatusNotifierInterface { /// Notified when the slot is completed. fn notify_completed(&self, slot: Slot); + + /// Notified when the slot has bank created. + fn notify_created_bank(&self, slot: Slot, parent: Slot); } pub type SlotStatusNotifier = Arc>; diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 6167e603f63e4e..0a4cb785f2c091 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -1048,7 +1048,7 @@ impl Bank { }; bank.transaction_processor = - TransactionBatchProcessor::new(bank.slot, bank.epoch, HashSet::default()); + TransactionBatchProcessor::new_uninitialized(bank.slot, bank.epoch, HashSet::default()); let accounts_data_size_initial = bank.get_total_accounts_stats().unwrap().data_len as u64; bank.accounts_data_size_initial = accounts_data_size_initial; @@ -1702,7 +1702,7 @@ impl Bank { }; bank.transaction_processor = - TransactionBatchProcessor::new(bank.slot, bank.epoch, HashSet::default()); + TransactionBatchProcessor::new_uninitialized(bank.slot, bank.epoch, HashSet::default()); let thread_pool = ThreadPoolBuilder::new() .thread_name(|i| format!("solBnkNewFlds{i:02}")) @@ -2869,11 +2869,34 @@ impl Bank { stake_weighted_timestamp } + /// Recalculates the bank hash + /// + /// This is used by ledger-tool when creating a snapshot, which + /// recalcuates the bank hash. + /// + /// Note that the account state is *not* allowed to change by rehashing. + /// If it does, this function will panic. + /// If modifying accounts in ledger-tool is needed, create a new bank. pub fn rehash(&self) { + let get_delta_hash = || { + self.rc + .accounts + .accounts_db + .get_accounts_delta_hash(self.slot()) + }; + let mut hash = self.hash.write().unwrap(); + let curr_accounts_delta_hash = get_delta_hash(); let new = self.hash_internal_state(); + if let Some(curr_accounts_delta_hash) = curr_accounts_delta_hash { + let new_accounts_delta_hash = get_delta_hash().unwrap(); + assert_eq!( + new_accounts_delta_hash, curr_accounts_delta_hash, + "rehashing is not allowed to change the account state", + ); + } if new != *hash { - warn!("Updating bank hash to {}", new); + warn!("Updating bank hash to {new}"); *hash = new; } } @@ -2911,6 +2934,7 @@ impl Bank { } // dangerous; don't use this; this is only needed for ledger-tool's special command + #[cfg(feature = "dev-context-only-utils")] pub fn unfreeze_for_ledger_tool(&self) { self.freeze_started.store(false, Relaxed); } diff --git a/runtime/src/bank/address_lookup_table.rs b/runtime/src/bank/address_lookup_table.rs index 4fa4e2bc0f570a..cb195202c9ddac 100644 --- a/runtime/src/bank/address_lookup_table.rs +++ b/runtime/src/bank/address_lookup_table.rs @@ -2,6 +2,7 @@ use { super::Bank, solana_sdk::{ address_lookup_table::error::AddressLookupError, + clock::Slot, message::{ v0::{LoadedAddresses, MessageAddressTableLookup}, AddressLoaderError, @@ -32,22 +33,25 @@ impl AddressLoader for &Bank { .iter() .map(SVMMessageAddressTableLookup::from), ) + .map(|(loaded_addresses, _deactivation_slot)| loaded_addresses) } } impl Bank { - /// Load addresses from an iterator of `SVMMessageAddressTableLookup`. + /// Load addresses from an iterator of `SVMMessageAddressTableLookup`, + /// additionally returning the minimum deactivation slot across all referenced ALTs pub fn load_addresses_from_ref<'a>( &self, address_table_lookups: impl Iterator>, - ) -> Result { + ) -> Result<(LoadedAddresses, Slot), AddressLoaderError> { let slot_hashes = self .transaction_processor .sysvar_cache() .get_slot_hashes() .map_err(|_| AddressLoaderError::SlotHashesSysvarNotFound)?; - address_table_lookups + let mut deactivation_slot = u64::MAX; + let loaded_addresses = address_table_lookups .map(|address_table_lookup| { self.rc .accounts @@ -56,8 +60,14 @@ impl Bank { address_table_lookup, &slot_hashes, ) + .map(|(loaded_addresses, table_deactivation_slot)| { + deactivation_slot = deactivation_slot.min(table_deactivation_slot); + loaded_addresses + }) .map_err(into_address_loader_error) }) - .collect::>() + .collect::>()?; + + Ok((loaded_addresses, deactivation_slot)) } } diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 454aa32215de11..9a8324d58a0165 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -13252,3 +13252,48 @@ fn test_bank_epoch_stakes() { ); } } + +#[test] +fn test_rehash_good() { + let ten_sol = 10 * LAMPORTS_PER_SOL; + let (genesis_config, _mint) = create_genesis_config(ten_sol); + let bank = Bank::new_for_tests(&genesis_config); + + let lamports = 123_456_789; + let account = AccountSharedData::new(lamports, 0, &Pubkey::default()); + let pubkey = Pubkey::new_unique(); + bank.store_account_and_update_capitalization(&pubkey, &account); + + // freeze the bank to trigger hash calculation + bank.freeze(); + + // ensure the bank hash is the same before and after rehashing + let prev_bank_hash = bank.hash(); + bank.rehash(); + let post_bank_hash = bank.hash(); + assert_eq!(post_bank_hash, prev_bank_hash); +} + +#[test] +#[should_panic(expected = "rehashing is not allowed to change the account state")] +fn test_rehash_bad() { + let ten_sol = 10 * LAMPORTS_PER_SOL; + let (genesis_config, _mint) = create_genesis_config(ten_sol); + let bank = Bank::new_for_tests(&genesis_config); + + let mut account = AccountSharedData::new(ten_sol, 0, &Pubkey::default()); + let pubkey = Pubkey::new_unique(); + bank.store_account_and_update_capitalization(&pubkey, &account); + + // freeze the bank to trigger hash calculation + bank.freeze(); + + // change an account, which will cause rehashing to panic + account.checked_add_lamports(ten_sol).unwrap(); + bank.rc + .accounts + .store_accounts_cached((bank.slot(), [(&pubkey, &account)].as_slice())); + + // let the show begin + bank.rehash(); +} diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 10eb2b0f8a6c8a..2ab85402d1c3b1 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -33,6 +33,7 @@ full = [ "libsecp256k1", "sha3", "digest", + "solana-pubkey/rand", ] borsh = ["dep:borsh", "solana-program/borsh", "solana-secp256k1-recover/borsh"] dev-context-only-utils = ["qualifier_attr", "solana-account/dev-context-only-utils"] @@ -95,9 +96,10 @@ solana-frozen-abi = { workspace = true, optional = true, features = [ solana-frozen-abi-macro = { workspace = true, optional = true, features = [ "frozen-abi", ] } +solana-native-token = { workspace = true } solana-program = { workspace = true } solana-program-memory = { workspace = true } -solana-pubkey = { workspace = true } +solana-pubkey = { workspace = true, default-features = false, features = ["std"] } solana-sanitize = { workspace = true } solana-sdk-macro = { workspace = true } solana-secp256k1-recover = { workspace = true } diff --git a/sdk/account/Cargo.toml b/sdk/account/Cargo.toml index 015736eebe08c4..62d35391142d64 100644 --- a/sdk/account/Cargo.toml +++ b/sdk/account/Cargo.toml @@ -17,14 +17,16 @@ serde_bytes = { workspace = true, optional = true } serde_derive = { workspace = true, optional = true } solana-frozen-abi = { workspace = true, optional = true } solana-frozen-abi-macro = { workspace = true, optional = true } +solana-instruction = { workspace = true, optional = true } solana-logger = { workspace = true, optional = true } solana-program = { workspace = true } [dev-dependencies] solana-account = { path = ".", features = ["dev-context-only-utils"] } +solana-pubkey = { workspace = true } [features] -bincode = ["dep:bincode", "serde"] +bincode = ["dep:bincode", "dep:solana-instruction", "serde"] dev-context-only-utils = ["bincode", "dep:qualifier_attr"] frozen-abi = [ "dep:solana-frozen-abi", diff --git a/sdk/account/src/lib.rs b/sdk/account/src/lib.rs index 762e41a5dd5abf..0d6df6ac6d8da4 100644 --- a/sdk/account/src/lib.rs +++ b/sdk/account/src/lib.rs @@ -29,6 +29,8 @@ use { sync::Arc, }, }; +#[cfg(feature = "bincode")] +pub mod state_traits; /// An Account with data that is stored on chain #[repr(C)] diff --git a/sdk/src/account_utils.rs b/sdk/account/src/state_traits.rs similarity index 93% rename from sdk/src/account_utils.rs rename to sdk/account/src/state_traits.rs index 7338d64cc33498..9b44e43eab159e 100644 --- a/sdk/src/account_utils.rs +++ b/sdk/account/src/state_traits.rs @@ -1,9 +1,9 @@ //! Useful extras for `Account` state. use { - crate::instruction::InstructionError, + crate::{Account, AccountSharedData}, bincode::ErrorKind, - solana_account::{Account, AccountSharedData}, + solana_instruction::error::InstructionError, std::cell::Ref, }; @@ -64,7 +64,7 @@ where #[cfg(test)] mod tests { - use {super::*, crate::pubkey::Pubkey, solana_account::AccountSharedData}; + use {super::*, solana_pubkey::Pubkey}; #[test] fn test_account_state() { diff --git a/sdk/cargo-build-sbf/tests/crates/package-metadata/Cargo.toml b/sdk/cargo-build-sbf/tests/crates/package-metadata/Cargo.toml index 4de95889d4bf4d..b4ff4139640939 100644 --- a/sdk/cargo-build-sbf/tests/crates/package-metadata/Cargo.toml +++ b/sdk/cargo-build-sbf/tests/crates/package-metadata/Cargo.toml @@ -11,8 +11,10 @@ publish = false [package.metadata.solana] tools-version = "v1.43" +program-id = "MyProgram1111111111111111111111111111111111" [dependencies] +solana-package-metadata = { path = "../../../../package-metadata", version = "=2.1.0" } solana-program = { path = "../../../../program", version = "=2.1.0" } [lib] diff --git a/sdk/cargo-build-sbf/tests/crates/package-metadata/src/lib.rs b/sdk/cargo-build-sbf/tests/crates/package-metadata/src/lib.rs index a6f2c05b770881..ee364d392441a8 100644 --- a/sdk/cargo-build-sbf/tests/crates/package-metadata/src/lib.rs +++ b/sdk/cargo-build-sbf/tests/crates/package-metadata/src/lib.rs @@ -2,6 +2,7 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +solana_package_metadata::declare_id_with_package_metadata!("solana.program-id"); solana_program::entrypoint!(process_instruction); fn process_instruction( _program_id: &Pubkey, diff --git a/sdk/epoch-schedule/src/lib.rs b/sdk/epoch-schedule/src/lib.rs index e0d9d1c24e3b30..3ee21acdb9a12e 100644 --- a/sdk/epoch-schedule/src/lib.rs +++ b/sdk/epoch-schedule/src/lib.rs @@ -10,6 +10,8 @@ //! though the length of an epoch does — during the initial launch of //! the chain there is a "warmup" period, where epochs are short, with subsequent //! epochs increasing in slots until they last for [`DEFAULT_SLOTS_PER_EPOCH`]. +//! +//! [`DEFAULT_SLOTS_PER_EPOCH`]: https://docs.rs/solana-clock/latest/solana_clock/constant.DEFAULT_SLOTS_PER_EPOCH.html #![cfg_attr(feature = "frozen-abi", feature(min_specialization))] #![no_std] #[cfg(feature = "frozen-abi")] diff --git a/sdk/feature-set/Cargo.toml b/sdk/feature-set/Cargo.toml index d6f2824b08982e..7352ff37a9680f 100644 --- a/sdk/feature-set/Cargo.toml +++ b/sdk/feature-set/Cargo.toml @@ -11,13 +11,17 @@ edition = { workspace = true } [dependencies] lazy_static = { workspace = true } +solana-clock = { workspace = true } +solana-epoch-schedule = { workspace = true } solana-frozen-abi = { workspace = true, optional = true, features = [ "frozen-abi", ] } solana-frozen-abi-macro = { workspace = true, optional = true, features = [ "frozen-abi", ] } -solana-program = { workspace = true } +solana-hash = { workspace = true } +solana-pubkey = { workspace = true } +solana-sha256-hasher = { workspace = true } [features] frozen-abi = [ diff --git a/sdk/feature-set/src/lib.rs b/sdk/feature-set/src/lib.rs index 6567404a947db5..83d59749758067 100644 --- a/sdk/feature-set/src/lib.rs +++ b/sdk/feature-set/src/lib.rs @@ -12,7 +12,7 @@ //! through these steps, the PR process will facilitate a keypair holder being picked. That //! person will generate the keypair, provide pubkey for PR, and ultimately enable the feature. //! 2. Add a public module for the feature, specifying keypair pubkey as the id with -//! `solana_program::declare_id!()` within the module. +//! `solana_pubkey::declare_id!()` within the module. //! Additionally, add an entry to `FEATURE_NAMES` map. //! 3. Add desired logic to check for and switch on feature availability. //! @@ -21,854 +21,852 @@ use { lazy_static::lazy_static, - solana_program::{ - clock::Slot, - epoch_schedule::EpochSchedule, - hash::{Hash, Hasher}, - pubkey::Pubkey, - stake_history::Epoch, - }, + solana_clock::{Epoch, Slot}, + solana_epoch_schedule::EpochSchedule, + solana_hash::Hash, + solana_pubkey::Pubkey, + solana_sha256_hasher::Hasher, std::collections::{HashMap, HashSet}, }; pub mod deprecate_rewards_sysvar { - solana_program::declare_id!("GaBtBJvmS4Arjj5W1NmFcyvPjsHN38UGYDq2MDwbs9Qu"); + solana_pubkey::declare_id!("GaBtBJvmS4Arjj5W1NmFcyvPjsHN38UGYDq2MDwbs9Qu"); } pub mod pico_inflation { - solana_program::declare_id!("4RWNif6C2WCNiKVW7otP4G7dkmkHGyKQWRpuZ1pxKU5m"); + solana_pubkey::declare_id!("4RWNif6C2WCNiKVW7otP4G7dkmkHGyKQWRpuZ1pxKU5m"); } pub mod full_inflation { pub mod devnet_and_testnet { - solana_program::declare_id!("DT4n6ABDqs6w4bnfwrXT9rsprcPf6cdDga1egctaPkLC"); + solana_pubkey::declare_id!("DT4n6ABDqs6w4bnfwrXT9rsprcPf6cdDga1egctaPkLC"); } pub mod mainnet { pub mod certusone { pub mod vote { - solana_program::declare_id!("BzBBveUDymEYoYzcMWNQCx3cd4jQs7puaVFHLtsbB6fm"); + solana_pubkey::declare_id!("BzBBveUDymEYoYzcMWNQCx3cd4jQs7puaVFHLtsbB6fm"); } pub mod enable { - solana_program::declare_id!("7XRJcS5Ud5vxGB54JbK9N2vBZVwnwdBNeJW1ibRgD9gx"); + solana_pubkey::declare_id!("7XRJcS5Ud5vxGB54JbK9N2vBZVwnwdBNeJW1ibRgD9gx"); } } } } pub mod secp256k1_program_enabled { - solana_program::declare_id!("E3PHP7w8kB7np3CTQ1qQ2tW3KCtjRSXBQgW9vM2mWv2Y"); + solana_pubkey::declare_id!("E3PHP7w8kB7np3CTQ1qQ2tW3KCtjRSXBQgW9vM2mWv2Y"); } pub mod spl_token_v2_multisig_fix { - solana_program::declare_id!("E5JiFDQCwyC6QfT9REFyMpfK2mHcmv1GUDySU1Ue7TYv"); + solana_pubkey::declare_id!("E5JiFDQCwyC6QfT9REFyMpfK2mHcmv1GUDySU1Ue7TYv"); } pub mod no_overflow_rent_distribution { - solana_program::declare_id!("4kpdyrcj5jS47CZb2oJGfVxjYbsMm2Kx97gFyZrxxwXz"); + solana_pubkey::declare_id!("4kpdyrcj5jS47CZb2oJGfVxjYbsMm2Kx97gFyZrxxwXz"); } pub mod filter_stake_delegation_accounts { - solana_program::declare_id!("GE7fRxmW46K6EmCD9AMZSbnaJ2e3LfqCZzdHi9hmYAgi"); + solana_pubkey::declare_id!("GE7fRxmW46K6EmCD9AMZSbnaJ2e3LfqCZzdHi9hmYAgi"); } pub mod require_custodian_for_locked_stake_authorize { - solana_program::declare_id!("D4jsDcXaqdW8tDAWn8H4R25Cdns2YwLneujSL1zvjW6R"); + solana_pubkey::declare_id!("D4jsDcXaqdW8tDAWn8H4R25Cdns2YwLneujSL1zvjW6R"); } pub mod spl_token_v2_self_transfer_fix { - solana_program::declare_id!("BL99GYhdjjcv6ys22C9wPgn2aTVERDbPHHo4NbS3hgp7"); + solana_pubkey::declare_id!("BL99GYhdjjcv6ys22C9wPgn2aTVERDbPHHo4NbS3hgp7"); } pub mod warp_timestamp_again { - solana_program::declare_id!("GvDsGDkH5gyzwpDhxNixx8vtx1kwYHH13RiNAPw27zXb"); + solana_pubkey::declare_id!("GvDsGDkH5gyzwpDhxNixx8vtx1kwYHH13RiNAPw27zXb"); } pub mod check_init_vote_data { - solana_program::declare_id!("3ccR6QpxGYsAbWyfevEtBNGfWV4xBffxRj2tD6A9i39F"); + solana_pubkey::declare_id!("3ccR6QpxGYsAbWyfevEtBNGfWV4xBffxRj2tD6A9i39F"); } pub mod secp256k1_recover_syscall_enabled { - solana_program::declare_id!("6RvdSWHh8oh72Dp7wMTS2DBkf3fRPtChfNrAo3cZZoXJ"); + solana_pubkey::declare_id!("6RvdSWHh8oh72Dp7wMTS2DBkf3fRPtChfNrAo3cZZoXJ"); } pub mod system_transfer_zero_check { - solana_program::declare_id!("BrTR9hzw4WBGFP65AJMbpAo64DcA3U6jdPSga9fMV5cS"); + solana_pubkey::declare_id!("BrTR9hzw4WBGFP65AJMbpAo64DcA3U6jdPSga9fMV5cS"); } pub mod blake3_syscall_enabled { - solana_program::declare_id!("HTW2pSyErTj4BV6KBM9NZ9VBUJVxt7sacNWcf76wtzb3"); + solana_pubkey::declare_id!("HTW2pSyErTj4BV6KBM9NZ9VBUJVxt7sacNWcf76wtzb3"); } pub mod dedupe_config_program_signers { - solana_program::declare_id!("8kEuAshXLsgkUEdcFVLqrjCGGHVWFW99ZZpxvAzzMtBp"); + solana_pubkey::declare_id!("8kEuAshXLsgkUEdcFVLqrjCGGHVWFW99ZZpxvAzzMtBp"); } pub mod verify_tx_signatures_len { - solana_program::declare_id!("EVW9B5xD9FFK7vw1SBARwMA4s5eRo5eKJdKpsBikzKBz"); + solana_pubkey::declare_id!("EVW9B5xD9FFK7vw1SBARwMA4s5eRo5eKJdKpsBikzKBz"); } pub mod vote_stake_checked_instructions { - solana_program::declare_id!("BcWknVcgvonN8sL4HE4XFuEVgfcee5MwxWPAgP6ZV89X"); + solana_pubkey::declare_id!("BcWknVcgvonN8sL4HE4XFuEVgfcee5MwxWPAgP6ZV89X"); } pub mod rent_for_sysvars { - solana_program::declare_id!("BKCPBQQBZqggVnFso5nQ8rQ4RwwogYwjuUt9biBjxwNF"); + solana_pubkey::declare_id!("BKCPBQQBZqggVnFso5nQ8rQ4RwwogYwjuUt9biBjxwNF"); } pub mod libsecp256k1_0_5_upgrade_enabled { - solana_program::declare_id!("DhsYfRjxfnh2g7HKJYSzT79r74Afa1wbHkAgHndrA1oy"); + solana_pubkey::declare_id!("DhsYfRjxfnh2g7HKJYSzT79r74Afa1wbHkAgHndrA1oy"); } pub mod tx_wide_compute_cap { - solana_program::declare_id!("5ekBxc8itEnPv4NzGJtr8BVVQLNMQuLMNQQj7pHoLNZ9"); + solana_pubkey::declare_id!("5ekBxc8itEnPv4NzGJtr8BVVQLNMQuLMNQQj7pHoLNZ9"); } pub mod spl_token_v2_set_authority_fix { - solana_program::declare_id!("FToKNBYyiF4ky9s8WsmLBXHCht17Ek7RXaLZGHzzQhJ1"); + solana_pubkey::declare_id!("FToKNBYyiF4ky9s8WsmLBXHCht17Ek7RXaLZGHzzQhJ1"); } pub mod merge_nonce_error_into_system_error { - solana_program::declare_id!("21AWDosvp3pBamFW91KB35pNoaoZVTM7ess8nr2nt53B"); + solana_pubkey::declare_id!("21AWDosvp3pBamFW91KB35pNoaoZVTM7ess8nr2nt53B"); } pub mod disable_fees_sysvar { - solana_program::declare_id!("JAN1trEUEtZjgXYzNBYHU9DYd7GnThhXfFP7SzPXkPsG"); + solana_pubkey::declare_id!("JAN1trEUEtZjgXYzNBYHU9DYd7GnThhXfFP7SzPXkPsG"); } pub mod stake_merge_with_unmatched_credits_observed { - solana_program::declare_id!("meRgp4ArRPhD3KtCY9c5yAf2med7mBLsjKTPeVUHqBL"); + solana_pubkey::declare_id!("meRgp4ArRPhD3KtCY9c5yAf2med7mBLsjKTPeVUHqBL"); } pub mod zk_token_sdk_enabled { - solana_program::declare_id!("zk1snxsc6Fh3wsGNbbHAJNHiJoYgF29mMnTSusGx5EJ"); + solana_pubkey::declare_id!("zk1snxsc6Fh3wsGNbbHAJNHiJoYgF29mMnTSusGx5EJ"); } pub mod curve25519_syscall_enabled { - solana_program::declare_id!("7rcw5UtqgDTBBv2EcynNfYckgdAaH1MAsCjKgXMkN7Ri"); + solana_pubkey::declare_id!("7rcw5UtqgDTBBv2EcynNfYckgdAaH1MAsCjKgXMkN7Ri"); } pub mod curve25519_restrict_msm_length { - solana_program::declare_id!("eca6zf6JJRjQsYYPkBHF3N32MTzur4n2WL4QiiacPCL"); + solana_pubkey::declare_id!("eca6zf6JJRjQsYYPkBHF3N32MTzur4n2WL4QiiacPCL"); } pub mod versioned_tx_message_enabled { - solana_program::declare_id!("3KZZ6Ks1885aGBQ45fwRcPXVBCtzUvxhUTkwKMR41Tca"); + solana_pubkey::declare_id!("3KZZ6Ks1885aGBQ45fwRcPXVBCtzUvxhUTkwKMR41Tca"); } pub mod libsecp256k1_fail_on_bad_count { - solana_program::declare_id!("8aXvSuopd1PUj7UhehfXJRg6619RHp8ZvwTyyJHdUYsj"); + solana_pubkey::declare_id!("8aXvSuopd1PUj7UhehfXJRg6619RHp8ZvwTyyJHdUYsj"); } pub mod libsecp256k1_fail_on_bad_count2 { - solana_program::declare_id!("54KAoNiUERNoWWUhTWWwXgym94gzoXFVnHyQwPA18V9A"); + solana_pubkey::declare_id!("54KAoNiUERNoWWUhTWWwXgym94gzoXFVnHyQwPA18V9A"); } pub mod instructions_sysvar_owned_by_sysvar { - solana_program::declare_id!("H3kBSaKdeiUsyHmeHqjJYNc27jesXZ6zWj3zWkowQbkV"); + solana_pubkey::declare_id!("H3kBSaKdeiUsyHmeHqjJYNc27jesXZ6zWj3zWkowQbkV"); } pub mod stake_program_advance_activating_credits_observed { - solana_program::declare_id!("SAdVFw3RZvzbo6DvySbSdBnHN4gkzSTH9dSxesyKKPj"); + solana_pubkey::declare_id!("SAdVFw3RZvzbo6DvySbSdBnHN4gkzSTH9dSxesyKKPj"); } pub mod credits_auto_rewind { - solana_program::declare_id!("BUS12ciZ5gCoFafUHWW8qaFMMtwFQGVxjsDheWLdqBE2"); + solana_pubkey::declare_id!("BUS12ciZ5gCoFafUHWW8qaFMMtwFQGVxjsDheWLdqBE2"); } pub mod demote_program_write_locks { - solana_program::declare_id!("3E3jV7v9VcdJL8iYZUMax9DiDno8j7EWUVbhm9RtShj2"); + solana_pubkey::declare_id!("3E3jV7v9VcdJL8iYZUMax9DiDno8j7EWUVbhm9RtShj2"); } pub mod ed25519_program_enabled { - solana_program::declare_id!("6ppMXNYLhVd7GcsZ5uV11wQEW7spppiMVfqQv5SXhDpX"); + solana_pubkey::declare_id!("6ppMXNYLhVd7GcsZ5uV11wQEW7spppiMVfqQv5SXhDpX"); } pub mod return_data_syscall_enabled { - solana_program::declare_id!("DwScAzPUjuv65TMbDnFY7AgwmotzWy3xpEJMXM3hZFaB"); + solana_pubkey::declare_id!("DwScAzPUjuv65TMbDnFY7AgwmotzWy3xpEJMXM3hZFaB"); } pub mod reduce_required_deploy_balance { - solana_program::declare_id!("EBeznQDjcPG8491sFsKZYBi5S5jTVXMpAKNDJMQPS2kq"); + solana_pubkey::declare_id!("EBeznQDjcPG8491sFsKZYBi5S5jTVXMpAKNDJMQPS2kq"); } pub mod sol_log_data_syscall_enabled { - solana_program::declare_id!("6uaHcKPGUy4J7emLBgUTeufhJdiwhngW6a1R9B7c2ob9"); + solana_pubkey::declare_id!("6uaHcKPGUy4J7emLBgUTeufhJdiwhngW6a1R9B7c2ob9"); } pub mod stakes_remove_delegation_if_inactive { - solana_program::declare_id!("HFpdDDNQjvcXnXKec697HDDsyk6tFoWS2o8fkxuhQZpL"); + solana_pubkey::declare_id!("HFpdDDNQjvcXnXKec697HDDsyk6tFoWS2o8fkxuhQZpL"); } pub mod do_support_realloc { - solana_program::declare_id!("75m6ysz33AfLA5DDEzWM1obBrnPQRSsdVQ2nRmc8Vuu1"); + solana_pubkey::declare_id!("75m6ysz33AfLA5DDEzWM1obBrnPQRSsdVQ2nRmc8Vuu1"); } pub mod prevent_calling_precompiles_as_programs { - solana_program::declare_id!("4ApgRX3ud6p7LNMJmsuaAcZY5HWctGPr5obAsjB3A54d"); + solana_pubkey::declare_id!("4ApgRX3ud6p7LNMJmsuaAcZY5HWctGPr5obAsjB3A54d"); } pub mod optimize_epoch_boundary_updates { - solana_program::declare_id!("265hPS8k8xJ37ot82KEgjRunsUp5w4n4Q4VwwiN9i9ps"); + solana_pubkey::declare_id!("265hPS8k8xJ37ot82KEgjRunsUp5w4n4Q4VwwiN9i9ps"); } pub mod remove_native_loader { - solana_program::declare_id!("HTTgmruMYRZEntyL3EdCDdnS6e4D5wRq1FA7kQsb66qq"); + solana_pubkey::declare_id!("HTTgmruMYRZEntyL3EdCDdnS6e4D5wRq1FA7kQsb66qq"); } pub mod send_to_tpu_vote_port { - solana_program::declare_id!("C5fh68nJ7uyKAuYZg2x9sEQ5YrVf3dkW6oojNBSc3Jvo"); + solana_pubkey::declare_id!("C5fh68nJ7uyKAuYZg2x9sEQ5YrVf3dkW6oojNBSc3Jvo"); } pub mod requestable_heap_size { - solana_program::declare_id!("CCu4boMmfLuqcmfTLPHQiUo22ZdUsXjgzPAURYaWt1Bw"); + solana_pubkey::declare_id!("CCu4boMmfLuqcmfTLPHQiUo22ZdUsXjgzPAURYaWt1Bw"); } pub mod disable_fee_calculator { - solana_program::declare_id!("2jXx2yDmGysmBKfKYNgLj2DQyAQv6mMk2BPh4eSbyB4H"); + solana_pubkey::declare_id!("2jXx2yDmGysmBKfKYNgLj2DQyAQv6mMk2BPh4eSbyB4H"); } pub mod add_compute_budget_program { - solana_program::declare_id!("4d5AKtxoh93Dwm1vHXUU3iRATuMndx1c431KgT2td52r"); + solana_pubkey::declare_id!("4d5AKtxoh93Dwm1vHXUU3iRATuMndx1c431KgT2td52r"); } pub mod nonce_must_be_writable { - solana_program::declare_id!("BiCU7M5w8ZCMykVSyhZ7Q3m2SWoR2qrEQ86ERcDX77ME"); + solana_pubkey::declare_id!("BiCU7M5w8ZCMykVSyhZ7Q3m2SWoR2qrEQ86ERcDX77ME"); } pub mod spl_token_v3_3_0_release { - solana_program::declare_id!("Ftok2jhqAqxUWEiCVRrfRs9DPppWP8cgTB7NQNKL88mS"); + solana_pubkey::declare_id!("Ftok2jhqAqxUWEiCVRrfRs9DPppWP8cgTB7NQNKL88mS"); } pub mod leave_nonce_on_success { - solana_program::declare_id!("E8MkiWZNNPGU6n55jkGzyj8ghUmjCHRmDFdYYFYHxWhQ"); + solana_pubkey::declare_id!("E8MkiWZNNPGU6n55jkGzyj8ghUmjCHRmDFdYYFYHxWhQ"); } pub mod reject_empty_instruction_without_program { - solana_program::declare_id!("9kdtFSrXHQg3hKkbXkQ6trJ3Ja1xpJ22CTFSNAciEwmL"); + solana_pubkey::declare_id!("9kdtFSrXHQg3hKkbXkQ6trJ3Ja1xpJ22CTFSNAciEwmL"); } pub mod fixed_memcpy_nonoverlapping_check { - solana_program::declare_id!("36PRUK2Dz6HWYdG9SpjeAsF5F3KxnFCakA2BZMbtMhSb"); + solana_pubkey::declare_id!("36PRUK2Dz6HWYdG9SpjeAsF5F3KxnFCakA2BZMbtMhSb"); } pub mod reject_non_rent_exempt_vote_withdraws { - solana_program::declare_id!("7txXZZD6Um59YoLMF7XUNimbMjsqsWhc7g2EniiTrmp1"); + solana_pubkey::declare_id!("7txXZZD6Um59YoLMF7XUNimbMjsqsWhc7g2EniiTrmp1"); } pub mod evict_invalid_stakes_cache_entries { - solana_program::declare_id!("EMX9Q7TVFAmQ9V1CggAkhMzhXSg8ECp7fHrWQX2G1chf"); + solana_pubkey::declare_id!("EMX9Q7TVFAmQ9V1CggAkhMzhXSg8ECp7fHrWQX2G1chf"); } pub mod allow_votes_to_directly_update_vote_state { - solana_program::declare_id!("Ff8b1fBeB86q8cjq47ZhsQLgv5EkHu3G1C99zjUfAzrq"); + solana_pubkey::declare_id!("Ff8b1fBeB86q8cjq47ZhsQLgv5EkHu3G1C99zjUfAzrq"); } pub mod max_tx_account_locks { - solana_program::declare_id!("CBkDroRDqm8HwHe6ak9cguPjUomrASEkfmxEaZ5CNNxz"); + solana_pubkey::declare_id!("CBkDroRDqm8HwHe6ak9cguPjUomrASEkfmxEaZ5CNNxz"); } pub mod require_rent_exempt_accounts { - solana_program::declare_id!("BkFDxiJQWZXGTZaJQxH7wVEHkAmwCgSEVkrvswFfRJPD"); + solana_pubkey::declare_id!("BkFDxiJQWZXGTZaJQxH7wVEHkAmwCgSEVkrvswFfRJPD"); } pub mod filter_votes_outside_slot_hashes { - solana_program::declare_id!("3gtZPqvPpsbXZVCx6hceMfWxtsmrjMzmg8C7PLKSxS2d"); + solana_pubkey::declare_id!("3gtZPqvPpsbXZVCx6hceMfWxtsmrjMzmg8C7PLKSxS2d"); } pub mod update_syscall_base_costs { - solana_program::declare_id!("2h63t332mGCCsWK2nqqqHhN4U9ayyqhLVFvczznHDoTZ"); + solana_pubkey::declare_id!("2h63t332mGCCsWK2nqqqHhN4U9ayyqhLVFvczznHDoTZ"); } pub mod stake_deactivate_delinquent_instruction { - solana_program::declare_id!("437r62HoAdUb63amq3D7ENnBLDhHT2xY8eFkLJYVKK4x"); + solana_pubkey::declare_id!("437r62HoAdUb63amq3D7ENnBLDhHT2xY8eFkLJYVKK4x"); } pub mod vote_withdraw_authority_may_change_authorized_voter { - solana_program::declare_id!("AVZS3ZsN4gi6Rkx2QUibYuSJG3S6QHib7xCYhG6vGJxU"); + solana_pubkey::declare_id!("AVZS3ZsN4gi6Rkx2QUibYuSJG3S6QHib7xCYhG6vGJxU"); } pub mod spl_associated_token_account_v1_0_4 { - solana_program::declare_id!("FaTa4SpiaSNH44PGC4z8bnGVTkSRYaWvrBs3KTu8XQQq"); + solana_pubkey::declare_id!("FaTa4SpiaSNH44PGC4z8bnGVTkSRYaWvrBs3KTu8XQQq"); } pub mod reject_vote_account_close_unless_zero_credit_epoch { - solana_program::declare_id!("ALBk3EWdeAg2WAGf6GPDUf1nynyNqCdEVmgouG7rpuCj"); + solana_pubkey::declare_id!("ALBk3EWdeAg2WAGf6GPDUf1nynyNqCdEVmgouG7rpuCj"); } pub mod add_get_processed_sibling_instruction_syscall { - solana_program::declare_id!("CFK1hRCNy8JJuAAY8Pb2GjLFNdCThS2qwZNe3izzBMgn"); + solana_pubkey::declare_id!("CFK1hRCNy8JJuAAY8Pb2GjLFNdCThS2qwZNe3izzBMgn"); } pub mod bank_transaction_count_fix { - solana_program::declare_id!("Vo5siZ442SaZBKPXNocthiXysNviW4UYPwRFggmbgAp"); + solana_pubkey::declare_id!("Vo5siZ442SaZBKPXNocthiXysNviW4UYPwRFggmbgAp"); } pub mod disable_bpf_deprecated_load_instructions { - solana_program::declare_id!("3XgNukcZWf9o3HdA3fpJbm94XFc4qpvTXc8h1wxYwiPi"); + solana_pubkey::declare_id!("3XgNukcZWf9o3HdA3fpJbm94XFc4qpvTXc8h1wxYwiPi"); } pub mod disable_bpf_unresolved_symbols_at_runtime { - solana_program::declare_id!("4yuaYAj2jGMGTh1sSmi4G2eFscsDq8qjugJXZoBN6YEa"); + solana_pubkey::declare_id!("4yuaYAj2jGMGTh1sSmi4G2eFscsDq8qjugJXZoBN6YEa"); } pub mod record_instruction_in_transaction_context_push { - solana_program::declare_id!("3aJdcZqxoLpSBxgeYGjPwaYS1zzcByxUDqJkbzWAH1Zb"); + solana_pubkey::declare_id!("3aJdcZqxoLpSBxgeYGjPwaYS1zzcByxUDqJkbzWAH1Zb"); } pub mod syscall_saturated_math { - solana_program::declare_id!("HyrbKftCdJ5CrUfEti6x26Cj7rZLNe32weugk7tLcWb8"); + solana_pubkey::declare_id!("HyrbKftCdJ5CrUfEti6x26Cj7rZLNe32weugk7tLcWb8"); } pub mod check_physical_overlapping { - solana_program::declare_id!("nWBqjr3gpETbiaVj3CBJ3HFC5TMdnJDGt21hnvSTvVZ"); + solana_pubkey::declare_id!("nWBqjr3gpETbiaVj3CBJ3HFC5TMdnJDGt21hnvSTvVZ"); } pub mod limit_secp256k1_recovery_id { - solana_program::declare_id!("7g9EUwj4j7CS21Yx1wvgWLjSZeh5aPq8x9kpoPwXM8n8"); + solana_pubkey::declare_id!("7g9EUwj4j7CS21Yx1wvgWLjSZeh5aPq8x9kpoPwXM8n8"); } pub mod disable_deprecated_loader { - solana_program::declare_id!("GTUMCZ8LTNxVfxdrw7ZsDFTxXb7TutYkzJnFwinpE6dg"); + solana_pubkey::declare_id!("GTUMCZ8LTNxVfxdrw7ZsDFTxXb7TutYkzJnFwinpE6dg"); } pub mod check_slice_translation_size { - solana_program::declare_id!("GmC19j9qLn2RFk5NduX6QXaDhVpGncVVBzyM8e9WMz2F"); + solana_pubkey::declare_id!("GmC19j9qLn2RFk5NduX6QXaDhVpGncVVBzyM8e9WMz2F"); } pub mod stake_split_uses_rent_sysvar { - solana_program::declare_id!("FQnc7U4koHqWgRvFaBJjZnV8VPg6L6wWK33yJeDp4yvV"); + solana_pubkey::declare_id!("FQnc7U4koHqWgRvFaBJjZnV8VPg6L6wWK33yJeDp4yvV"); } pub mod add_get_minimum_delegation_instruction_to_stake_program { - solana_program::declare_id!("St8k9dVXP97xT6faW24YmRSYConLbhsMJA4TJTBLmMT"); + solana_pubkey::declare_id!("St8k9dVXP97xT6faW24YmRSYConLbhsMJA4TJTBLmMT"); } pub mod error_on_syscall_bpf_function_hash_collisions { - solana_program::declare_id!("8199Q2gMD2kwgfopK5qqVWuDbegLgpuFUFHCcUJQDN8b"); + solana_pubkey::declare_id!("8199Q2gMD2kwgfopK5qqVWuDbegLgpuFUFHCcUJQDN8b"); } pub mod reject_callx_r10 { - solana_program::declare_id!("3NKRSwpySNwD3TvP5pHnRmkAQRsdkXWRr1WaQh8p4PWX"); + solana_pubkey::declare_id!("3NKRSwpySNwD3TvP5pHnRmkAQRsdkXWRr1WaQh8p4PWX"); } pub mod drop_redundant_turbine_path { - solana_program::declare_id!("4Di3y24QFLt5QEUPZtbnjyfQKfm6ZMTfa6Dw1psfoMKU"); + solana_pubkey::declare_id!("4Di3y24QFLt5QEUPZtbnjyfQKfm6ZMTfa6Dw1psfoMKU"); } pub mod executables_incur_cpi_data_cost { - solana_program::declare_id!("7GUcYgq4tVtaqNCKT3dho9r4665Qp5TxCZ27Qgjx3829"); + solana_pubkey::declare_id!("7GUcYgq4tVtaqNCKT3dho9r4665Qp5TxCZ27Qgjx3829"); } pub mod fix_recent_blockhashes { - solana_program::declare_id!("6iyggb5MTcsvdcugX7bEKbHV8c6jdLbpHwkncrgLMhfo"); + solana_pubkey::declare_id!("6iyggb5MTcsvdcugX7bEKbHV8c6jdLbpHwkncrgLMhfo"); } pub mod update_rewards_from_cached_accounts { - solana_program::declare_id!("28s7i3htzhahXQKqmS2ExzbEoUypg9krwvtK2M9UWXh9"); + solana_pubkey::declare_id!("28s7i3htzhahXQKqmS2ExzbEoUypg9krwvtK2M9UWXh9"); } pub mod enable_partitioned_epoch_reward { - solana_program::declare_id!("9bn2vTJUsUcnpiZWbu2woSKtTGW3ErZC9ERv88SDqQjK"); + solana_pubkey::declare_id!("9bn2vTJUsUcnpiZWbu2woSKtTGW3ErZC9ERv88SDqQjK"); } pub mod partitioned_epoch_rewards_superfeature { - solana_program::declare_id!("PERzQrt5gBD1XEe2c9XdFWqwgHY3mr7cYWbm5V772V8"); + solana_pubkey::declare_id!("PERzQrt5gBD1XEe2c9XdFWqwgHY3mr7cYWbm5V772V8"); } pub mod spl_token_v3_4_0 { - solana_program::declare_id!("Ftok4njE8b7tDffYkC5bAbCaQv5sL6jispYrprzatUwN"); + solana_pubkey::declare_id!("Ftok4njE8b7tDffYkC5bAbCaQv5sL6jispYrprzatUwN"); } pub mod spl_associated_token_account_v1_1_0 { - solana_program::declare_id!("FaTa17gVKoqbh38HcfiQonPsAaQViyDCCSg71AubYZw8"); + solana_pubkey::declare_id!("FaTa17gVKoqbh38HcfiQonPsAaQViyDCCSg71AubYZw8"); } pub mod default_units_per_instruction { - solana_program::declare_id!("J2QdYx8crLbTVK8nur1jeLsmc3krDbfjoxoea2V1Uy5Q"); + solana_pubkey::declare_id!("J2QdYx8crLbTVK8nur1jeLsmc3krDbfjoxoea2V1Uy5Q"); } pub mod stake_allow_zero_undelegated_amount { - solana_program::declare_id!("sTKz343FM8mqtyGvYWvbLpTThw3ixRM4Xk8QvZ985mw"); + solana_pubkey::declare_id!("sTKz343FM8mqtyGvYWvbLpTThw3ixRM4Xk8QvZ985mw"); } pub mod require_static_program_ids_in_transaction { - solana_program::declare_id!("8FdwgyHFEjhAdjWfV2vfqk7wA1g9X3fQpKH7SBpEv3kC"); + solana_pubkey::declare_id!("8FdwgyHFEjhAdjWfV2vfqk7wA1g9X3fQpKH7SBpEv3kC"); } pub mod stake_raise_minimum_delegation_to_1_sol { // This is a feature-proposal *feature id*. The feature keypair address is `GQXzC7YiSNkje6FFUk6sc2p53XRvKoaZ9VMktYzUMnpL`. - solana_program::declare_id!("9onWzzvCzNC2jfhxxeqRgs5q7nFAAKpCUvkj6T6GJK9i"); + solana_pubkey::declare_id!("9onWzzvCzNC2jfhxxeqRgs5q7nFAAKpCUvkj6T6GJK9i"); } pub mod stake_minimum_delegation_for_rewards { - solana_program::declare_id!("G6ANXD6ptCSyNd9znZm7j4dEczAJCfx7Cy43oBx3rKHJ"); + solana_pubkey::declare_id!("G6ANXD6ptCSyNd9znZm7j4dEczAJCfx7Cy43oBx3rKHJ"); } pub mod add_set_compute_unit_price_ix { - solana_program::declare_id!("98std1NSHqXi9WYvFShfVepRdCoq1qvsp8fsR2XZtG8g"); + solana_pubkey::declare_id!("98std1NSHqXi9WYvFShfVepRdCoq1qvsp8fsR2XZtG8g"); } pub mod disable_deploy_of_alloc_free_syscall { - solana_program::declare_id!("79HWsX9rpnnJBPcdNURVqygpMAfxdrAirzAGAVmf92im"); + solana_pubkey::declare_id!("79HWsX9rpnnJBPcdNURVqygpMAfxdrAirzAGAVmf92im"); } pub mod include_account_index_in_rent_error { - solana_program::declare_id!("2R72wpcQ7qV7aTJWUumdn8u5wmmTyXbK7qzEy7YSAgyY"); + solana_pubkey::declare_id!("2R72wpcQ7qV7aTJWUumdn8u5wmmTyXbK7qzEy7YSAgyY"); } pub mod add_shred_type_to_shred_seed { - solana_program::declare_id!("Ds87KVeqhbv7Jw8W6avsS1mqz3Mw5J3pRTpPoDQ2QdiJ"); + solana_pubkey::declare_id!("Ds87KVeqhbv7Jw8W6avsS1mqz3Mw5J3pRTpPoDQ2QdiJ"); } pub mod warp_timestamp_with_a_vengeance { - solana_program::declare_id!("3BX6SBeEBibHaVQXywdkcgyUk6evfYZkHdztXiDtEpFS"); + solana_pubkey::declare_id!("3BX6SBeEBibHaVQXywdkcgyUk6evfYZkHdztXiDtEpFS"); } pub mod separate_nonce_from_blockhash { - solana_program::declare_id!("Gea3ZkK2N4pHuVZVxWcnAtS6UEDdyumdYt4pFcKjA3ar"); + solana_pubkey::declare_id!("Gea3ZkK2N4pHuVZVxWcnAtS6UEDdyumdYt4pFcKjA3ar"); } pub mod enable_durable_nonce { - solana_program::declare_id!("4EJQtF2pkRyawwcTVfQutzq4Sa5hRhibF6QAK1QXhtEX"); + solana_pubkey::declare_id!("4EJQtF2pkRyawwcTVfQutzq4Sa5hRhibF6QAK1QXhtEX"); } pub mod vote_state_update_credit_per_dequeue { - solana_program::declare_id!("CveezY6FDLVBToHDcvJRmtMouqzsmj4UXYh5ths5G5Uv"); + solana_pubkey::declare_id!("CveezY6FDLVBToHDcvJRmtMouqzsmj4UXYh5ths5G5Uv"); } pub mod quick_bail_on_panic { - solana_program::declare_id!("DpJREPyuMZ5nDfU6H3WTqSqUFSXAfw8u7xqmWtEwJDcP"); + solana_pubkey::declare_id!("DpJREPyuMZ5nDfU6H3WTqSqUFSXAfw8u7xqmWtEwJDcP"); } pub mod nonce_must_be_authorized { - solana_program::declare_id!("HxrEu1gXuH7iD3Puua1ohd5n4iUKJyFNtNxk9DVJkvgr"); + solana_pubkey::declare_id!("HxrEu1gXuH7iD3Puua1ohd5n4iUKJyFNtNxk9DVJkvgr"); } pub mod nonce_must_be_advanceable { - solana_program::declare_id!("3u3Er5Vc2jVcwz4xr2GJeSAXT3fAj6ADHZ4BJMZiScFd"); + solana_pubkey::declare_id!("3u3Er5Vc2jVcwz4xr2GJeSAXT3fAj6ADHZ4BJMZiScFd"); } pub mod vote_authorize_with_seed { - solana_program::declare_id!("6tRxEYKuy2L5nnv5bgn7iT28MxUbYxp5h7F3Ncf1exrT"); + solana_pubkey::declare_id!("6tRxEYKuy2L5nnv5bgn7iT28MxUbYxp5h7F3Ncf1exrT"); } pub mod preserve_rent_epoch_for_rent_exempt_accounts { - solana_program::declare_id!("HH3MUYReL2BvqqA3oEcAa7txju5GY6G4nxJ51zvsEjEZ"); + solana_pubkey::declare_id!("HH3MUYReL2BvqqA3oEcAa7txju5GY6G4nxJ51zvsEjEZ"); } pub mod enable_bpf_loader_extend_program_ix { - solana_program::declare_id!("8Zs9W7D9MpSEtUWSQdGniZk2cNmV22y6FLJwCx53asme"); + solana_pubkey::declare_id!("8Zs9W7D9MpSEtUWSQdGniZk2cNmV22y6FLJwCx53asme"); } pub mod enable_early_verification_of_account_modifications { - solana_program::declare_id!("7Vced912WrRnfjaiKRiNBcbuFw7RrnLv3E3z95Y4GTNc"); + solana_pubkey::declare_id!("7Vced912WrRnfjaiKRiNBcbuFw7RrnLv3E3z95Y4GTNc"); } pub mod skip_rent_rewrites { - solana_program::declare_id!("CGB2jM8pwZkeeiXQ66kBMyBR6Np61mggL7XUsmLjVcrw"); + solana_pubkey::declare_id!("CGB2jM8pwZkeeiXQ66kBMyBR6Np61mggL7XUsmLjVcrw"); } pub mod prevent_crediting_accounts_that_end_rent_paying { - solana_program::declare_id!("812kqX67odAp5NFwM8D2N24cku7WTm9CHUTFUXaDkWPn"); + solana_pubkey::declare_id!("812kqX67odAp5NFwM8D2N24cku7WTm9CHUTFUXaDkWPn"); } pub mod cap_bpf_program_instruction_accounts { - solana_program::declare_id!("9k5ijzTbYPtjzu8wj2ErH9v45xecHzQ1x4PMYMMxFgdM"); + solana_pubkey::declare_id!("9k5ijzTbYPtjzu8wj2ErH9v45xecHzQ1x4PMYMMxFgdM"); } pub mod loosen_cpi_size_restriction { - solana_program::declare_id!("GDH5TVdbTPUpRnXaRyQqiKUa7uZAbZ28Q2N9bhbKoMLm"); + solana_pubkey::declare_id!("GDH5TVdbTPUpRnXaRyQqiKUa7uZAbZ28Q2N9bhbKoMLm"); } pub mod use_default_units_in_fee_calculation { - solana_program::declare_id!("8sKQrMQoUHtQSUP83SPG4ta2JDjSAiWs7t5aJ9uEd6To"); + solana_pubkey::declare_id!("8sKQrMQoUHtQSUP83SPG4ta2JDjSAiWs7t5aJ9uEd6To"); } pub mod compact_vote_state_updates { - solana_program::declare_id!("86HpNqzutEZwLcPxS6EHDcMNYWk6ikhteg9un7Y2PBKE"); + solana_pubkey::declare_id!("86HpNqzutEZwLcPxS6EHDcMNYWk6ikhteg9un7Y2PBKE"); } pub mod incremental_snapshot_only_incremental_hash_calculation { - solana_program::declare_id!("25vqsfjk7Nv1prsQJmA4Xu1bN61s8LXCBGUPp8Rfy1UF"); + solana_pubkey::declare_id!("25vqsfjk7Nv1prsQJmA4Xu1bN61s8LXCBGUPp8Rfy1UF"); } pub mod disable_cpi_setting_executable_and_rent_epoch { - solana_program::declare_id!("B9cdB55u4jQsDNsdTK525yE9dmSc5Ga7YBaBrDFvEhM9"); + solana_pubkey::declare_id!("B9cdB55u4jQsDNsdTK525yE9dmSc5Ga7YBaBrDFvEhM9"); } pub mod on_load_preserve_rent_epoch_for_rent_exempt_accounts { - solana_program::declare_id!("CpkdQmspsaZZ8FVAouQTtTWZkc8eeQ7V3uj7dWz543rZ"); + solana_pubkey::declare_id!("CpkdQmspsaZZ8FVAouQTtTWZkc8eeQ7V3uj7dWz543rZ"); } pub mod account_hash_ignore_slot { - solana_program::declare_id!("SVn36yVApPLYsa8koK3qUcy14zXDnqkNYWyUh1f4oK1"); + solana_pubkey::declare_id!("SVn36yVApPLYsa8koK3qUcy14zXDnqkNYWyUh1f4oK1"); } pub mod set_exempt_rent_epoch_max { - solana_program::declare_id!("5wAGiy15X1Jb2hkHnPDCM8oB9V42VNA9ftNVFK84dEgv"); + solana_pubkey::declare_id!("5wAGiy15X1Jb2hkHnPDCM8oB9V42VNA9ftNVFK84dEgv"); } pub mod relax_authority_signer_check_for_lookup_table_creation { - solana_program::declare_id!("FKAcEvNgSY79RpqsPNUV5gDyumopH4cEHqUxyfm8b8Ap"); + solana_pubkey::declare_id!("FKAcEvNgSY79RpqsPNUV5gDyumopH4cEHqUxyfm8b8Ap"); } pub mod stop_sibling_instruction_search_at_parent { - solana_program::declare_id!("EYVpEP7uzH1CoXzbD6PubGhYmnxRXPeq3PPsm1ba3gpo"); + solana_pubkey::declare_id!("EYVpEP7uzH1CoXzbD6PubGhYmnxRXPeq3PPsm1ba3gpo"); } pub mod vote_state_update_root_fix { - solana_program::declare_id!("G74BkWBzmsByZ1kxHy44H3wjwp5hp7JbrGRuDpco22tY"); + solana_pubkey::declare_id!("G74BkWBzmsByZ1kxHy44H3wjwp5hp7JbrGRuDpco22tY"); } pub mod cap_accounts_data_allocations_per_transaction { - solana_program::declare_id!("9gxu85LYRAcZL38We8MYJ4A9AwgBBPtVBAqebMcT1241"); + solana_pubkey::declare_id!("9gxu85LYRAcZL38We8MYJ4A9AwgBBPtVBAqebMcT1241"); } pub mod epoch_accounts_hash { - solana_program::declare_id!("5GpmAKxaGsWWbPp4bNXFLJxZVvG92ctxf7jQnzTQjF3n"); + solana_pubkey::declare_id!("5GpmAKxaGsWWbPp4bNXFLJxZVvG92ctxf7jQnzTQjF3n"); } pub mod remove_deprecated_request_unit_ix { - solana_program::declare_id!("EfhYd3SafzGT472tYQDUc4dPd2xdEfKs5fwkowUgVt4W"); + solana_pubkey::declare_id!("EfhYd3SafzGT472tYQDUc4dPd2xdEfKs5fwkowUgVt4W"); } pub mod disable_rehash_for_rent_epoch { - solana_program::declare_id!("DTVTkmw3JSofd8CJVJte8PXEbxNQ2yZijvVr3pe2APPj"); + solana_pubkey::declare_id!("DTVTkmw3JSofd8CJVJte8PXEbxNQ2yZijvVr3pe2APPj"); } pub mod increase_tx_account_lock_limit { - solana_program::declare_id!("9LZdXeKGeBV6hRLdxS1rHbHoEUsKqesCC2ZAPTPKJAbK"); + solana_pubkey::declare_id!("9LZdXeKGeBV6hRLdxS1rHbHoEUsKqesCC2ZAPTPKJAbK"); } pub mod limit_max_instruction_trace_length { - solana_program::declare_id!("GQALDaC48fEhZGWRj9iL5Q889emJKcj3aCvHF7VCbbF4"); + solana_pubkey::declare_id!("GQALDaC48fEhZGWRj9iL5Q889emJKcj3aCvHF7VCbbF4"); } pub mod check_syscall_outputs_do_not_overlap { - solana_program::declare_id!("3uRVPBpyEJRo1emLCrq38eLRFGcu6uKSpUXqGvU8T7SZ"); + solana_pubkey::declare_id!("3uRVPBpyEJRo1emLCrq38eLRFGcu6uKSpUXqGvU8T7SZ"); } pub mod enable_bpf_loader_set_authority_checked_ix { - solana_program::declare_id!("5x3825XS7M2A3Ekbn5VGGkvFoAg5qrRWkTrY4bARP1GL"); + solana_pubkey::declare_id!("5x3825XS7M2A3Ekbn5VGGkvFoAg5qrRWkTrY4bARP1GL"); } pub mod enable_alt_bn128_syscall { - solana_program::declare_id!("A16q37opZdQMCbe5qJ6xpBB9usykfv8jZaMkxvZQi4GJ"); + solana_pubkey::declare_id!("A16q37opZdQMCbe5qJ6xpBB9usykfv8jZaMkxvZQi4GJ"); } pub mod simplify_alt_bn128_syscall_error_codes { - solana_program::declare_id!("JDn5q3GBeqzvUa7z67BbmVHVdE3EbUAjvFep3weR3jxX"); + solana_pubkey::declare_id!("JDn5q3GBeqzvUa7z67BbmVHVdE3EbUAjvFep3weR3jxX"); } pub mod enable_alt_bn128_compression_syscall { - solana_program::declare_id!("EJJewYSddEEtSZHiqugnvhQHiWyZKjkFDQASd7oKSagn"); + solana_pubkey::declare_id!("EJJewYSddEEtSZHiqugnvhQHiWyZKjkFDQASd7oKSagn"); } pub mod enable_program_redeployment_cooldown { - solana_program::declare_id!("J4HFT8usBxpcF63y46t1upYobJgChmKyZPm5uTBRg25Z"); + solana_pubkey::declare_id!("J4HFT8usBxpcF63y46t1upYobJgChmKyZPm5uTBRg25Z"); } pub mod commission_updates_only_allowed_in_first_half_of_epoch { - solana_program::declare_id!("noRuG2kzACwgaY7TVmLRnUNPLKNVQE1fb7X55YWBehp"); + solana_pubkey::declare_id!("noRuG2kzACwgaY7TVmLRnUNPLKNVQE1fb7X55YWBehp"); } pub mod enable_turbine_fanout_experiments { - solana_program::declare_id!("D31EFnLgdiysi84Woo3of4JMu7VmasUS3Z7j9HYXCeLY"); + solana_pubkey::declare_id!("D31EFnLgdiysi84Woo3of4JMu7VmasUS3Z7j9HYXCeLY"); } pub mod disable_turbine_fanout_experiments { - solana_program::declare_id!("Gz1aLrbeQ4Q6PTSafCZcGWZXz91yVRi7ASFzFEr1U4sa"); + solana_pubkey::declare_id!("Gz1aLrbeQ4Q6PTSafCZcGWZXz91yVRi7ASFzFEr1U4sa"); } pub mod move_serialized_len_ptr_in_cpi { - solana_program::declare_id!("74CoWuBmt3rUVUrCb2JiSTvh6nXyBWUsK4SaMj3CtE3T"); + solana_pubkey::declare_id!("74CoWuBmt3rUVUrCb2JiSTvh6nXyBWUsK4SaMj3CtE3T"); } pub mod update_hashes_per_tick { - solana_program::declare_id!("3uFHb9oKdGfgZGJK9EHaAXN4USvnQtAFC13Fh5gGFS5B"); + solana_pubkey::declare_id!("3uFHb9oKdGfgZGJK9EHaAXN4USvnQtAFC13Fh5gGFS5B"); } pub mod enable_big_mod_exp_syscall { - solana_program::declare_id!("EBq48m8irRKuE7ZnMTLvLg2UuGSqhe8s8oMqnmja1fJw"); + solana_pubkey::declare_id!("EBq48m8irRKuE7ZnMTLvLg2UuGSqhe8s8oMqnmja1fJw"); } pub mod disable_builtin_loader_ownership_chains { - solana_program::declare_id!("4UDcAfQ6EcA6bdcadkeHpkarkhZGJ7Bpq7wTAiRMjkoi"); + solana_pubkey::declare_id!("4UDcAfQ6EcA6bdcadkeHpkarkhZGJ7Bpq7wTAiRMjkoi"); } pub mod cap_transaction_accounts_data_size { - solana_program::declare_id!("DdLwVYuvDz26JohmgSbA7mjpJFgX5zP2dkp8qsF2C33V"); + solana_pubkey::declare_id!("DdLwVYuvDz26JohmgSbA7mjpJFgX5zP2dkp8qsF2C33V"); } pub mod remove_congestion_multiplier_from_fee_calculation { - solana_program::declare_id!("A8xyMHZovGXFkorFqEmVH2PKGLiBip5JD7jt4zsUWo4H"); + solana_pubkey::declare_id!("A8xyMHZovGXFkorFqEmVH2PKGLiBip5JD7jt4zsUWo4H"); } pub mod enable_request_heap_frame_ix { - solana_program::declare_id!("Hr1nUA9b7NJ6eChS26o7Vi8gYYDDwWD3YeBfzJkTbU86"); + solana_pubkey::declare_id!("Hr1nUA9b7NJ6eChS26o7Vi8gYYDDwWD3YeBfzJkTbU86"); } pub mod prevent_rent_paying_rent_recipients { - solana_program::declare_id!("Fab5oP3DmsLYCiQZXdjyqT3ukFFPrsmqhXU4WU1AWVVF"); + solana_pubkey::declare_id!("Fab5oP3DmsLYCiQZXdjyqT3ukFFPrsmqhXU4WU1AWVVF"); } pub mod delay_visibility_of_program_deployment { - solana_program::declare_id!("GmuBvtFb2aHfSfMXpuFeWZGHyDeCLPS79s48fmCWCfM5"); + solana_pubkey::declare_id!("GmuBvtFb2aHfSfMXpuFeWZGHyDeCLPS79s48fmCWCfM5"); } pub mod apply_cost_tracker_during_replay { - solana_program::declare_id!("2ry7ygxiYURULZCrypHhveanvP5tzZ4toRwVp89oCNSj"); + solana_pubkey::declare_id!("2ry7ygxiYURULZCrypHhveanvP5tzZ4toRwVp89oCNSj"); } pub mod bpf_account_data_direct_mapping { - solana_program::declare_id!("EenyoWx9UMXYKpR8mW5Jmfmy2fRjzUtM7NduYMY8bx33"); + solana_pubkey::declare_id!("EenyoWx9UMXYKpR8mW5Jmfmy2fRjzUtM7NduYMY8bx33"); } pub mod add_set_tx_loaded_accounts_data_size_instruction { - solana_program::declare_id!("G6vbf1UBok8MWb8m25ex86aoQHeKTzDKzuZADHkShqm6"); + solana_pubkey::declare_id!("G6vbf1UBok8MWb8m25ex86aoQHeKTzDKzuZADHkShqm6"); } pub mod switch_to_new_elf_parser { - solana_program::declare_id!("Cdkc8PPTeTNUPoZEfCY5AyetUrEdkZtNPMgz58nqyaHD"); + solana_pubkey::declare_id!("Cdkc8PPTeTNUPoZEfCY5AyetUrEdkZtNPMgz58nqyaHD"); } pub mod round_up_heap_size { - solana_program::declare_id!("CE2et8pqgyQMP2mQRg3CgvX8nJBKUArMu3wfiQiQKY1y"); + solana_pubkey::declare_id!("CE2et8pqgyQMP2mQRg3CgvX8nJBKUArMu3wfiQiQKY1y"); } pub mod remove_bpf_loader_incorrect_program_id { - solana_program::declare_id!("2HmTkCj9tXuPE4ueHzdD7jPeMf9JGCoZh5AsyoATiWEe"); + solana_pubkey::declare_id!("2HmTkCj9tXuPE4ueHzdD7jPeMf9JGCoZh5AsyoATiWEe"); } pub mod include_loaded_accounts_data_size_in_fee_calculation { - solana_program::declare_id!("EaQpmC6GtRssaZ3PCUM5YksGqUdMLeZ46BQXYtHYakDS"); + solana_pubkey::declare_id!("EaQpmC6GtRssaZ3PCUM5YksGqUdMLeZ46BQXYtHYakDS"); } pub mod native_programs_consume_cu { - solana_program::declare_id!("8pgXCMNXC8qyEFypuwpXyRxLXZdpM4Qo72gJ6k87A6wL"); + solana_pubkey::declare_id!("8pgXCMNXC8qyEFypuwpXyRxLXZdpM4Qo72gJ6k87A6wL"); } pub mod simplify_writable_program_account_check { - solana_program::declare_id!("5ZCcFAzJ1zsFKe1KSZa9K92jhx7gkcKj97ci2DBo1vwj"); + solana_pubkey::declare_id!("5ZCcFAzJ1zsFKe1KSZa9K92jhx7gkcKj97ci2DBo1vwj"); } pub mod stop_truncating_strings_in_syscalls { - solana_program::declare_id!("16FMCmgLzCNNz6eTwGanbyN2ZxvTBSLuQ6DZhgeMshg"); + solana_pubkey::declare_id!("16FMCmgLzCNNz6eTwGanbyN2ZxvTBSLuQ6DZhgeMshg"); } pub mod clean_up_delegation_errors { - solana_program::declare_id!("Bj2jmUsM2iRhfdLLDSTkhM5UQRQvQHm57HSmPibPtEyu"); + solana_pubkey::declare_id!("Bj2jmUsM2iRhfdLLDSTkhM5UQRQvQHm57HSmPibPtEyu"); } pub mod vote_state_add_vote_latency { - solana_program::declare_id!("7axKe5BTYBDD87ftzWbk5DfzWMGyRvqmWTduuo22Yaqy"); + solana_pubkey::declare_id!("7axKe5BTYBDD87ftzWbk5DfzWMGyRvqmWTduuo22Yaqy"); } pub mod checked_arithmetic_in_fee_validation { - solana_program::declare_id!("5Pecy6ie6XGm22pc9d4P9W5c31BugcFBuy6hsP2zkETv"); + solana_pubkey::declare_id!("5Pecy6ie6XGm22pc9d4P9W5c31BugcFBuy6hsP2zkETv"); } pub mod last_restart_slot_sysvar { - solana_program::declare_id!("HooKD5NC9QNxk25QuzCssB8ecrEzGt6eXEPBUxWp1LaR"); + solana_pubkey::declare_id!("HooKD5NC9QNxk25QuzCssB8ecrEzGt6eXEPBUxWp1LaR"); } pub mod reduce_stake_warmup_cooldown { - solana_program::declare_id!("GwtDQBghCTBgmX2cpEGNPxTEBUTQRaDMGTr5qychdGMj"); + solana_pubkey::declare_id!("GwtDQBghCTBgmX2cpEGNPxTEBUTQRaDMGTr5qychdGMj"); } mod revise_turbine_epoch_stakes { - solana_program::declare_id!("BTWmtJC8U5ZLMbBUUA1k6As62sYjPEjAiNAT55xYGdJU"); + solana_pubkey::declare_id!("BTWmtJC8U5ZLMbBUUA1k6As62sYjPEjAiNAT55xYGdJU"); } pub mod enable_poseidon_syscall { - solana_program::declare_id!("FL9RsQA6TVUoh5xJQ9d936RHSebA1NLQqe3Zv9sXZRpr"); + solana_pubkey::declare_id!("FL9RsQA6TVUoh5xJQ9d936RHSebA1NLQqe3Zv9sXZRpr"); } pub mod timely_vote_credits { - solana_program::declare_id!("tvcF6b1TRz353zKuhBjinZkKzjmihXmBAHJdjNYw1sQ"); + solana_pubkey::declare_id!("tvcF6b1TRz353zKuhBjinZkKzjmihXmBAHJdjNYw1sQ"); } pub mod remaining_compute_units_syscall_enabled { - solana_program::declare_id!("5TuppMutoyzhUSfuYdhgzD47F92GL1g89KpCZQKqedxP"); + solana_pubkey::declare_id!("5TuppMutoyzhUSfuYdhgzD47F92GL1g89KpCZQKqedxP"); } pub mod enable_program_runtime_v2_and_loader_v4 { - solana_program::declare_id!("8oBxsYqnCvUTGzgEpxPcnVf7MLbWWPYddE33PftFeBBd"); + solana_pubkey::declare_id!("8oBxsYqnCvUTGzgEpxPcnVf7MLbWWPYddE33PftFeBBd"); } pub mod require_rent_exempt_split_destination { - solana_program::declare_id!("D2aip4BBr8NPWtU9vLrwrBvbuaQ8w1zV38zFLxx4pfBV"); + solana_pubkey::declare_id!("D2aip4BBr8NPWtU9vLrwrBvbuaQ8w1zV38zFLxx4pfBV"); } pub mod better_error_codes_for_tx_lamport_check { - solana_program::declare_id!("Ffswd3egL3tccB6Rv3XY6oqfdzn913vUcjCSnpvCKpfx"); + solana_pubkey::declare_id!("Ffswd3egL3tccB6Rv3XY6oqfdzn913vUcjCSnpvCKpfx"); } pub mod update_hashes_per_tick2 { - solana_program::declare_id!("EWme9uFqfy1ikK1jhJs8fM5hxWnK336QJpbscNtizkTU"); + solana_pubkey::declare_id!("EWme9uFqfy1ikK1jhJs8fM5hxWnK336QJpbscNtizkTU"); } pub mod update_hashes_per_tick3 { - solana_program::declare_id!("8C8MCtsab5SsfammbzvYz65HHauuUYdbY2DZ4sznH6h5"); + solana_pubkey::declare_id!("8C8MCtsab5SsfammbzvYz65HHauuUYdbY2DZ4sznH6h5"); } pub mod update_hashes_per_tick4 { - solana_program::declare_id!("8We4E7DPwF2WfAN8tRTtWQNhi98B99Qpuj7JoZ3Aikgg"); + solana_pubkey::declare_id!("8We4E7DPwF2WfAN8tRTtWQNhi98B99Qpuj7JoZ3Aikgg"); } pub mod update_hashes_per_tick5 { - solana_program::declare_id!("BsKLKAn1WM4HVhPRDsjosmqSg2J8Tq5xP2s2daDS6Ni4"); + solana_pubkey::declare_id!("BsKLKAn1WM4HVhPRDsjosmqSg2J8Tq5xP2s2daDS6Ni4"); } pub mod update_hashes_per_tick6 { - solana_program::declare_id!("FKu1qYwLQSiehz644H6Si65U5ZQ2cp9GxsyFUfYcuADv"); + solana_pubkey::declare_id!("FKu1qYwLQSiehz644H6Si65U5ZQ2cp9GxsyFUfYcuADv"); } pub mod validate_fee_collector_account { - solana_program::declare_id!("prpFrMtgNmzaNzkPJg9o753fVvbHKqNrNTm76foJ2wm"); + solana_pubkey::declare_id!("prpFrMtgNmzaNzkPJg9o753fVvbHKqNrNTm76foJ2wm"); } pub mod disable_rent_fees_collection { - solana_program::declare_id!("CJzY83ggJHqPGDq8VisV3U91jDJLuEaALZooBrXtnnLU"); + solana_pubkey::declare_id!("CJzY83ggJHqPGDq8VisV3U91jDJLuEaALZooBrXtnnLU"); } pub mod enable_zk_transfer_with_fee { - solana_program::declare_id!("zkNLP7EQALfC1TYeB3biDU7akDckj8iPkvh9y2Mt2K3"); + solana_pubkey::declare_id!("zkNLP7EQALfC1TYeB3biDU7akDckj8iPkvh9y2Mt2K3"); } pub mod drop_legacy_shreds { - solana_program::declare_id!("GV49KKQdBNaiv2pgqhS2Dy3GWYJGXMTVYbYkdk91orRy"); + solana_pubkey::declare_id!("GV49KKQdBNaiv2pgqhS2Dy3GWYJGXMTVYbYkdk91orRy"); } pub mod allow_commission_decrease_at_any_time { - solana_program::declare_id!("decoMktMcnmiq6t3u7g5BfgcQu91nKZr6RvMYf9z1Jb"); + solana_pubkey::declare_id!("decoMktMcnmiq6t3u7g5BfgcQu91nKZr6RvMYf9z1Jb"); } pub mod add_new_reserved_account_keys { - solana_program::declare_id!("8U4skmMVnF6k2kMvrWbQuRUT3qQSiTYpSjqmhmgfthZu"); + solana_pubkey::declare_id!("8U4skmMVnF6k2kMvrWbQuRUT3qQSiTYpSjqmhmgfthZu"); } pub mod consume_blockstore_duplicate_proofs { - solana_program::declare_id!("6YsBCejwK96GZCkJ6mkZ4b68oP63z2PLoQmWjC7ggTqZ"); + solana_pubkey::declare_id!("6YsBCejwK96GZCkJ6mkZ4b68oP63z2PLoQmWjC7ggTqZ"); } pub mod index_erasure_conflict_duplicate_proofs { - solana_program::declare_id!("dupPajaLy2SSn8ko42aZz4mHANDNrLe8Nw8VQgFecLa"); + solana_pubkey::declare_id!("dupPajaLy2SSn8ko42aZz4mHANDNrLe8Nw8VQgFecLa"); } pub mod merkle_conflict_duplicate_proofs { - solana_program::declare_id!("mrkPjRg79B2oK2ZLgd7S3AfEJaX9B6gAF3H9aEykRUS"); + solana_pubkey::declare_id!("mrkPjRg79B2oK2ZLgd7S3AfEJaX9B6gAF3H9aEykRUS"); } pub mod disable_bpf_loader_instructions { - solana_program::declare_id!("7WeS1vfPRgeeoXArLh7879YcB9mgE9ktjPDtajXeWfXn"); + solana_pubkey::declare_id!("7WeS1vfPRgeeoXArLh7879YcB9mgE9ktjPDtajXeWfXn"); } pub mod enable_zk_proof_from_account { - solana_program::declare_id!("zkiTNuzBKxrCLMKehzuQeKZyLtX2yvFcEKMML8nExU8"); + solana_pubkey::declare_id!("zkiTNuzBKxrCLMKehzuQeKZyLtX2yvFcEKMML8nExU8"); } pub mod cost_model_requested_write_lock_cost { - solana_program::declare_id!("wLckV1a64ngtcKPRGU4S4grVTestXjmNjxBjaKZrAcn"); + solana_pubkey::declare_id!("wLckV1a64ngtcKPRGU4S4grVTestXjmNjxBjaKZrAcn"); } pub mod enable_gossip_duplicate_proof_ingestion { - solana_program::declare_id!("FNKCMBzYUdjhHyPdsKG2LSmdzH8TCHXn3ytj8RNBS4nG"); + solana_pubkey::declare_id!("FNKCMBzYUdjhHyPdsKG2LSmdzH8TCHXn3ytj8RNBS4nG"); } pub mod chained_merkle_conflict_duplicate_proofs { - solana_program::declare_id!("chaie9S2zVfuxJKNRGkyTDokLwWxx6kD2ZLsqQHaDD8"); + solana_pubkey::declare_id!("chaie9S2zVfuxJKNRGkyTDokLwWxx6kD2ZLsqQHaDD8"); } pub mod enable_chained_merkle_shreds { - solana_program::declare_id!("7uZBkJXJ1HkuP6R3MJfZs7mLwymBcDbKdqbF51ZWLier"); + solana_pubkey::declare_id!("7uZBkJXJ1HkuP6R3MJfZs7mLwymBcDbKdqbF51ZWLier"); } pub mod remove_rounding_in_fee_calculation { - solana_program::declare_id!("BtVN7YjDzNE6Dk7kTT7YTDgMNUZTNgiSJgsdzAeTg2jF"); + solana_pubkey::declare_id!("BtVN7YjDzNE6Dk7kTT7YTDgMNUZTNgiSJgsdzAeTg2jF"); } pub mod enable_tower_sync_ix { - solana_program::declare_id!("tSynMCspg4xFiCj1v3TDb4c7crMR5tSBhLz4sF7rrNA"); + solana_pubkey::declare_id!("tSynMCspg4xFiCj1v3TDb4c7crMR5tSBhLz4sF7rrNA"); } pub mod deprecate_unused_legacy_vote_plumbing { - solana_program::declare_id!("6Uf8S75PVh91MYgPQSHnjRAPQq6an5BDv9vomrCwDqLe"); + solana_pubkey::declare_id!("6Uf8S75PVh91MYgPQSHnjRAPQq6an5BDv9vomrCwDqLe"); } pub mod reward_full_priority_fee { - solana_program::declare_id!("3opE3EzAKnUftUDURkzMgwpNgimBAypW1mNDYH4x4Zg7"); + solana_pubkey::declare_id!("3opE3EzAKnUftUDURkzMgwpNgimBAypW1mNDYH4x4Zg7"); } pub mod get_sysvar_syscall_enabled { - solana_program::declare_id!("CLCoTADvV64PSrnR6QXty6Fwrt9Xc6EdxSJE4wLRePjq"); + solana_pubkey::declare_id!("CLCoTADvV64PSrnR6QXty6Fwrt9Xc6EdxSJE4wLRePjq"); } pub mod abort_on_invalid_curve { - solana_program::declare_id!("FuS3FPfJDKSNot99ECLXtp3rueq36hMNStJkPJwWodLh"); + solana_pubkey::declare_id!("FuS3FPfJDKSNot99ECLXtp3rueq36hMNStJkPJwWodLh"); } pub mod migrate_feature_gate_program_to_core_bpf { - solana_program::declare_id!("4eohviozzEeivk1y9UbrnekbAFMDQyJz5JjA9Y6gyvky"); + solana_pubkey::declare_id!("4eohviozzEeivk1y9UbrnekbAFMDQyJz5JjA9Y6gyvky"); } pub mod vote_only_full_fec_sets { - solana_program::declare_id!("ffecLRhhakKSGhMuc6Fz2Lnfq4uT9q3iu9ZsNaPLxPc"); + solana_pubkey::declare_id!("ffecLRhhakKSGhMuc6Fz2Lnfq4uT9q3iu9ZsNaPLxPc"); } pub mod migrate_config_program_to_core_bpf { - solana_program::declare_id!("2Fr57nzzkLYXW695UdDxDeR5fhnZWSttZeZYemrnpGFV"); + solana_pubkey::declare_id!("2Fr57nzzkLYXW695UdDxDeR5fhnZWSttZeZYemrnpGFV"); } pub mod enable_get_epoch_stake_syscall { - solana_program::declare_id!("7mScTYkJXsbdrcwTQRs7oeCSXoJm4WjzBsRyf8bCU3Np"); + solana_pubkey::declare_id!("7mScTYkJXsbdrcwTQRs7oeCSXoJm4WjzBsRyf8bCU3Np"); } pub mod migrate_address_lookup_table_program_to_core_bpf { - solana_program::declare_id!("C97eKZygrkU4JxJsZdjgbUY7iQR7rKTr4NyDWo2E5pRm"); + solana_pubkey::declare_id!("C97eKZygrkU4JxJsZdjgbUY7iQR7rKTr4NyDWo2E5pRm"); } pub mod zk_elgamal_proof_program_enabled { - solana_program::declare_id!("zkhiy5oLowR7HY4zogXjCjeMXyruLqBwSWH21qcFtnv"); + solana_pubkey::declare_id!("zkhiy5oLowR7HY4zogXjCjeMXyruLqBwSWH21qcFtnv"); } pub mod verify_retransmitter_signature { - solana_program::declare_id!("BZ5g4hRbu5hLQQBdPyo2z9icGyJ8Khiyj3QS6dhWijTb"); + solana_pubkey::declare_id!("BZ5g4hRbu5hLQQBdPyo2z9icGyJ8Khiyj3QS6dhWijTb"); } pub mod move_stake_and_move_lamports_ixs { - solana_program::declare_id!("7bTK6Jis8Xpfrs8ZoUfiMDPazTcdPcTWheZFJTA5Z6X4"); + solana_pubkey::declare_id!("7bTK6Jis8Xpfrs8ZoUfiMDPazTcdPcTWheZFJTA5Z6X4"); } pub mod ed25519_precompile_verify_strict { - solana_program::declare_id!("ed9tNscbWLYBooxWA7FE2B5KHWs8A6sxfY8EzezEcoo"); + solana_pubkey::declare_id!("ed9tNscbWLYBooxWA7FE2B5KHWs8A6sxfY8EzezEcoo"); } pub mod vote_only_retransmitter_signed_fec_sets { - solana_program::declare_id!("RfEcA95xnhuwooVAhUUksEJLZBF7xKCLuqrJoqk4Zph"); + solana_pubkey::declare_id!("RfEcA95xnhuwooVAhUUksEJLZBF7xKCLuqrJoqk4Zph"); } pub mod move_precompile_verification_to_svm { - solana_program::declare_id!("9ypxGLzkMxi89eDerRKXWDXe44UY2z4hBig4mDhNq5Dp"); + solana_pubkey::declare_id!("9ypxGLzkMxi89eDerRKXWDXe44UY2z4hBig4mDhNq5Dp"); } pub mod enable_transaction_loading_failure_fees { - solana_program::declare_id!("PaymEPK2oqwT9TXAVfadjztH2H6KfLEB9Hhd5Q5frvP"); + solana_pubkey::declare_id!("PaymEPK2oqwT9TXAVfadjztH2H6KfLEB9Hhd5Q5frvP"); } pub mod enable_turbine_extended_fanout_experiments { - solana_program::declare_id!("BZn14Liea52wtBwrXUxTv6vojuTTmfc7XGEDTXrvMD7b"); + solana_pubkey::declare_id!("BZn14Liea52wtBwrXUxTv6vojuTTmfc7XGEDTXrvMD7b"); } pub mod deprecate_legacy_vote_ixs { - solana_program::declare_id!("depVvnQ2UysGrhwdiwU42tCadZL8GcBb1i2GYhMopQv"); + solana_pubkey::declare_id!("depVvnQ2UysGrhwdiwU42tCadZL8GcBb1i2GYhMopQv"); } pub mod disable_sbpf_v1_execution { - solana_program::declare_id!("TestFeature11111111111111111111111111111111"); + solana_pubkey::declare_id!("TestFeature11111111111111111111111111111111"); } pub mod reenable_sbpf_v1_execution { - solana_program::declare_id!("TestFeature21111111111111111111111111111111"); + solana_pubkey::declare_id!("TestFeature21111111111111111111111111111111"); } lazy_static! { @@ -1173,7 +1171,7 @@ impl FeatureSet { } /// Activate a feature - pub fn activate(&mut self, feature_id: &Pubkey, slot: u64) { + pub fn activate(&mut self, feature_id: &Pubkey, slot: Slot) { self.inactive.remove(feature_id); self.active.insert(*feature_id, slot); } diff --git a/sdk/native-token/Cargo.toml b/sdk/native-token/Cargo.toml new file mode 100644 index 00000000000000..cac63f3e88597f --- /dev/null +++ b/sdk/native-token/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "solana-native-token" +description = "Definitions for the native SOL token and its fractional lamports." +documentation = "https://docs.rs/solana-native-token" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/sdk/program/src/native_token.rs b/sdk/native-token/src/lib.rs similarity index 100% rename from sdk/program/src/native_token.rs rename to sdk/native-token/src/lib.rs diff --git a/sdk/package-metadata/Cargo.toml b/sdk/package-metadata/Cargo.toml new file mode 100644 index 00000000000000..8f73e50055f7d4 --- /dev/null +++ b/sdk/package-metadata/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "solana-package-metadata" +description = "Solana Package Metadata" +documentation = "https://docs.rs/solana-package-metadata" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +solana-package-metadata-macro = { workspace = true } +solana-pubkey = { workspace = true } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/sdk/package-metadata/src/lib.rs b/sdk/package-metadata/src/lib.rs new file mode 100644 index 00000000000000..28fdf08cca35ff --- /dev/null +++ b/sdk/package-metadata/src/lib.rs @@ -0,0 +1,90 @@ +/// Macro for accessing data from the `package.metadata` section of the Cargo manifest +/// +/// # Arguments +/// * `key` - A string slice of a dot-separated path to the TOML key of interest +/// +/// # Example +/// Given the following `Cargo.toml`: +/// ```ignore +/// [package] +/// name = "MyApp" +/// version = "0.1.0" +/// +/// [package.metadata] +/// copyright = "Copyright (c) 2024 ACME Inc." +/// ``` +/// +/// You can fetch the copyright with the following: +/// ```ignore +/// use solana_package_metadata::package_metadata; +/// +/// pub fn main() { +/// let copyright = package_metadata!("copyright"); +/// assert_eq!(copyright, "Copyright (c) 2024 ACME Inc."); +/// } +/// ``` +/// +/// ## TOML Support +/// This macro only supports static data: +/// * Strings +/// * Integers +/// * Floating-point numbers +/// * Booleans +/// * Datetimes +/// * Arrays +/// +/// ## Array Example +/// Given the following Cargo manifest: +/// ```ignore +/// [package.metadata.arrays] +/// some_array = [ 1, 2, 3 ] +/// ``` +/// +/// This is legal: +/// ```ignore +/// static ARR: [i64; 3] = package_metadata!("arrays.some_array"); +/// ``` +/// +/// It does *not* currently support accessing TOML array elements directly. +/// TOML tables are not supported. +pub use solana_package_metadata_macro::package_metadata; +/// Re-export solana_pubkey::declare_id for easy usage within the macro +pub use solana_pubkey::declare_id; + +/// Convenience macro for declaring a program id from Cargo.toml package metadata. +/// +/// # Arguments +/// * `key` - A string slice of a dot-separated path to the TOML key of interest +/// +/// # Example +/// Given the following `Cargo.toml`: +/// ```ignore +/// [package] +/// name = "my-solana-program" +/// version = "0.1.0" +/// +/// [package.metadata.solana] +/// program-id = "MyProgram1111111111111111111111111111111111" +/// ``` +/// +/// A program can use the program id declared in its `Cargo.toml` as the program +/// id in code: +/// +/// ```ignore +/// declare_id_with_package_metadata!("solana.program-id"); +/// ``` +/// +/// This program id behaves exactly as if the developer had written: +/// +/// ``` +/// solana_pubkey::declare_id!("MyProgram1111111111111111111111111111111111"); +/// ``` +/// +/// Meaning that it's possible to refer to the program id using `crate::id()`, +/// without needing to specify the program id in multiple places. +#[macro_export] +macro_rules! declare_id_with_package_metadata { + ($key:literal) => { + $crate::declare_id!($crate::package_metadata!($key)); + }; +} diff --git a/sdk/program-entrypoint/Cargo.toml b/sdk/program-entrypoint/Cargo.toml new file mode 100644 index 00000000000000..80f4760b4ad174 --- /dev/null +++ b/sdk/program-entrypoint/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "solana-program-entrypoint" +description = "The Solana BPF program entrypoint supported by the latest BPF loader." +documentation = "https://docs.rs/solana-program-entrypoint" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +solana-account-info = { workspace = true } +solana-msg = { workspace = true } +solana-program-error = { workspace = true } +solana-pubkey = { workspace = true, default-features = false } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/sdk/program/src/entrypoint.rs b/sdk/program-entrypoint/src/lib.rs similarity index 95% rename from sdk/program/src/entrypoint.rs rename to sdk/program-entrypoint/src/lib.rs index f360d5ef2b2ece..1893b79b4a527a 100644 --- a/sdk/program/src/entrypoint.rs +++ b/sdk/program-entrypoint/src/lib.rs @@ -6,8 +6,9 @@ extern crate alloc; use { - crate::{account_info::AccountInfo, pubkey::Pubkey}, alloc::vec::Vec, + solana_account_info::AccountInfo, + solana_pubkey::Pubkey, std::{ alloc::Layout, cell::RefCell, @@ -17,7 +18,11 @@ use { slice::{from_raw_parts, from_raw_parts_mut}, }, }; -pub use {solana_account_info::MAX_PERMITTED_DATA_INCREASE, solana_program_error::ProgramResult}; +// need to re-export msg for custom_heap_default macro +pub use { + solana_account_info::MAX_PERMITTED_DATA_INCREASE, solana_msg::msg as __msg, + solana_program_error::ProgramResult, +}; /// User implemented function to process an instruction /// @@ -97,13 +102,11 @@ pub const NON_DUP_MARKER: u8 = u8::MAX; /// #[cfg(not(feature = "no-entrypoint"))] /// pub mod entrypoint { /// -/// use solana_program::{ -/// account_info::AccountInfo, -/// entrypoint, -/// entrypoint::ProgramResult, -/// msg, -/// pubkey::Pubkey, -/// }; +/// use solana_account_info::AccountInfo; +/// use solana_program_entrypoint::entrypoint; +/// use solana_program_entrypoint::ProgramResult; +/// use solana_msg::msg; +/// use solana_pubkey::Pubkey; /// /// entrypoint!(process_instruction); /// @@ -125,10 +128,9 @@ macro_rules! entrypoint { /// # Safety #[no_mangle] pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 { - let (program_id, accounts, instruction_data) = - unsafe { $crate::entrypoint::deserialize(input) }; + let (program_id, accounts, instruction_data) = unsafe { $crate::deserialize(input) }; match $process_instruction(program_id, &accounts, instruction_data) { - Ok(()) => $crate::entrypoint::SUCCESS, + Ok(()) => $crate::SUCCESS, Err(error) => error.into(), } } @@ -170,7 +172,7 @@ macro_rules! entrypoint_no_alloc { const MAX_ACCOUNT_INFOS: usize = 64; let mut accounts = [UNINIT_ACCOUNT_INFO; MAX_ACCOUNT_INFOS]; let (program_id, num_accounts, instruction_data) = - unsafe { $crate::entrypoint::deserialize_into(input, &mut accounts) }; + unsafe { $crate::deserialize_into(input, &mut accounts) }; // Use `slice_assume_init_ref` once it's stabilized let accounts = &*(&accounts[..num_accounts] as *const [MaybeUninit>] as *const [AccountInfo<'_>]); @@ -178,7 +180,7 @@ macro_rules! entrypoint_no_alloc { #[inline(never)] fn call_program(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> u64 { match $process_instruction(program_id, accounts, data) { - Ok(()) => $crate::entrypoint::SUCCESS, + Ok(()) => $crate::SUCCESS, Err(error) => error.into(), } } @@ -214,9 +216,9 @@ macro_rules! custom_heap_default { () => { #[cfg(all(not(feature = "custom-heap"), target_os = "solana"))] #[global_allocator] - static A: $crate::entrypoint::BumpAllocator = $crate::entrypoint::BumpAllocator { - start: $crate::entrypoint::HEAP_START_ADDRESS as usize, - len: $crate::entrypoint::HEAP_LENGTH, + static A: $crate::BumpAllocator = $crate::BumpAllocator { + start: $crate::HEAP_START_ADDRESS as usize, + len: $crate::HEAP_LENGTH, }; }; } @@ -272,7 +274,7 @@ macro_rules! custom_panic_default { #[no_mangle] fn custom_panic(info: &core::panic::PanicInfo<'_>) { // Full panic reporting - $crate::msg!("{}", info); + $crate::__msg!("{}", info); } }; } diff --git a/sdk/program/Cargo.toml b/sdk/program/Cargo.toml index 391e5e148f11ec..8d15f64d2a22a1 100644 --- a/sdk/program/Cargo.toml +++ b/sdk/program/Cargo.toml @@ -51,6 +51,8 @@ solana-instruction = { workspace = true, default-features = false, features = [ "std", ] } solana-msg = { workspace = true } +solana-native-token = { workspace = true } +solana-program-entrypoint = { workspace = true } solana-program-error = { workspace = true, features = ["serde"] } solana-program-memory = { workspace = true } solana-program-option = { workspace = true } diff --git a/sdk/program/src/address_lookup_table/state.rs b/sdk/program/src/address_lookup_table/state.rs index 13a66637faa919..3136dd063f85da 100644 --- a/sdk/program/src/address_lookup_table/state.rs +++ b/sdk/program/src/address_lookup_table/state.rs @@ -1,6 +1,7 @@ #[cfg(feature = "frozen-abi")] use solana_frozen_abi_macro::{AbiEnumVisitor, AbiExample}; use { + crate::slot_hashes::get_entries, serde_derive::{Deserialize, Serialize}, solana_clock::Slot, solana_program::{ @@ -12,6 +13,19 @@ use { std::borrow::Cow, }; +/// The lookup table may be in a deactivating state until +/// the `deactivation_slot`` is no longer "recent". +/// This function returns a conservative estimate for the +/// last block that the table may be used for lookups. +/// This estimate may be incorrect due to skipped blocks, +/// however, if the current slot is lower than the returned +/// value, the table is guaranteed to still be in the +/// deactivating state. +#[inline] +pub fn estimate_last_valid_slot(deactivation_slot: Slot) -> Slot { + deactivation_slot.saturating_add(get_entries() as Slot) +} + /// The maximum number of addresses that a lookup table can hold pub const LOOKUP_TABLE_MAX_ADDRESSES: usize = 256; diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index 11a2ccaab2f9d5..e029208fa26639 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -483,7 +483,6 @@ pub mod bpf_loader_deprecated; pub mod bpf_loader_upgradeable; pub mod compute_units; pub mod ed25519_program; -pub mod entrypoint; pub mod entrypoint_deprecated; pub mod epoch_rewards; pub mod epoch_schedule; @@ -502,7 +501,6 @@ pub mod loader_v4; pub mod loader_v4_instruction; pub mod log; pub mod message; -pub mod native_token; pub mod nonce; pub mod program; pub mod program_error; @@ -541,9 +539,13 @@ pub use { solana_account_info::{self as account_info, debug_account_data}, solana_clock as clock, solana_msg::msg, + solana_native_token as native_token, + solana_program_entrypoint::{ + self as entrypoint, custom_heap_default, custom_panic_default, entrypoint, + entrypoint_no_alloc, + }, solana_program_option as program_option, solana_pubkey as pubkey, solana_rent as rent, }; - /// The [config native program][np]. /// /// [np]: https://docs.solanalabs.com/runtime/programs#config-program diff --git a/sdk/pubkey/Cargo.toml b/sdk/pubkey/Cargo.toml index 50d7a5ed6bdef7..cdafef8a939f15 100644 --- a/sdk/pubkey/Cargo.toml +++ b/sdk/pubkey/Cargo.toml @@ -18,6 +18,7 @@ bytemuck = { workspace = true, optional = true } bytemuck_derive = { workspace = true, optional = true } five8_const = { workspace = true } num-traits = { workspace = true } +rand = { workspace = true, optional = true } serde = { workspace = true, optional = true } serde_derive = { workspace = true, optional = true } solana-atomic-u64 = { workspace = true } @@ -47,7 +48,6 @@ wasm-bindgen = { workspace = true } anyhow = { workspace = true } arbitrary = { workspace = true, features = ["derive"] } bs58 = { workspace = true, features = ["alloc"] } -rand = { workspace = true } # circular dev deps need to be path deps for `cargo publish` to be happy, # and for now the doc tests need solana-program solana-program = { path = "../program" } @@ -65,11 +65,12 @@ borsh = ["dep:borsh", "dep:borsh0-10", "std"] bytemuck = ["dep:bytemuck", "dep:bytemuck_derive"] curve25519 = ["dep:curve25519-dalek", "sha2"] default = ["std"] -dev-context-only-utils = ["dep:arbitrary", "std"] +dev-context-only-utils = ["dep:arbitrary", "rand"] frozen-abi = [ "dep:solana-frozen-abi", "dep:solana-frozen-abi-macro" ] +rand = ["dep:rand", "std"] serde = ["dep:serde", "dep:serde_derive"] sha2 = ["dep:solana-sha256-hasher", "solana-sha256-hasher/sha2"] std = [] diff --git a/sdk/pubkey/src/lib.rs b/sdk/pubkey/src/lib.rs index e378c44caf3f06..d603d6a97b3b9b 100644 --- a/sdk/pubkey/src/lib.rs +++ b/sdk/pubkey/src/lib.rs @@ -1114,6 +1114,12 @@ macro_rules! pubkey { }; } +/// New random Pubkey for tests and benchmarks. +#[cfg(all(feature = "rand", not(target_os = "solana")))] +pub fn new_rand() -> Pubkey { + Pubkey::from(rand::random::<[u8; PUBKEY_BYTES]>()) +} + #[cfg(test)] mod tests { use {super::*, strum::IntoEnumIterator}; diff --git a/sanitize/Cargo.toml b/sdk/sanitize/Cargo.toml similarity index 100% rename from sanitize/Cargo.toml rename to sdk/sanitize/Cargo.toml diff --git a/sanitize/src/lib.rs b/sdk/sanitize/src/lib.rs similarity index 100% rename from sanitize/src/lib.rs rename to sdk/sanitize/src/lib.rs diff --git a/sdk/src/fee.rs b/sdk/src/fee.rs index 22d04812fa0d76..08a825cd1ba2a2 100644 --- a/sdk/src/fee.rs +++ b/sdk/src/fee.rs @@ -2,7 +2,7 @@ #[cfg(not(target_os = "solana"))] use solana_program::message::SanitizedMessage; -use {crate::native_token::sol_to_lamports, std::num::NonZeroU32}; +use {solana_native_token::sol_to_lamports, std::num::NonZeroU32}; /// A fee and its associated compute unit limit #[derive(Debug, Default, Clone, Eq, PartialEq)] diff --git a/sdk/src/genesis_config.rs b/sdk/src/genesis_config.rs index 6b48f8fd1e7645..ba82ee531e5ccc 100644 --- a/sdk/src/genesis_config.rs +++ b/sdk/src/genesis_config.rs @@ -9,7 +9,6 @@ use { fee_calculator::FeeRateGovernor, hash::{hash, Hash}, inflation::Inflation, - native_token::lamports_to_sol, poh_config::PohConfig, pubkey::Pubkey, rent::Rent, @@ -22,6 +21,7 @@ use { chrono::{TimeZone, Utc}, memmap2::Mmap, solana_account::{Account, AccountSharedData}, + solana_native_token::lamports_to_sol, std::{ collections::BTreeMap, fmt, diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index ed07e8c7806b57..e1d9503a75de20 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -58,7 +58,6 @@ pub use solana_program::{ }; #[cfg(feature = "borsh")] pub use solana_program::{borsh, borsh0_10, borsh1}; -pub mod account_utils; pub mod client; pub mod commitment_config; pub mod compute_budget; @@ -108,6 +107,11 @@ pub mod wasm; #[deprecated(since = "2.1.0", note = "Use `solana-account` crate instead")] pub use solana_account as account; +#[deprecated( + since = "2.1.0", + note = "Use `solana_account::state_traits` crate instead" +)] +pub use solana_account::state_traits as account_utils; #[deprecated(since = "2.1.0", note = "Use `solana-bn254` crate instead")] pub use solana_bn254 as alt_bn128; #[deprecated(since = "2.1.0", note = "Use `solana-decode-error` crate instead")] diff --git a/sdk/src/pubkey.rs b/sdk/src/pubkey.rs index 92d1365d03c5bf..344f0698444cb2 100644 --- a/sdk/src/pubkey.rs +++ b/sdk/src/pubkey.rs @@ -1,13 +1,13 @@ -//! Solana account addresses. - -pub use solana_program::pubkey::*; - -/// New random Pubkey for tests and benchmarks. #[cfg(feature = "full")] -pub fn new_rand() -> Pubkey { - Pubkey::from(rand::random::<[u8; PUBKEY_BYTES]>()) -} - +pub use solana_pubkey::new_rand; +#[cfg(target_os = "solana")] +pub use solana_pubkey::syscalls; +pub use solana_pubkey::{ + bytes_are_curve_point, ParsePubkeyError, Pubkey, PubkeyError, MAX_SEEDS, MAX_SEED_LEN, + PUBKEY_BYTES, +}; + +#[deprecated(since = "2.1.0")] #[cfg(feature = "full")] pub fn write_pubkey_file(outfile: &str, pubkey: Pubkey) -> Result<(), Box> { use std::io::Write; @@ -24,6 +24,7 @@ pub fn write_pubkey_file(outfile: &str, pubkey: Pubkey) -> Result<(), Box Result> { let f = std::fs::File::open(infile)?; @@ -32,19 +33,3 @@ pub fn read_pubkey_file(infile: &str) -> Result Result<(), Box> { - let filename = "test_pubkey.json"; - let pubkey = solana_sdk::pubkey::new_rand(); - write_pubkey_file(filename, pubkey)?; - let read = read_pubkey_file(filename)?; - assert_eq!(read, pubkey); - remove_file(filename)?; - Ok(()) - } -} diff --git a/svm/examples/json-rpc/server/src/rpc_process.rs b/svm/examples/json-rpc/server/src/rpc_process.rs index ed239323b462b4..f0721783c9d38d 100644 --- a/svm/examples/json-rpc/server/src/rpc_process.rs +++ b/svm/examples/json-rpc/server/src/rpc_process.rs @@ -208,7 +208,7 @@ impl JsonRpcRequestProcessor { (pubkey, acc_data) }) .collect(); - let batch_processor = TransactionBatchProcessor::::new( + let batch_processor = TransactionBatchProcessor::::new_uninitialized( EXECUTION_SLOT, EXECUTION_EPOCH, HashSet::new(), diff --git a/svm/examples/paytube/src/processor.rs b/svm/examples/paytube/src/processor.rs index 71eaccc956826b..663c1b5044665e 100644 --- a/svm/examples/paytube/src/processor.rs +++ b/svm/examples/paytube/src/processor.rs @@ -3,17 +3,18 @@ use { solana_bpf_loader_program::syscalls::create_program_runtime_environment_v1, solana_compute_budget::compute_budget::ComputeBudget, - solana_program_runtime::loaded_programs::{ - BlockRelation, ForkGraph, LoadProgramMetrics, ProgramCacheEntry, - }, - solana_sdk::{account::ReadableAccount, clock::Slot, feature_set::FeatureSet, transaction}, + solana_program_runtime::loaded_programs::{BlockRelation, ForkGraph, ProgramCacheEntry}, + solana_sdk::{clock::Slot, feature_set::FeatureSet, transaction}, solana_svm::{ account_loader::CheckedTransactionDetails, transaction_processing_callback::TransactionProcessingCallback, transaction_processor::TransactionBatchProcessor, }, solana_system_program::system_processor, - std::sync::{Arc, RwLock}, + std::{ + collections::HashSet, + sync::{Arc, RwLock}, + }, }; /// In order to use the `TransactionBatchProcessor`, another trait - Solana @@ -40,13 +41,25 @@ pub(crate) fn create_transaction_batch_processor>, ) -> TransactionBatchProcessor { - let processor = TransactionBatchProcessor::::default(); + // Create a new transaction batch processor. + // + // We're going to use slot 1 specifically because any programs we add will + // be deployed in slot 0, and they are delayed visibility until the next + // slot (1). + // This includes programs owned by BPF Loader v2, which are automatically + // marked as "depoyed" in slot 0. + // See `solana_svm::program_loader::load_program_with_pubkey` for more + // details. + let processor = TransactionBatchProcessor::::new_uninitialized( + /* slot */ 1, + /* epoch */ 1, + /* builtin_program_ids */ HashSet::new(), + ); { let mut cache = processor.program_cache.write().unwrap(); // Initialize the mocked fork graph. - // let fork_graph = Arc::new(RwLock::new(PayTubeForkGraph {})); cache.fork_graph = Some(Arc::downgrade(&fork_graph)); // Initialize a proper cache environment. @@ -55,27 +68,6 @@ pub(crate) fn create_transaction_batch_processor SanitizedTransaction { + let mut message = Message::new( + &[Instruction::new_with_bytes(program_id, &[], vec![])], + Some(&fee_payer), + ); + message.header.num_readonly_unsigned_accounts = 0; + + let legacy_message = LegacyMessage { + message: Cow::Owned(message), + is_writable_account_cache: vec![true, true], + }; + + SanitizedTransaction::new_for_tests( + SanitizedMessage::Legacy(legacy_message), + vec![Signature::default()], + false, + ) + } + fn load_accounts_aux_test( tx: Transaction, accounts: &[TransactionAccount], @@ -1378,6 +1402,178 @@ mod tests { assert_eq!(result.err(), Some(TransactionError::AccountNotFound)); } + #[test] + fn test_load_transaction_accounts_program_account_executable_bypass() { + // currently, the account loader retrieves read-only non-instruction accounts from the program cache + // it creates a mock AccountSharedData with the executable flag set to true + // however, it does not check whether these accounts are actually executable before doing so + // this affects consensus: a transaction that uses a cached non-executable program executes and fails + // but if the transaction gets the program from accounts-db, it will be dropped during account loading + // this test enforces the current behavior, so that future account loader changes do not break consensus + + let mut mock_bank = TestCallbacks::default(); + let account_keypair = Keypair::new(); + let program_keypair = Keypair::new(); + + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(200); + mock_bank + .accounts_map + .insert(account_keypair.pubkey(), account_data.clone()); + + let mut program_data = AccountSharedData::default(); + program_data.set_lamports(200); + program_data.set_owner(bpf_loader::id()); + mock_bank + .accounts_map + .insert(program_keypair.pubkey(), program_data); + + let mut loader_data = AccountSharedData::default(); + loader_data.set_lamports(200); + loader_data.set_executable(true); + loader_data.set_owner(native_loader::id()); + mock_bank + .accounts_map + .insert(bpf_loader::id(), loader_data.clone()); + mock_bank + .accounts_map + .insert(native_loader::id(), loader_data); + + let mut error_metrics = TransactionErrorMetrics::default(); + let mut loaded_programs = ProgramCacheForTxBatch::default(); + + let transaction = + SanitizedTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_keypair.pubkey(), + &[], + vec![], + )], + Some(&account_keypair.pubkey()), + &[&account_keypair], + Hash::default(), + )); + + let result = load_transaction_accounts( + &mock_bank, + transaction.message(), + LoadedTransactionAccount { + account: account_data.clone(), + ..LoadedTransactionAccount::default() + }, + &ComputeBudgetLimits::default(), + &mut error_metrics, + None, + &FeatureSet::default(), + &RentCollector::default(), + &loaded_programs, + ); + + // without cache, program is invalid + assert_eq!( + result.err(), + Some(TransactionError::InvalidProgramForExecution) + ); + + loaded_programs.replenish( + program_keypair.pubkey(), + Arc::new(ProgramCacheEntry::default()), + ); + + let result = load_transaction_accounts( + &mock_bank, + transaction.message(), + LoadedTransactionAccount { + account: account_data.clone(), + ..LoadedTransactionAccount::default() + }, + &ComputeBudgetLimits::default(), + &mut error_metrics, + None, + &FeatureSet::default(), + &RentCollector::default(), + &loaded_programs, + ); + + // with cache, executable flag is bypassed + let mut cached_program = AccountSharedData::default(); + cached_program.set_owner(native_loader::id()); + cached_program.set_executable(true); + + assert_eq!( + result.unwrap(), + LoadedTransactionAccounts { + accounts: vec![ + (account_keypair.pubkey(), account_data.clone()), + (program_keypair.pubkey(), cached_program), + ], + program_indices: vec![vec![1]], + rent: 0, + rent_debits: RentDebits::default(), + loaded_accounts_data_size: 0, + } + ); + + let transaction = + SanitizedTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_keypair.pubkey(), + &[], + vec![AccountMeta::new_readonly(program_keypair.pubkey(), false)], + )], + Some(&account_keypair.pubkey()), + &[&account_keypair], + Hash::default(), + )); + + let result = load_transaction_accounts( + &mock_bank, + transaction.message(), + LoadedTransactionAccount { + account: account_data.clone(), + ..LoadedTransactionAccount::default() + }, + &ComputeBudgetLimits::default(), + &mut error_metrics, + None, + &FeatureSet::default(), + &RentCollector::default(), + &loaded_programs, + ); + + // including program as instruction account bypasses executable bypass + assert_eq!( + result.err(), + Some(TransactionError::InvalidProgramForExecution) + ); + + let transaction = new_unchecked_sanitized_transaction_with_writable_program( + program_keypair.pubkey(), + account_keypair.pubkey(), + ); + + let result = load_transaction_accounts( + &mock_bank, + transaction.message(), + LoadedTransactionAccount { + account: account_data.clone(), + ..LoadedTransactionAccount::default() + }, + &ComputeBudgetLimits::default(), + &mut error_metrics, + None, + &FeatureSet::default(), + &RentCollector::default(), + &loaded_programs, + ); + + // including program as writable bypasses executable bypass + assert_eq!( + result.err(), + Some(TransactionError::InvalidProgramForExecution) + ); + } + #[test] fn test_load_transaction_accounts_program_account_no_data() { let key1 = Keypair::new(); @@ -2214,4 +2410,287 @@ mod tests { assert_eq!(actual_inspected_accounts, expected_inspected_accounts,); } + + #[test] + fn test_load_transaction_accounts_data_sizes() { + let mut mock_bank = TestCallbacks::default(); + + let mut next_size = 1; + let mut make_account = |pubkey, owner, executable| { + let size = next_size; + let account = AccountSharedData::create( + LAMPORTS_PER_SOL, + vec![0; size], + owner, + executable, + u64::MAX, + ); + + mock_bank.accounts_map.insert(pubkey, account.clone()); + + // accounts are counted at most twice + // by multiplying account size by 4, we ensure all totals are unique + next_size *= 4; + + (size as u32, account) + }; + + let (native_loader_size, _) = make_account(native_loader::id(), native_loader::id(), true); + let (bpf_loader_size, _) = make_account(bpf_loader::id(), native_loader::id(), true); + let (upgradeable_loader_size, _) = + make_account(bpf_loader_upgradeable::id(), native_loader::id(), true); + + let program1_keypair = Keypair::new(); + let program1 = program1_keypair.pubkey(); + let (program1_size, _) = make_account(program1, bpf_loader::id(), true); + + let program2 = Pubkey::new_unique(); + let (program2_size, _) = make_account(program2, bpf_loader_upgradeable::id(), true); + + let programdata2 = Pubkey::new_unique(); + let (programdata2_size, _) = + make_account(programdata2, bpf_loader_upgradeable::id(), false); + + let fee_payer_keypair = Keypair::new(); + let fee_payer = fee_payer_keypair.pubkey(); + let (fee_payer_size, fee_payer_account) = + make_account(fee_payer, system_program::id(), false); + + let account1 = Pubkey::new_unique(); + let (account1_size, _) = make_account(account1, program1, false); + + let account2 = Pubkey::new_unique(); + let (account2_size, _) = make_account(account2, program2, false); + + let test_transaction_data_size_with_cache = |transaction, cache, expected_size| { + let loaded_transaction_accounts = load_transaction_accounts( + &mock_bank, + &transaction, + LoadedTransactionAccount { + account: fee_payer_account.clone(), + loaded_size: fee_payer_size as usize, + rent_collected: 0, + }, + &ComputeBudgetLimits::default(), + &mut TransactionErrorMetrics::default(), + None, + &FeatureSet::default(), + &RentCollector::default(), + &cache, + ) + .unwrap(); + + assert_eq!( + loaded_transaction_accounts.loaded_accounts_data_size, + expected_size + ); + }; + + let test_data_size_with_cache = |instructions: Vec<_>, cache, expected_size| { + let transaction = SanitizedTransaction::from_transaction_for_tests( + Transaction::new_signed_with_payer( + &instructions, + Some(&fee_payer), + &[&fee_payer_keypair], + Hash::default(), + ), + ); + + test_transaction_data_size_with_cache(transaction, cache, expected_size) + }; + + for account_meta in [AccountMeta::new, AccountMeta::new_readonly] { + let test_data_size = |instructions, expected_size| { + test_data_size_with_cache( + instructions, + ProgramCacheForTxBatch::default(), + expected_size, + ) + }; + + // one program plus loader + let ixns = vec![Instruction::new_with_bytes(program1, &[], vec![])]; + test_data_size(ixns, program1_size + bpf_loader_size + fee_payer_size); + + // two programs, two loaders, two accounts + let ixns = vec![ + Instruction::new_with_bytes(program1, &[], vec![account_meta(account1, false)]), + Instruction::new_with_bytes(program2, &[], vec![account_meta(account2, false)]), + ]; + test_data_size( + ixns, + account1_size + + account2_size + + program1_size + + program2_size + + bpf_loader_size + + upgradeable_loader_size + + fee_payer_size, + ); + + // ordinary owners not counted + let ixns = vec![Instruction::new_with_bytes( + program1, + &[], + vec![account_meta(account2, false)], + )]; + test_data_size( + ixns, + account2_size + program1_size + bpf_loader_size + fee_payer_size, + ); + + // program and loader counted once + let ixns = vec![ + Instruction::new_with_bytes(program1, &[], vec![]), + Instruction::new_with_bytes(program1, &[], vec![]), + ]; + test_data_size(ixns, program1_size + bpf_loader_size + fee_payer_size); + + // native loader not counted if loader + let ixns = vec![Instruction::new_with_bytes(bpf_loader::id(), &[], vec![])]; + test_data_size(ixns, bpf_loader_size + fee_payer_size); + + // native loader counted if instruction + let ixns = vec![Instruction::new_with_bytes( + bpf_loader::id(), + &[], + vec![account_meta(native_loader::id(), false)], + )]; + test_data_size(ixns, bpf_loader_size + native_loader_size + fee_payer_size); + + // native loader counted if invoked + let ixns = vec![Instruction::new_with_bytes( + native_loader::id(), + &[], + vec![], + )]; + test_data_size(ixns, native_loader_size + fee_payer_size); + + // native loader counted once if invoked and instruction + let ixns = vec![Instruction::new_with_bytes( + native_loader::id(), + &[], + vec![account_meta(native_loader::id(), false)], + )]; + test_data_size(ixns, native_loader_size + fee_payer_size); + + // loader counted twice if included in instruction + let ixns = vec![Instruction::new_with_bytes( + program1, + &[], + vec![account_meta(bpf_loader::id(), false)], + )]; + test_data_size(ixns, program1_size + bpf_loader_size * 2 + fee_payer_size); + + // cover that case with multiple loaders to be sure + let ixns = vec![ + Instruction::new_with_bytes( + program1, + &[], + vec![ + account_meta(bpf_loader::id(), false), + account_meta(bpf_loader_upgradeable::id(), false), + ], + ), + Instruction::new_with_bytes(program2, &[], vec![account_meta(account1, false)]), + Instruction::new_with_bytes( + bpf_loader_upgradeable::id(), + &[], + vec![account_meta(account1, false)], + ), + ]; + test_data_size( + ixns, + account1_size + + program1_size + + program2_size + + bpf_loader_size * 2 + + upgradeable_loader_size * 2 + + fee_payer_size, + ); + + // loader counted twice even if included first + let ixns = vec![ + Instruction::new_with_bytes(bpf_loader::id(), &[], vec![]), + Instruction::new_with_bytes(program1, &[], vec![]), + ]; + test_data_size(ixns, program1_size + bpf_loader_size * 2 + fee_payer_size); + + // fee-payer counted once + let ixns = vec![Instruction::new_with_bytes( + program1, + &[], + vec![account_meta(fee_payer, false)], + )]; + test_data_size(ixns, program1_size + bpf_loader_size + fee_payer_size); + + // edge cases involving program cache + let mut program_cache = ProgramCacheForTxBatch::default(); + + let program2_entry = ProgramCacheEntry { + account_size: (program2_size + programdata2_size) as usize, + account_owner: ProgramCacheEntryOwner::LoaderV3, + ..ProgramCacheEntry::default() + }; + program_cache.replenish(program2, Arc::new(program2_entry)); + + // normal function call uses the combined cache size + let ixns = vec![Instruction::new_with_bytes(program2, &[], vec![])]; + test_data_size_with_cache( + ixns, + program_cache.clone(), + program2_size + programdata2_size + upgradeable_loader_size + fee_payer_size, + ); + + // program as instruction account bypasses the cache + let ixns = vec![Instruction::new_with_bytes( + program2, + &[], + vec![account_meta(program2, false)], + )]; + test_data_size_with_cache( + ixns, + program_cache.clone(), + program2_size + upgradeable_loader_size + fee_payer_size, + ); + + // programdata as instruction account double-counts it + let ixns = vec![Instruction::new_with_bytes( + program2, + &[], + vec![account_meta(programdata2, false)], + )]; + test_data_size_with_cache( + ixns, + program_cache.clone(), + program2_size + programdata2_size * 2 + upgradeable_loader_size + fee_payer_size, + ); + + // both as instruction accounts, for completeness + let ixns = vec![Instruction::new_with_bytes( + program2, + &[], + vec![ + account_meta(program2, false), + account_meta(programdata2, false), + ], + )]; + test_data_size_with_cache( + ixns, + program_cache.clone(), + program2_size + programdata2_size + upgradeable_loader_size + fee_payer_size, + ); + + // writable program bypasses the cache + let tx = new_unchecked_sanitized_transaction_with_writable_program(program2, fee_payer); + test_transaction_data_size_with_cache( + tx, + program_cache.clone(), + program2_size + upgradeable_loader_size + fee_payer_size, + ); + + // NOTE for the new loader we *must* also test arbitrary permutations of the cache transactions + // to ensure that the batched loading is overridden on a tx-per-tx basis + } + } } diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index 92706997471c52..91f79952254095 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -189,7 +189,20 @@ impl Default for TransactionBatchProcessor { } impl TransactionBatchProcessor { - pub fn new(slot: Slot, epoch: Epoch, builtin_program_ids: HashSet) -> Self { + /// Create a new, uninitialized `TransactionBatchProcessor`. + /// + /// In this context, uninitialized means that the `TransactionBatchProcessor` + /// has been initialized with an empty program cache. The cache contains no + /// programs (including builtins) and has not been configured with a valid + /// fork graph. + /// + /// When using this method, it's advisable to call `set_fork_graph_in_program_cache` + /// as well as `add_builtin` to configure the cache before using the processor. + pub fn new_uninitialized( + slot: Slot, + epoch: Epoch, + builtin_program_ids: HashSet, + ) -> Self { Self { slot, epoch, @@ -199,6 +212,12 @@ impl TransactionBatchProcessor { } } + /// Create a new `TransactionBatchProcessor` from the current instance, but + /// with the provided slot and epoch. + /// + /// * Inherits the program cache and builtin program ids from the current + /// instance. + /// * Resets the sysvar cache. pub fn new_from(&self, slot: Slot, epoch: Epoch) -> Self { Self { slot, diff --git a/svm/tests/concurrent_tests.rs b/svm/tests/concurrent_tests.rs index 2e84fbba243663..4c547675784577 100644 --- a/svm/tests/concurrent_tests.rs +++ b/svm/tests/concurrent_tests.rs @@ -40,7 +40,8 @@ mod transaction_builder; fn program_cache_execution(threads: usize) { let mut mock_bank = MockBankCallback::default(); - let batch_processor = TransactionBatchProcessor::::new(5, 5, HashSet::new()); + let batch_processor = + TransactionBatchProcessor::::new_uninitialized(5, 5, HashSet::new()); let fork_graph = Arc::new(RwLock::new(MockForkGraph {})); batch_processor.program_cache.write().unwrap().fork_graph = Some(Arc::downgrade(&fork_graph)); @@ -126,11 +127,9 @@ fn test_program_cache_with_exhaustive_scheduler() { // correctly. fn svm_concurrent() { let mock_bank = Arc::new(MockBankCallback::default()); - let batch_processor = Arc::new(TransactionBatchProcessor::::new( - 5, - 2, - HashSet::new(), - )); + let batch_processor = Arc::new( + TransactionBatchProcessor::::new_uninitialized(5, 2, HashSet::new()), + ); let fork_graph = Arc::new(RwLock::new(MockForkGraph {})); create_executable_environment( diff --git a/svm/tests/conformance.rs b/svm/tests/conformance.rs index dc521bc36eee15..7bc9ee5184b2c6 100644 --- a/svm/tests/conformance.rs +++ b/svm/tests/conformance.rs @@ -244,7 +244,8 @@ fn run_fixture(fixture: InstrFixture, filename: OsString, execute_as_instr: bool create_program_runtime_environment_v1(&feature_set, &compute_budget, false, false).unwrap(); mock_bank.override_feature_set(feature_set); - let batch_processor = TransactionBatchProcessor::::new(42, 2, HashSet::new()); + let batch_processor = + TransactionBatchProcessor::::new_uninitialized(42, 2, HashSet::new()); let fork_graph = Arc::new(RwLock::new(MockForkGraph {})); { diff --git a/svm/tests/integration_test.rs b/svm/tests/integration_test.rs index 6b1325a643d2f0..9f781607aa3112 100644 --- a/svm/tests/integration_test.rs +++ b/svm/tests/integration_test.rs @@ -872,7 +872,7 @@ fn execute_test_entry(test_entry: SvmTestEntry) { .insert(*pubkey, account.clone()); } - let batch_processor = TransactionBatchProcessor::::new( + let batch_processor = TransactionBatchProcessor::::new_uninitialized( EXECUTION_SLOT, EXECUTION_EPOCH, HashSet::new(), @@ -1059,7 +1059,7 @@ fn svm_inspect_account() { // Load and execute the transaction - let batch_processor = TransactionBatchProcessor::::new( + let batch_processor = TransactionBatchProcessor::::new_uninitialized( EXECUTION_SLOT, EXECUTION_EPOCH, HashSet::new(), diff --git a/svm/tests/transaction_builder.rs b/svm/tests/transaction_builder.rs index 803487773a63ec..664ea6237120cb 100644 --- a/svm/tests/transaction_builder.rs +++ b/svm/tests/transaction_builder.rs @@ -26,7 +26,7 @@ pub struct SanitizedTransactionBuilder { signed_readonly_accounts: Vec<(Pubkey, Signature)>, signed_mutable_accounts: Vec<(Pubkey, Signature)>, unsigned_readonly_accounts: Vec, - unsigned_mutable_account: Vec, + unsigned_mutable_accounts: Vec, } #[derive(PartialEq, Eq, Hash, Clone)] @@ -91,7 +91,7 @@ impl SanitizedTransactionBuilder { AccountType::SignerReadonly } (false, true) => { - self.unsigned_mutable_account.push(item.pubkey); + self.unsigned_mutable_accounts.push(item.pubkey); AccountType::Writable } (false, false) => { @@ -117,7 +117,7 @@ impl SanitizedTransactionBuilder { self.signed_mutable_accounts .len() .saturating_add(self.signed_readonly_accounts.len()) - .saturating_add(self.unsigned_mutable_account.len()) + .saturating_add(self.unsigned_mutable_accounts.len()) .saturating_add(self.unsigned_readonly_accounts.len()) .saturating_add(1), ); @@ -159,7 +159,7 @@ impl SanitizedTransactionBuilder { positions_lambda(key, AccountType::SignerReadonly); signatures.push(*signature); }); - self.unsigned_mutable_account + self.unsigned_mutable_accounts .iter() .for_each(|key| positions_lambda(key, AccountType::Writable)); self.unsigned_readonly_accounts @@ -232,8 +232,8 @@ impl SanitizedTransactionBuilder { self.num_readonly_unsigned_accounts = 0; self.signed_mutable_accounts.clear(); self.signed_readonly_accounts.clear(); - self.unsigned_mutable_account.clear(); - self.unsigned_mutable_account.clear(); + self.unsigned_mutable_accounts.clear(); + self.unsigned_readonly_accounts.clear(); instructions } diff --git a/zk-sdk/Cargo.toml b/zk-sdk/Cargo.toml index 5a1ff83a620b9c..8dc8ffc18029fe 100644 --- a/zk-sdk/Cargo.toml +++ b/zk-sdk/Cargo.toml @@ -38,6 +38,10 @@ solana-sdk = { workspace = true } subtle = { workspace = true } zeroize = { workspace = true, features = ["zeroize_derive"] } +[target.'cfg(target_arch = "wasm32")'.dependencies] +js-sys = { workspace = true } +wasm-bindgen = { workspace = true } + [lib] crate-type = ["cdylib", "rlib"] diff --git a/zk-sdk/src/encryption/auth_encryption.rs b/zk-sdk/src/encryption/auth_encryption.rs index 14c145decb6736..15ed58069b80ca 100644 --- a/zk-sdk/src/encryption/auth_encryption.rs +++ b/zk-sdk/src/encryption/auth_encryption.rs @@ -13,7 +13,17 @@ use { }, base64::{prelude::BASE64_STANDARD, Engine}, rand::{rngs::OsRng, Rng}, - sha3::{Digest, Sha3_512}, + std::{convert::TryInto, fmt}, + zeroize::Zeroize, +}; +// Currently, `wasm_bindgen` exports types and functions included in the current crate, but all +// types and functions exported for wasm targets in all of its dependencies +// (https://github.com/rustwasm/wasm-bindgen/issues/3759). We specifically exclude some of the +// dependencies that will cause unnecessary bloat to the wasm binary. +#[cfg(not(target_arch = "wasm32"))] +use { + sha3::Digest, + sha3::Sha3_512, solana_derivation_path::DerivationPath, solana_sdk::{ signature::Signature, @@ -23,12 +33,10 @@ use { }, }, std::{ - convert::TryInto, - error, fmt, + error, io::{Read, Write}, }, subtle::ConstantTimeEq, - zeroize::Zeroize, }; /// Byte length of an authenticated encryption nonce component @@ -82,6 +90,25 @@ impl AuthenticatedEncryption { #[derive(Debug, Zeroize, Eq, PartialEq)] pub struct AeKey([u8; AE_KEY_LEN]); +impl AeKey { + /// Generates a random authenticated encryption key. + /// + /// This function is randomized. It internally samples a scalar element using `OsRng`. + pub fn new_rand() -> Self { + AuthenticatedEncryption::keygen() + } + + /// Encrypts an amount under the authenticated encryption key. + pub fn encrypt(&self, amount: u64) -> AeCiphertext { + AuthenticatedEncryption::encrypt(self, amount) + } + + pub fn decrypt(&self, ciphertext: &AeCiphertext) -> Option { + AuthenticatedEncryption::decrypt(self, ciphertext) + } +} + +#[cfg(not(target_arch = "wasm32"))] impl AeKey { /// Deterministically derives an authenticated encryption key from a Solana signer and a public /// seed. @@ -130,24 +157,9 @@ impl AeKey { result.to_vec() } - - /// Generates a random authenticated encryption key. - /// - /// This function is randomized. It internally samples a scalar element using `OsRng`. - pub fn new_rand() -> Self { - AuthenticatedEncryption::keygen() - } - - /// Encrypts an amount under the authenticated encryption key. - pub fn encrypt(&self, amount: u64) -> AeCiphertext { - AuthenticatedEncryption::encrypt(self, amount) - } - - pub fn decrypt(&self, ciphertext: &AeCiphertext) -> Option { - AuthenticatedEncryption::decrypt(self, ciphertext) - } } +#[cfg(not(target_arch = "wasm32"))] impl EncodableKey for AeKey { fn read(reader: &mut R) -> Result> { let bytes: [u8; AE_KEY_LEN] = serde_json::from_reader(reader)?; @@ -162,6 +174,7 @@ impl EncodableKey for AeKey { } } +#[cfg(not(target_arch = "wasm32"))] impl SeedDerivable for AeKey { fn from_seed(seed: &[u8]) -> Result> { const MINIMUM_SEED_LEN: usize = AE_KEY_LEN; diff --git a/zk-sdk/src/encryption/elgamal.rs b/zk-sdk/src/encryption/elgamal.rs index 850db36329c715..9df73d6b199b85 100644 --- a/zk-sdk/src/encryption/elgamal.rs +++ b/zk-sdk/src/encryption/elgamal.rs @@ -14,10 +14,33 @@ //! As the messages are encrypted as scalar elements (a.k.a. in the "exponent"), one must solve the //! discrete log to recover the originally encrypted value. +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; +// Currently, `wasm_bindgen` exports types and functions included in the current crate, but all +// types and functions exported for wasm targets in all of its dependencies +// (https://github.com/rustwasm/wasm-bindgen/issues/3759). We specifically exclude some of the +// dependencies that will cause unnecessary bloat to the wasm binary. +#[cfg(not(target_arch = "wasm32"))] +use { + crate::encryption::discrete_log::DiscreteLog, + sha3::Digest, + solana_derivation_path::DerivationPath, + solana_sdk::{ + signature::Signature, + signer::{ + keypair::generate_seed_from_seed_phrase_and_passphrase, EncodableKey, EncodableKeypair, + SeedDerivable, Signer, SignerError, + }, + }, + std::{ + error, + io::{Read, Write}, + path::Path, + }, +}; use { crate::{ encryption::{ - discrete_log::DiscreteLog, pedersen::{Pedersen, PedersenCommitment, PedersenOpening, G, H}, DECRYPT_HANDLE_LEN, ELGAMAL_CIPHERTEXT_LEN, ELGAMAL_KEYPAIR_LEN, ELGAMAL_PUBKEY_LEN, ELGAMAL_SECRET_KEY_LEN, PEDERSEN_COMMITMENT_LEN, @@ -33,21 +56,8 @@ use { }, rand::rngs::OsRng, serde::{Deserialize, Serialize}, - sha3::{Digest, Sha3_512}, - solana_derivation_path::DerivationPath, - solana_sdk::{ - signature::Signature, - signer::{ - keypair::generate_seed_from_seed_phrase_and_passphrase, EncodableKey, EncodableKeypair, - SeedDerivable, Signer, SignerError, - }, - }, - std::{ - convert::TryInto, - error, fmt, - io::{Read, Write}, - path::Path, - }, + sha3::Sha3_512, + std::{convert::TryInto, fmt}, subtle::{Choice, ConstantTimeEq}, zeroize::Zeroize, }; @@ -116,6 +126,7 @@ impl ElGamal { /// /// The output of this function is of type `DiscreteLog`. To recover, the originally encrypted /// amount, use `DiscreteLog::decode`. + #[cfg(not(target_arch = "wasm32"))] fn decrypt(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> DiscreteLog { DiscreteLog::new( *G, @@ -128,6 +139,7 @@ impl ElGamal { /// /// If the originally encrypted amount is not a positive 32-bit number, then the function /// returns `None`. + #[cfg(not(target_arch = "wasm32"))] fn decrypt_u32(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> Option { let discrete_log_instance = Self::decrypt(secret, ciphertext); discrete_log_instance.decode_u32() @@ -137,6 +149,7 @@ impl ElGamal { /// A (twisted) ElGamal encryption keypair. /// /// The instances of the secret key are zeroized on drop. +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Zeroize)] pub struct ElGamalKeypair { /// The public half of this keypair. @@ -145,6 +158,31 @@ pub struct ElGamalKeypair { secret: ElGamalSecretKey, } +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +impl ElGamalKeypair { + /// Generates the public and secret keys for ElGamal encryption. + /// + /// This function is randomized. It internally samples a scalar element using `OsRng`. + pub fn new_rand() -> Self { + ElGamal::keygen() + } + + pub fn pubkey_owned(&self) -> ElGamalPubkey { + self.public + } +} + +impl ElGamalKeypair { + pub fn pubkey(&self) -> &ElGamalPubkey { + &self.public + } + + pub fn secret(&self) -> &ElGamalSecretKey { + &self.secret + } +} + +#[cfg(not(target_arch = "wasm32"))] impl ElGamalKeypair { /// Create an ElGamal keypair from an ElGamal public key and an ElGamal secret key. /// @@ -187,21 +225,6 @@ impl ElGamalKeypair { Ok(Self::new(secret)) } - /// Generates the public and secret keys for ElGamal encryption. - /// - /// This function is randomized. It internally samples a scalar element using `OsRng`. - pub fn new_rand() -> Self { - ElGamal::keygen() - } - - pub fn pubkey(&self) -> &ElGamalPubkey { - &self.public - } - - pub fn secret(&self) -> &ElGamalSecretKey { - &self.secret - } - /// Reads a JSON-encoded keypair from a `Reader` implementor pub fn read_json(reader: &mut R) -> Result> { let bytes: Vec = serde_json::from_reader(reader)?; @@ -232,6 +255,7 @@ impl ElGamalKeypair { } } +#[cfg(not(target_arch = "wasm32"))] impl EncodableKey for ElGamalKeypair { fn read(reader: &mut R) -> Result> { Self::read_json(reader) @@ -276,6 +300,7 @@ impl From<&ElGamalKeypair> for [u8; ELGAMAL_KEYPAIR_LEN] { } } +#[cfg(not(target_arch = "wasm32"))] impl SeedDerivable for ElGamalKeypair { fn from_seed(seed: &[u8]) -> Result> { let secret = ElGamalSecretKey::from_seed(seed)?; @@ -301,6 +326,7 @@ impl SeedDerivable for ElGamalKeypair { } } +#[cfg(not(target_arch = "wasm32"))] impl EncodableKeypair for ElGamalKeypair { type Pubkey = ElGamalPubkey; @@ -310,6 +336,7 @@ impl EncodableKeypair for ElGamalKeypair { } /// Public key for the ElGamal encryption scheme. +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Zeroize)] pub struct ElGamalPubkey(RistrettoPoint); impl ElGamalPubkey { @@ -348,6 +375,7 @@ impl ElGamalPubkey { } } +#[cfg(not(target_arch = "wasm32"))] impl EncodableKey for ElGamalPubkey { fn read(reader: &mut R) -> Result> { let bytes: Vec = serde_json::from_reader(reader)?; @@ -410,6 +438,38 @@ impl From<&ElGamalPubkey> for [u8; ELGAMAL_PUBKEY_LEN] { #[derive(Clone, Debug, Deserialize, Serialize, Zeroize)] #[zeroize(drop)] pub struct ElGamalSecretKey(Scalar); +impl ElGamalSecretKey { + /// Randomly samples an ElGamal secret key. + /// + /// This function is randomized. It internally samples a scalar element using `OsRng`. + pub fn new_rand() -> Self { + ElGamalSecretKey(Scalar::random(&mut OsRng)) + } + + /// Derive an ElGamal secret key from an entropy seed. + pub fn from_seed(seed: &[u8]) -> Result { + const MINIMUM_SEED_LEN: usize = ELGAMAL_SECRET_KEY_LEN; + const MAXIMUM_SEED_LEN: usize = 65535; + + if seed.len() < MINIMUM_SEED_LEN { + return Err(ElGamalError::SeedLengthTooShort); + } + if seed.len() > MAXIMUM_SEED_LEN { + return Err(ElGamalError::SeedLengthTooLong); + } + Ok(ElGamalSecretKey(Scalar::hash_from_bytes::(seed))) + } + + pub fn get_scalar(&self) -> &Scalar { + &self.0 + } + + pub fn as_bytes(&self) -> &[u8; ELGAMAL_SECRET_KEY_LEN] { + self.0.as_bytes() + } +} + +#[cfg(not(target_arch = "wasm32"))] impl ElGamalSecretKey { /// Deterministically derives an ElGamal secret key from a Solana signer and a public seed. /// @@ -458,31 +518,6 @@ impl ElGamalSecretKey { result.to_vec() } - /// Randomly samples an ElGamal secret key. - /// - /// This function is randomized. It internally samples a scalar element using `OsRng`. - pub fn new_rand() -> Self { - ElGamalSecretKey(Scalar::random(&mut OsRng)) - } - - /// Derive an ElGamal secret key from an entropy seed. - pub fn from_seed(seed: &[u8]) -> Result { - const MINIMUM_SEED_LEN: usize = ELGAMAL_SECRET_KEY_LEN; - const MAXIMUM_SEED_LEN: usize = 65535; - - if seed.len() < MINIMUM_SEED_LEN { - return Err(ElGamalError::SeedLengthTooShort); - } - if seed.len() > MAXIMUM_SEED_LEN { - return Err(ElGamalError::SeedLengthTooLong); - } - Ok(ElGamalSecretKey(Scalar::hash_from_bytes::(seed))) - } - - pub fn get_scalar(&self) -> &Scalar { - &self.0 - } - /// Decrypts a ciphertext using the ElGamal secret key. /// /// The output of this function is of type `DiscreteLog`. To recover, the originally encrypted @@ -495,12 +530,9 @@ impl ElGamalSecretKey { pub fn decrypt_u32(&self, ciphertext: &ElGamalCiphertext) -> Option { ElGamal::decrypt_u32(self, ciphertext) } - - pub fn as_bytes(&self) -> &[u8; ELGAMAL_SECRET_KEY_LEN] { - self.0.as_bytes() - } } +#[cfg(not(target_arch = "wasm32"))] impl EncodableKey for ElGamalSecretKey { fn read(reader: &mut R) -> Result> { let bytes: Vec = serde_json::from_reader(reader)?; @@ -517,6 +549,7 @@ impl EncodableKey for ElGamalSecretKey { } } +#[cfg(not(target_arch = "wasm32"))] impl SeedDerivable for ElGamalSecretKey { fn from_seed(seed: &[u8]) -> Result> { let key = Self::from_seed(seed)?; @@ -633,6 +666,7 @@ impl ElGamalCiphertext { /// /// The output of this function is of type `DiscreteLog`. To recover, the originally encrypted /// amount, use `DiscreteLog::decode`. + #[cfg(not(target_arch = "wasm32"))] pub fn decrypt(&self, secret: &ElGamalSecretKey) -> DiscreteLog { ElGamal::decrypt(secret, self) } @@ -642,6 +676,7 @@ impl ElGamalCiphertext { /// /// If the originally encrypted amount is not a positive 32-bit number, then the function /// returns `None`. + #[cfg(not(target_arch = "wasm32"))] pub fn decrypt_u32(&self, secret: &ElGamalSecretKey) -> Option { ElGamal::decrypt_u32(secret, self) } diff --git a/zk-sdk/src/encryption/grouped_elgamal.rs b/zk-sdk/src/encryption/grouped_elgamal.rs index b786d251973c38..e1eee744f98540 100644 --- a/zk-sdk/src/encryption/grouped_elgamal.rs +++ b/zk-sdk/src/encryption/grouped_elgamal.rs @@ -12,11 +12,12 @@ //! ElGamal ciphertext. //! +#[cfg(not(target_arch = "wasm32"))] +use crate::encryption::{discrete_log::DiscreteLog, elgamal::ElGamalSecretKey}; use { crate::{ encryption::{ - discrete_log::DiscreteLog, - elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalPubkey, ElGamalSecretKey}, + elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalPubkey}, pedersen::{Pedersen, PedersenCommitment, PedersenOpening}, }, RISTRETTO_POINT_LEN, @@ -92,7 +93,10 @@ impl GroupedElGamal { handle: *handle, }) } +} +#[cfg(not(target_arch = "wasm32"))] +impl GroupedElGamal { /// Decrypts a grouped ElGamal ciphertext using an ElGamal secret key pertaining to a /// decryption handle at a specified index. /// @@ -142,32 +146,6 @@ impl GroupedElGamalCiphertext { GroupedElGamal::to_elgamal_ciphertext(self, index) } - /// Decrypts the grouped ElGamal ciphertext using an ElGamal secret key pertaining to a - /// specified index. - /// - /// The output of this function is of type `DiscreteLog`. To recover the originally encrypted - /// amount, use `DiscreteLog::decode`. - pub fn decrypt( - &self, - secret: &ElGamalSecretKey, - index: usize, - ) -> Result { - GroupedElGamal::decrypt(self, secret, index) - } - - /// Decrypts the grouped ElGamal ciphertext to a number that is interpreted as a positive 32-bit - /// number (but still of type `u64`). - /// - /// If the originally encrypted amount is not a positive 32-bit number, then the function - /// returns `None`. - pub fn decrypt_u32( - &self, - secret: &ElGamalSecretKey, - index: usize, - ) -> Result, GroupedElGamalError> { - GroupedElGamal::decrypt_u32(self, secret, index) - } - /// The expected length of a serialized grouped ElGamal ciphertext. /// /// A grouped ElGamal ciphertext consists of a Pedersen commitment and an array of decryption @@ -209,6 +187,35 @@ impl GroupedElGamalCiphertext { } } +#[cfg(not(target_arch = "wasm32"))] +impl GroupedElGamalCiphertext { + /// Decrypts the grouped ElGamal ciphertext using an ElGamal secret key pertaining to a + /// specified index. + /// + /// The output of this function is of type `DiscreteLog`. To recover the originally encrypted + /// amount, use `DiscreteLog::decode`. + pub fn decrypt( + &self, + secret: &ElGamalSecretKey, + index: usize, + ) -> Result { + GroupedElGamal::decrypt(self, secret, index) + } + + /// Decrypts the grouped ElGamal ciphertext to a number that is interpreted as a positive 32-bit + /// number (but still of type `u64`). + /// + /// If the originally encrypted amount is not a positive 32-bit number, then the function + /// returns `None`. + pub fn decrypt_u32( + &self, + secret: &ElGamalSecretKey, + index: usize, + ) -> Result, GroupedElGamalError> { + GroupedElGamal::decrypt_u32(self, secret, index) + } +} + #[cfg(test)] mod tests { use {super::*, crate::encryption::elgamal::ElGamalKeypair}; diff --git a/zk-sdk/src/encryption/mod.rs b/zk-sdk/src/encryption/mod.rs index 8cad6217fc4c68..28a9ae6bf7fded 100644 --- a/zk-sdk/src/encryption/mod.rs +++ b/zk-sdk/src/encryption/mod.rs @@ -17,7 +17,7 @@ use crate::{RISTRETTO_POINT_LEN, SCALAR_LEN}; pub(crate) mod macros; #[cfg(not(target_os = "solana"))] pub mod auth_encryption; -#[cfg(not(target_os = "solana"))] +#[cfg(all(not(target_os = "solana"), not(target_arch = "wasm32")))] pub mod discrete_log; #[cfg(not(target_os = "solana"))] pub mod elgamal; diff --git a/zk-sdk/src/encryption/pod/elgamal.rs b/zk-sdk/src/encryption/pod/elgamal.rs index 6b30f27a127e3a..1dac45378b588c 100644 --- a/zk-sdk/src/encryption/pod/elgamal.rs +++ b/zk-sdk/src/encryption/pod/elgamal.rs @@ -17,6 +17,11 @@ use { bytemuck::Zeroable, std::fmt, }; +#[cfg(target_arch = "wasm32")] +use { + js_sys::{Array, Uint8Array}, + wasm_bindgen::prelude::*, +}; /// Maximum length of a base64 encoded ElGamal public key const ELGAMAL_PUBKEY_MAX_BASE64_LEN: usize = 44; @@ -80,8 +85,77 @@ impl TryFrom for ElGamalCiphertext { /// The `ElGamalPubkey` type as a `Pod`. #[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub struct PodElGamalPubkey(pub(crate) [u8; ELGAMAL_PUBKEY_LEN]); +#[cfg(target_arch = "wasm32")] +#[allow(non_snake_case)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +impl PodElGamalPubkey { + /// Create a new `PodElGamalPubkey` object + /// + /// * `value` - optional public key as a base64 encoded string, `Uint8Array`, `[number]` + #[wasm_bindgen(constructor)] + pub fn constructor(value: JsValue) -> Result { + if let Some(base64_str) = value.as_string() { + base64_str + .parse::() + .map_err(|e| e.to_string().into()) + } else if let Some(uint8_array) = value.dyn_ref::() { + bytemuck::try_from_bytes(&uint8_array.to_vec()) + .map_err(|err| JsValue::from(format!("Invalid Uint8Array ElGamalPubkey: {err:?}"))) + .map(|pubkey| *pubkey) + } else if let Some(array) = value.dyn_ref::() { + let mut bytes = vec![]; + let iterator = js_sys::try_iter(&array.values())?.expect("array to be iterable"); + for x in iterator { + let x = x?; + + if let Some(n) = x.as_f64() { + if (0. ..=255.).contains(&n) { + bytes.push(n as u8); + continue; + } + } + return Err(format!("Invalid array argument: {:?}", x).into()); + } + + bytemuck::try_from_bytes(&bytes) + .map_err(|err| JsValue::from(format!("Invalid Array pubkey: {err:?}"))) + .map(|pubkey| *pubkey) + } else if value.is_undefined() { + Ok(PodElGamalPubkey::default()) + } else { + Err("Unsupported argument".into()) + } + } + + /// Return the base64 string representation of the public key + pub fn toString(&self) -> String { + self.to_string() + } + + /// Checks if two `ElGamalPubkey`s are equal + pub fn equals(&self, other: &PodElGamalPubkey) -> bool { + self == other + } + + /// Return the `Uint8Array` representation of the public key + pub fn toBytes(&self) -> Box<[u8]> { + self.0.into() + } + + pub fn compressed(decoded: &ElGamalPubkey) -> PodElGamalPubkey { + (*decoded).into() + } + + pub fn decompressed(&self) -> Result { + (*self) + .try_into() + .map_err(|err| JsValue::from(format!("Invalid ElGamalPubkey: {err:?}"))) + } +} + impl fmt::Debug for PodElGamalPubkey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.0) diff --git a/zk-sdk/src/lib.rs b/zk-sdk/src/lib.rs index 824a4718f14c5c..8d7388475f2f36 100644 --- a/zk-sdk/src/lib.rs +++ b/zk-sdk/src/lib.rs @@ -25,6 +25,7 @@ pub mod pod; mod range_proof; mod sigma_proofs; mod transcript; +#[cfg(not(target_arch = "wasm32"))] pub mod zk_elgamal_proof_program; /// Byte length of a compressed Ristretto point or scalar in Curve255519