diff --git a/Cargo.lock b/Cargo.lock index 9b78bd1289b5b5..c45b35358823ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,6 +204,7 @@ dependencies = [ name = "agave-ledger-tool" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "assert_cmd", "bs58", "chrono", @@ -260,6 +261,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "agave-reserved-account-keys" +version = "2.3.0" +dependencies = [ + "lazy_static", + "solana-feature-set", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-message", + "solana-pubkey", + "solana-sdk-ids", + "solana-sysvar", +] + [[package]] name = "agave-store-histogram" version = "2.3.0" @@ -7178,6 +7193,7 @@ name = "solana-cli-output" version = "2.3.0" dependencies = [ "Inflector", + "agave-reserved-account-keys", "base64 0.22.1", "chrono", "clap 2.33.3", @@ -7203,7 +7219,6 @@ dependencies = [ "solana-packet", "solana-program", "solana-pubkey", - "solana-reserved-account-keys", "solana-rpc-client-api", "solana-sdk-ids", "solana-signature", @@ -7479,6 +7494,7 @@ name = "solana-core" version = "2.3.0" dependencies = [ "agave-banking-stage-ingress-types", + "agave-reserved-account-keys", "agave-transaction-view", "ahash 0.8.11", "anyhow", @@ -7589,6 +7605,7 @@ dependencies = [ name = "solana-cost-model" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "ahash 0.8.11", "itertools 0.12.1", "lazy_static", @@ -7615,7 +7632,6 @@ dependencies = [ "solana-metrics", "solana-packet", "solana-pubkey", - "solana-reserved-account-keys", "solana-runtime-transaction", "solana-sdk-ids", "solana-signature", @@ -7758,6 +7774,7 @@ dependencies = [ name = "solana-entry" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "assert_matches", "bincode", "crossbeam-channel", @@ -7778,7 +7795,6 @@ dependencies = [ "solana-perf", "solana-pubkey", "solana-rayon-threadlimit", - "solana-reserved-account-keys", "solana-runtime-transaction", "solana-sha256-hasher", "solana-signature", @@ -8397,6 +8413,7 @@ dependencies = [ name = "solana-ledger" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "anyhow", "assert_matches", "bincode", @@ -9385,6 +9402,7 @@ dependencies = [ name = "solana-rpc" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "base64 0.22.1", "bincode", "bs58", @@ -9611,6 +9629,7 @@ dependencies = [ name = "solana-runtime" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "agave-transaction-view", "ahash 0.8.11", "aquamarine", @@ -9710,6 +9729,7 @@ dependencies = [ name = "solana-runtime-transaction" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "agave-transaction-view", "bincode", "criterion", @@ -9725,7 +9745,6 @@ dependencies = [ "solana-message", "solana-program", "solana-pubkey", - "solana-reserved-account-keys", "solana-sdk-ids", "solana-signature", "solana-signer", @@ -10163,6 +10182,7 @@ dependencies = [ name = "solana-storage-bigtable" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "backoff", "bincode", "bytes", @@ -10187,7 +10207,6 @@ dependencies = [ "solana-message", "solana-metrics", "solana-pubkey", - "solana-reserved-account-keys", "solana-serde", "solana-signature", "solana-storage-proto", @@ -10280,6 +10299,7 @@ dependencies = [ name = "solana-svm" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "ahash 0.8.11", "assert_matches", "bincode", @@ -10327,7 +10347,6 @@ dependencies = [ "solana-pubkey", "solana-rent", "solana-rent-debits", - "solana-reserved-account-keys", "solana-sbpf", "solana-sdk", "solana-sdk-ids", @@ -10839,6 +10858,7 @@ name = "solana-transaction-status" version = "2.3.0" dependencies = [ "Inflector", + "agave-reserved-account-keys", "base64 0.22.1", "bincode", "borsh 1.5.6", @@ -10857,7 +10877,6 @@ dependencies = [ "solana-message", "solana-program", "solana-pubkey", - "solana-reserved-account-keys", "solana-reward-info", "solana-sdk-ids", "solana-signature", diff --git a/Cargo.toml b/Cargo.toml index 29825782bc03ee..2041e3700618ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,6 +108,7 @@ members = [ "rayon-threadlimit", "rbpf-cli", "remote-wallet", + "reserved-account-keys", "rpc", "rpc-client", "rpc-client-api", @@ -181,6 +182,7 @@ Inflector = "0.11.4" axum = "0.7.9" agave-banking-stage-ingress-types = { path = "banking-stage-ingress-types", version = "=2.3.0" } agave-feature-set = { path = "feature-set", version = "=2.3.0" } +agave-reserved-account-keys = { path = "reserved-account-keys", version = "=2.3.0" } agave-transaction-view = { path = "transaction-view", version = "=2.3.0" } aquamarine = "0.6.0" aes-gcm-siv = "0.11.1" @@ -493,7 +495,6 @@ solana-remote-wallet = { path = "remote-wallet", version = "=2.3.0", default-fea solana-rent = "=2.2.1" solana-rent-collector = "=2.2.1" solana-rent-debits = "=2.2.1" -solana-reserved-account-keys = "=2.2.1" solana-reward-info = "=2.2.1" solana-sanitize = "=2.2.1" solana-secp256r1-program = "=2.2.1" diff --git a/cli-output/Cargo.toml b/cli-output/Cargo.toml index 95cc7767c722ee..3cc8cd9bc270eb 100644 --- a/cli-output/Cargo.toml +++ b/cli-output/Cargo.toml @@ -11,6 +11,7 @@ edition = { workspace = true } [dependencies] Inflector = { workspace = true } +agave-reserved-account-keys = { workspace = true } base64 = { workspace = true } chrono = { workspace = true, features = ["default", "serde"] } clap = "2.33.0" @@ -34,7 +35,6 @@ solana-native-token = { workspace = true } solana-packet = { workspace = true } solana-program = { workspace = true } solana-pubkey = { workspace = true } -solana-reserved-account-keys = { workspace = true } solana-rpc-client-api = { workspace = true } solana-sdk-ids = { workspace = true } solana-signature = { workspace = true } diff --git a/cli-output/src/display.rs b/cli-output/src/display.rs index 9a31d11692beb6..94c8f239172351 100644 --- a/cli-output/src/display.rs +++ b/cli-output/src/display.rs @@ -1,5 +1,6 @@ use { crate::cli_output::CliSignatureVerificationStatus, + agave_reserved_account_keys::ReservedAccountKeys, base64::{prelude::BASE64_STANDARD, Engine}, chrono::{DateTime, Local, SecondsFormat, TimeZone, Utc}, console::style, @@ -12,7 +13,6 @@ use { solana_native_token::lamports_to_sol, solana_program::stake, solana_pubkey::Pubkey, - solana_reserved_account_keys::ReservedAccountKeys, solana_signature::Signature, solana_transaction::versioned::{TransactionVersion, VersionedTransaction}, solana_transaction_error::TransactionError, diff --git a/core/Cargo.toml b/core/Cargo.toml index e7d05aa3d965fd..b1fc87d0ef8911 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -108,6 +108,7 @@ tokio = { workspace = true, features = ["full"] } trees = { workspace = true } [dev-dependencies] +agave-reserved-account-keys = { workspace = true } criterion = { workspace = true } fs_extra = { workspace = true } serde_json = { workspace = true } diff --git a/core/src/banking_stage/consumer.rs b/core/src/banking_stage/consumer.rs index a4034c5ce2be7e..322bd78ddb258b 100644 --- a/core/src/banking_stage/consumer.rs +++ b/core/src/banking_stage/consumer.rs @@ -858,6 +858,7 @@ mod tests { crate::banking_stage::tests::{ create_slow_genesis_config, sanitize_transactions, simulate_poh, }, + agave_reserved_account_keys::ReservedAccountKeys, crossbeam_channel::{unbounded, Receiver}, solana_cost_model::{cost_model::CostModel, transaction_cost::TransactionCost}, solana_entry::entry::{next_entry, next_versioned_entry}, @@ -890,7 +891,6 @@ mod tests { nonce_account::verify_nonce_account, poh_config::PohConfig, pubkey::Pubkey, - reserved_account_keys::ReservedAccountKeys, signature::Keypair, signer::Signer, system_program, system_transaction, diff --git a/core/src/banking_stage/unprocessed_packet_batches.rs b/core/src/banking_stage/unprocessed_packet_batches.rs index 28418cfdcd26d3..c82bfa9c1a9d90 100644 --- a/core/src/banking_stage/unprocessed_packet_batches.rs +++ b/core/src/banking_stage/unprocessed_packet_batches.rs @@ -50,11 +50,11 @@ impl Ord for DeserializedPacket { mod tests { use { super::*, + agave_reserved_account_keys::ReservedAccountKeys, solana_perf::packet::PacketFlags, solana_runtime::bank::Bank, solana_sdk::{ hash::Hash, - reserved_account_keys::ReservedAccountKeys, signature::{Keypair, Signer}, system_transaction, transaction::Transaction, diff --git a/cost-model/Cargo.toml b/cost-model/Cargo.toml index 8ee33b590f664d..79ad93d34821a5 100644 --- a/cost-model/Cargo.toml +++ b/cost-model/Cargo.toml @@ -48,6 +48,7 @@ crate-type = ["lib"] name = "solana_cost_model" [dev-dependencies] +agave-reserved-account-keys = { workspace = true } itertools = { workspace = true } rand = "0.8.5" # See order-crates-for-publishing.py for using this unusual `path = "."` @@ -61,7 +62,6 @@ solana-instruction = { workspace = true } solana-keypair = { workspace = true } solana-logger = { workspace = true } solana-pubkey = { workspace = true, features = ["rand"] } -solana-reserved-account-keys = { workspace = true } solana-runtime-transaction = { workspace = true, features = [ "dev-context-only-utils", ] } diff --git a/cost-model/src/transaction_cost.rs b/cost-model/src/transaction_cost.rs index 85e4efb8af3cff..f2e2a6c16e049e 100644 --- a/cost-model/src/transaction_cost.rs +++ b/cost-model/src/transaction_cost.rs @@ -295,11 +295,11 @@ mod tests { use { super::*, crate::cost_model::CostModel, + agave_reserved_account_keys::ReservedAccountKeys, solana_feature_set::FeatureSet, solana_hash::Hash, solana_keypair::Keypair, solana_message::SimpleAddressLoader, - solana_reserved_account_keys::ReservedAccountKeys, solana_runtime_transaction::runtime_transaction::RuntimeTransaction, solana_transaction::{sanitized::MessageHash, versioned::VersionedTransaction}, solana_vote::vote_transaction, diff --git a/entry/Cargo.toml b/entry/Cargo.toml index 26d9f11ea8d01b..4ea3d9e58e5236 100644 --- a/entry/Cargo.toml +++ b/entry/Cargo.toml @@ -31,13 +31,13 @@ solana-transaction = { workspace = true } solana-transaction-error = { workspace = true } [dev-dependencies] +agave-reserved-account-keys = { workspace = true } assert_matches = { workspace = true } solana-keypair = { workspace = true } solana-logger = { workspace = true } solana-message = { workspace = true } solana-perf = { workspace = true, features = ["dev-context-only-utils"] } solana-pubkey = { workspace = true } -solana-reserved-account-keys = { workspace = true } solana-signature = { workspace = true } solana-signer = { workspace = true } solana-system-transaction = { workspace = true } diff --git a/entry/benches/entry_sigverify.rs b/entry/benches/entry_sigverify.rs index a4488af586f71e..12b30e23200e11 100644 --- a/entry/benches/entry_sigverify.rs +++ b/entry/benches/entry_sigverify.rs @@ -1,11 +1,11 @@ #![feature(test)] extern crate test; use { + agave_reserved_account_keys::ReservedAccountKeys, solana_entry::entry::{self, VerifyRecyclers}, solana_hash::Hash, solana_message::SimpleAddressLoader, solana_perf::test_tx::test_tx, - solana_reserved_account_keys::ReservedAccountKeys, solana_runtime_transaction::runtime_transaction::RuntimeTransaction, solana_transaction::{ sanitized::{MessageHash, SanitizedTransaction}, diff --git a/entry/src/entry.rs b/entry/src/entry.rs index 6877e73dcb7f92..dcb60f140ccf2f 100644 --- a/entry/src/entry.rs +++ b/entry/src/entry.rs @@ -974,12 +974,12 @@ pub fn thread_pool_for_benches() -> ThreadPool { mod tests { use { super::*, + agave_reserved_account_keys::ReservedAccountKeys, solana_hash::Hash, solana_keypair::Keypair, solana_message::SimpleAddressLoader, solana_perf::test_tx::{test_invalid_tx, test_tx}, solana_pubkey::Pubkey, - solana_reserved_account_keys::ReservedAccountKeys, solana_runtime_transaction::runtime_transaction::RuntimeTransaction, solana_sha256_hasher::hash, solana_signer::Signer, diff --git a/ledger-tool/Cargo.toml b/ledger-tool/Cargo.toml index 38e1cc97b84b58..ca8fbb6d70a19e 100644 --- a/ledger-tool/Cargo.toml +++ b/ledger-tool/Cargo.toml @@ -10,6 +10,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +agave-reserved-account-keys = { workspace = true } bs58 = { workspace = true } chrono = { workspace = true, features = ["default"] } clap = { workspace = true } diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index ba3aaa88781c6c..04f8a64bbf4640 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -12,6 +12,7 @@ use { }, program::*, }, + agave_reserved_account_keys::ReservedAccountKeys, clap::{ crate_description, crate_name, value_t, value_t_or_exit, values_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand, @@ -74,7 +75,6 @@ use { native_token::{lamports_to_sol, sol_to_lamports, Sol}, pubkey::Pubkey, rent::Rent, - reserved_account_keys::ReservedAccountKeys, shred_version::compute_shred_version, stake::{self, state::StakeStateV2}, system_program, diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index 71d191c20be66e..5e54b870662c2b 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -10,6 +10,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +agave-reserved-account-keys = { workspace = true } anyhow = { workspace = true } assert_matches = { workspace = true } bincode = { workspace = true } diff --git a/ledger/src/transaction_address_lookup_table_scanner.rs b/ledger/src/transaction_address_lookup_table_scanner.rs index ec0e22059ac9be..12465357cf51ac 100644 --- a/ledger/src/transaction_address_lookup_table_scanner.rs +++ b/ledger/src/transaction_address_lookup_table_scanner.rs @@ -1,10 +1,10 @@ use { + agave_reserved_account_keys::ReservedAccountKeys, bincode::deserialize, lazy_static::lazy_static, solana_sdk::{ address_lookup_table::{self, instruction::ProgramInstruction}, pubkey::Pubkey, - reserved_account_keys::ReservedAccountKeys, transaction::SanitizedVersionedTransaction, }, std::collections::HashSet, diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index bf125feb23b2d9..f545fddcc1391a 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -83,6 +83,16 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "agave-reserved-account-keys" +version = "2.3.0" +dependencies = [ + "lazy_static", + "solana-feature-set", + "solana-pubkey", + "solana-sdk-ids", +] + [[package]] name = "agave-transaction-view" version = "2.3.0" @@ -5647,6 +5657,7 @@ name = "solana-cli-output" version = "2.3.0" dependencies = [ "Inflector", + "agave-reserved-account-keys", "base64 0.22.1", "chrono", "clap", @@ -5670,7 +5681,6 @@ dependencies = [ "solana-packet", "solana-program", "solana-pubkey", - "solana-reserved-account-keys", "solana-rpc-client-api", "solana-sdk-ids", "solana-signature", @@ -6548,6 +6558,7 @@ dependencies = [ name = "solana-ledger" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "anyhow", "assert_matches", "bincode", @@ -7516,6 +7527,7 @@ dependencies = [ name = "solana-runtime" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "ahash 0.8.11", "aquamarine", "arrayref", @@ -7626,6 +7638,7 @@ checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf" name = "solana-sbf-programs" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "agave-validator", "bincode", "borsh 1.5.6", @@ -8495,6 +8508,7 @@ dependencies = [ name = "solana-storage-bigtable" version = "2.3.0" dependencies = [ + "agave-reserved-account-keys", "backoff", "bincode", "bytes", @@ -8517,7 +8531,6 @@ dependencies = [ "solana-message", "solana-metrics", "solana-pubkey", - "solana-reserved-account-keys", "solana-serde", "solana-signature", "solana-storage-proto", @@ -8994,6 +9007,7 @@ name = "solana-transaction-status" version = "2.3.0" dependencies = [ "Inflector", + "agave-reserved-account-keys", "base64 0.22.1", "bincode", "borsh 1.5.6", @@ -9011,7 +9025,6 @@ dependencies = [ "solana-message", "solana-program", "solana-pubkey", - "solana-reserved-account-keys", "solana-reward-info", "solana-sdk-ids", "solana-signature", diff --git a/programs/sbf/Cargo.toml b/programs/sbf/Cargo.toml index 868227cd76952b..022bd55ee53ff8 100644 --- a/programs/sbf/Cargo.toml +++ b/programs/sbf/Cargo.toml @@ -15,6 +15,7 @@ check-cfg = [ ] [workspace.dependencies] +agave-reserved-account-keys = { path = "../../reserved-account-keys", version = "=2.3.0" } array-bytes = "=1.4.1" bincode = { version = "1.1.4", default-features = false } blake3 = "1.0.0" @@ -103,6 +104,7 @@ dummy-for-ci-check = ["sbf_c", "sbf_rust", "sbf_sanity_list"] frozen-abi = [] [dev-dependencies] +agave-reserved-account-keys = { workspace = true } agave-validator = { workspace = true } bincode = { workspace = true } borsh = { workspace = true } diff --git a/programs/sbf/tests/programs.rs b/programs/sbf/tests/programs.rs index fe5a3f5ffb0f82..1f1e167860da09 100644 --- a/programs/sbf/tests/programs.rs +++ b/programs/sbf/tests/programs.rs @@ -9,6 +9,7 @@ #[cfg(feature = "sbf_rust")] use { + agave_reserved_account_keys::ReservedAccountKeys, borsh::{from_slice, to_vec, BorshDeserialize, BorshSerialize}, solana_compute_budget::compute_budget::ComputeBudget, solana_compute_budget_instruction::instructions_processor::process_compute_budget_instructions, @@ -47,7 +48,6 @@ use { message::{Message, SanitizedMessage}, pubkey::Pubkey, rent::Rent, - reserved_account_keys::ReservedAccountKeys, signature::{Keypair, Signer}, stake, system_instruction::MAX_PERMITTED_DATA_LENGTH, diff --git a/reserved-account-keys/Cargo.toml b/reserved-account-keys/Cargo.toml new file mode 100644 index 00000000000000..94fe6c05c67dea --- /dev/null +++ b/reserved-account-keys/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "agave-reserved-account-keys" +description = "Reserved Solana account keys" +documentation = "https://docs.rs/agave-reserved-account-keys" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +lazy_static = { workspace = true } +solana-feature-set = { workspace = true } +solana-frozen-abi = { workspace = true, optional = true, features = [ + "frozen-abi", +] } +solana-frozen-abi-macro = { workspace = true, optional = true, features = [ + "frozen-abi", +] } +solana-pubkey = { workspace = true, default-features = false } +solana-sdk-ids = { workspace = true } + +[dev-dependencies] +solana-message = { workspace = true } +solana-sysvar = { workspace = true } + +[features] +frozen-abi = ["dep:solana-frozen-abi", "dep:solana-frozen-abi-macro"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] +all-features = true +rustdoc-args = ["--cfg=docsrs"] + +[lints] +workspace = true diff --git a/reserved-account-keys/src/lib.rs b/reserved-account-keys/src/lib.rs new file mode 100644 index 00000000000000..06cad17e20c9ae --- /dev/null +++ b/reserved-account-keys/src/lib.rs @@ -0,0 +1,260 @@ +//! Collection of reserved account keys that cannot be write-locked by transactions. +//! New reserved account keys may be added as long as they specify a feature +//! gate that transitions the key into read-only at an epoch boundary. +#![cfg_attr(feature = "frozen-abi", feature(min_specialization))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +use { + lazy_static::lazy_static, + solana_feature_set::{self as feature_set, FeatureSet}, + solana_pubkey::Pubkey, + solana_sdk_ids::{ + address_lookup_table, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, + compute_budget, config, ed25519_program, feature, loader_v4, native_loader, + secp256k1_program, secp256r1_program, stake, system_program, sysvar, vote, + zk_elgamal_proof_program, zk_token_proof_program, + }, + std::collections::{HashMap, HashSet}, +}; + +// ReservedAccountKeys is not serialized into or deserialized from bank +// snapshots but the bank requires this trait to be implemented anyways. +#[cfg(feature = "frozen-abi")] +impl ::solana_frozen_abi::abi_example::AbiExample for ReservedAccountKeys { + fn example() -> Self { + // ReservedAccountKeys is not Serialize so just rely on Default. + ReservedAccountKeys::default() + } +} + +/// `ReservedAccountKeys` holds the set of currently active/inactive +/// account keys that are reserved by the protocol and may not be write-locked +/// during transaction processing. +#[derive(Debug, Clone, PartialEq)] +pub struct ReservedAccountKeys { + /// Set of currently active reserved account keys + pub active: HashSet, + /// Set of currently inactive reserved account keys that will be moved to the + /// active set when their feature id is activated + inactive: HashMap, +} + +impl Default for ReservedAccountKeys { + fn default() -> Self { + Self::new(&RESERVED_ACCOUNTS) + } +} + +impl ReservedAccountKeys { + /// Compute a set of active / inactive reserved account keys from a list of + /// keys with a designated feature id. If a reserved account key doesn't + /// designate a feature id, it's already activated and should be inserted + /// into the active set. If it does have a feature id, insert the key and + /// its feature id into the inactive map. + fn new(reserved_accounts: &[ReservedAccount]) -> Self { + Self { + active: reserved_accounts + .iter() + .filter(|reserved| reserved.feature_id.is_none()) + .map(|reserved| reserved.key) + .collect(), + inactive: reserved_accounts + .iter() + .filter_map(|ReservedAccount { key, feature_id }| { + feature_id.as_ref().map(|feature_id| (*key, *feature_id)) + }) + .collect(), + } + } + + /// Compute a set with all reserved keys active, regardless of whether their + /// feature was activated. This is not to be used by the runtime. Useful for + /// off-chain utilities that need to filter out reserved accounts. + pub fn new_all_activated() -> Self { + Self { + active: Self::all_keys_iter().copied().collect(), + inactive: HashMap::default(), + } + } + + /// Returns whether the specified key is reserved + pub fn is_reserved(&self, key: &Pubkey) -> bool { + self.active.contains(key) + } + + /// Move inactive reserved account keys to the active set if their feature + /// is active. + pub fn update_active_set(&mut self, feature_set: &FeatureSet) { + self.inactive.retain(|reserved_key, feature_id| { + if feature_set.is_active(feature_id) { + self.active.insert(*reserved_key); + false + } else { + true + } + }); + } + + /// Return an iterator over all active / inactive reserved keys. This is not + /// to be used by the runtime. Useful for off-chain utilities that need to + /// filter out reserved accounts. + pub fn all_keys_iter() -> impl Iterator { + RESERVED_ACCOUNTS + .iter() + .map(|reserved_key| &reserved_key.key) + } + + /// Return an empty set of reserved keys for visibility when using in + /// tests where the dynamic reserved key set is not available + pub fn empty_key_set() -> HashSet { + HashSet::default() + } +} + +/// `ReservedAccount` represents a reserved account that will not be +/// write-lockable by transactions. If a feature id is set, the account will +/// become read-only only after the feature has been activated. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +struct ReservedAccount { + key: Pubkey, + feature_id: Option, +} + +impl ReservedAccount { + fn new_pending(key: Pubkey, feature_id: Pubkey) -> Self { + Self { + key, + feature_id: Some(feature_id), + } + } + + fn new_active(key: Pubkey) -> Self { + Self { + key, + feature_id: None, + } + } +} + +// New reserved accounts should be added in alphabetical order and must specify +// a feature id for activation. Reserved accounts cannot be removed from this +// list without breaking consensus. +lazy_static! { + static ref RESERVED_ACCOUNTS: Vec = [ + // builtin programs + ReservedAccount::new_pending(address_lookup_table::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_active(bpf_loader::id()), + ReservedAccount::new_active(bpf_loader_deprecated::id()), + ReservedAccount::new_active(bpf_loader_upgradeable::id()), + ReservedAccount::new_pending(compute_budget::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_active(config::id()), + ReservedAccount::new_pending(ed25519_program::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_active(feature::id()), + ReservedAccount::new_pending(loader_v4::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_pending(secp256k1_program::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_pending(secp256r1_program::id(), feature_set::enable_secp256r1_precompile::id()), + #[allow(deprecated)] + ReservedAccount::new_active(stake::config::id()), + ReservedAccount::new_active(stake::id()), + ReservedAccount::new_active(system_program::id()), + ReservedAccount::new_active(vote::id()), + ReservedAccount::new_pending(zk_elgamal_proof_program::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_pending(zk_token_proof_program::id(), feature_set::add_new_reserved_account_keys::id()), + + // sysvars + ReservedAccount::new_active(sysvar::clock::id()), + ReservedAccount::new_pending(sysvar::epoch_rewards::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_active(sysvar::epoch_schedule::id()), + #[allow(deprecated)] + ReservedAccount::new_active(sysvar::fees::id()), + ReservedAccount::new_active(sysvar::instructions::id()), + ReservedAccount::new_pending(sysvar::last_restart_slot::id(), feature_set::add_new_reserved_account_keys::id()), + #[allow(deprecated)] + ReservedAccount::new_active(sysvar::recent_blockhashes::id()), + ReservedAccount::new_active(sysvar::rent::id()), + ReservedAccount::new_active(sysvar::rewards::id()), + ReservedAccount::new_active(sysvar::slot_hashes::id()), + ReservedAccount::new_active(sysvar::slot_history::id()), + ReservedAccount::new_active(sysvar::stake_history::id()), + + // other + ReservedAccount::new_active(native_loader::id()), + ReservedAccount::new_pending(sysvar::id(), feature_set::add_new_reserved_account_keys::id()), + ].to_vec(); +} + +#[cfg(test)] +mod tests { + #![allow(deprecated)] + use {super::*, solana_message::legacy::BUILTIN_PROGRAMS_KEYS, solana_sysvar::ALL_IDS}; + + #[test] + fn test_is_reserved() { + let feature_id = Pubkey::new_unique(); + let active_reserved_account = ReservedAccount::new_active(Pubkey::new_unique()); + let pending_reserved_account = + ReservedAccount::new_pending(Pubkey::new_unique(), feature_id); + let reserved_account_keys = + ReservedAccountKeys::new(&[active_reserved_account, pending_reserved_account]); + + assert!( + reserved_account_keys.is_reserved(&active_reserved_account.key), + "active reserved accounts should be inserted into the active set" + ); + assert!( + !reserved_account_keys.is_reserved(&pending_reserved_account.key), + "pending reserved accounts should NOT be inserted into the active set" + ); + } + + #[test] + fn test_update_active_set() { + let feature_ids = [Pubkey::new_unique(), Pubkey::new_unique()]; + let active_reserved_key = Pubkey::new_unique(); + let pending_reserved_keys = [Pubkey::new_unique(), Pubkey::new_unique()]; + let reserved_accounts = vec![ + ReservedAccount::new_active(active_reserved_key), + ReservedAccount::new_pending(pending_reserved_keys[0], feature_ids[0]), + ReservedAccount::new_pending(pending_reserved_keys[1], feature_ids[1]), + ]; + + let mut reserved_account_keys = ReservedAccountKeys::new(&reserved_accounts); + assert!(reserved_account_keys.is_reserved(&active_reserved_key)); + assert!(!reserved_account_keys.is_reserved(&pending_reserved_keys[0])); + assert!(!reserved_account_keys.is_reserved(&pending_reserved_keys[1])); + + // Updating the active set with a default feature set should be a no-op + let previous_reserved_account_keys = reserved_account_keys.clone(); + let mut feature_set = FeatureSet::default(); + reserved_account_keys.update_active_set(&feature_set); + assert_eq!(reserved_account_keys, previous_reserved_account_keys); + + // Updating the active set with an activated feature should also activate + // the corresponding reserved key from inactive to active + feature_set.active.insert(feature_ids[0], 0); + reserved_account_keys.update_active_set(&feature_set); + + assert!(reserved_account_keys.is_reserved(&active_reserved_key)); + assert!(reserved_account_keys.is_reserved(&pending_reserved_keys[0])); + assert!(!reserved_account_keys.is_reserved(&pending_reserved_keys[1])); + + // Update the active set again to ensure that the inactive map is + // properly retained + feature_set.active.insert(feature_ids[1], 0); + reserved_account_keys.update_active_set(&feature_set); + + assert!(reserved_account_keys.is_reserved(&active_reserved_key)); + assert!(reserved_account_keys.is_reserved(&pending_reserved_keys[0])); + assert!(reserved_account_keys.is_reserved(&pending_reserved_keys[1])); + } + + #[test] + fn test_static_list_compat() { + let mut static_set = HashSet::new(); + static_set.extend(ALL_IDS.iter().cloned()); + static_set.extend(BUILTIN_PROGRAMS_KEYS.iter().cloned()); + + let initial_active_set = ReservedAccountKeys::default().active; + + assert_eq!(initial_active_set, static_set); + } +} diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 69b5c929c9df4a..e5fab83dc9e2fd 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -67,6 +67,7 @@ tokio = { workspace = true, features = ["full"] } tokio-util = { workspace = true, features = ["codec", "compat"] } [dev-dependencies] +agave-reserved-account-keys = { workspace = true } serial_test = { workspace = true } solana-net-utils = { workspace = true } solana-rpc = { path = ".", features = ["dev-context-only-utils"] } diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index d6af683ce9892b..7171f06cce97cb 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -4507,6 +4507,7 @@ pub mod tests { rpc_service::service_runtime, rpc_subscriptions::RpcSubscriptions, }, + agave_reserved_account_keys::ReservedAccountKeys, bincode::deserialize, jsonrpc_core::{futures, ErrorCode, MetaIoHandler, Output, Response, Value}, jsonrpc_core_client::transports::local, @@ -4548,7 +4549,6 @@ pub mod tests { Message, MessageHeader, VersionedMessage, }, nonce::{self, state::DurableNonce}, - reserved_account_keys::ReservedAccountKeys, rpc_port, signature::{Keypair, Signer}, slot_hashes::SlotHashes, diff --git a/rpc/src/transaction_status_service.rs b/rpc/src/transaction_status_service.rs index b28f3b8c1a4290..472eeb697542ce 100644 --- a/rpc/src/transaction_status_service.rs +++ b/rpc/src/transaction_status_service.rs @@ -261,6 +261,7 @@ pub(crate) mod tests { use { super::*, crate::transaction_notifier_interface::TransactionNotifier, + agave_reserved_account_keys::ReservedAccountKeys, crossbeam_channel::unbounded, dashmap::DashMap, solana_account_decoder::{ @@ -277,7 +278,6 @@ pub(crate) mod tests { nonce_account, pubkey::Pubkey, rent_debits::RentDebits, - reserved_account_keys::ReservedAccountKeys, signature::{Keypair, Signature, Signer}, system_transaction, transaction::{ diff --git a/runtime-transaction/Cargo.toml b/runtime-transaction/Cargo.toml index 438f53dce4f4b6..5dc106db778776 100644 --- a/runtime-transaction/Cargo.toml +++ b/runtime-transaction/Cargo.toml @@ -29,6 +29,7 @@ crate-type = ["lib"] name = "solana_runtime_transaction" [dev-dependencies] +agave-reserved-account-keys = { workspace = true } bincode = { workspace = true } criterion = { workspace = true } rand = { workspace = true } @@ -39,7 +40,6 @@ solana-instruction = { workspace = true } solana-keypair = { workspace = true } solana-program = { workspace = true } solana-pubkey = { workspace = true, features = ["rand"] } -solana-reserved-account-keys = { workspace = true } solana-signer = { workspace = true } solana-system-interface = { workspace = true, features = ["bincode"] } solana-system-transaction = { workspace = true } diff --git a/runtime-transaction/src/runtime_transaction/sdk_transactions.rs b/runtime-transaction/src/runtime_transaction/sdk_transactions.rs index 61167b7c253713..89c33c31dda755 100644 --- a/runtime-transaction/src/runtime_transaction/sdk_transactions.rs +++ b/runtime-transaction/src/runtime_transaction/sdk_transactions.rs @@ -153,6 +153,7 @@ impl RuntimeTransaction { mod tests { use { super::*, + agave_reserved_account_keys::ReservedAccountKeys, solana_compute_budget_interface::ComputeBudgetInstruction, solana_feature_set::FeatureSet, solana_hash::Hash, @@ -160,7 +161,6 @@ mod tests { solana_keypair::Keypair, solana_message::{Message, SimpleAddressLoader}, solana_program::vote::{self, state::Vote}, - solana_reserved_account_keys::ReservedAccountKeys, solana_signer::Signer, solana_system_interface::instruction as system_instruction, solana_transaction::{versioned::VersionedTransaction, Transaction}, diff --git a/runtime-transaction/src/runtime_transaction/transaction_view.rs b/runtime-transaction/src/runtime_transaction/transaction_view.rs index 0a09fc2c310cae..fb23922d3d79e0 100644 --- a/runtime-transaction/src/runtime_transaction/transaction_view.rs +++ b/runtime-transaction/src/runtime_transaction/transaction_view.rs @@ -192,10 +192,10 @@ impl TransactionWithMeta for RuntimeTransaction Vec { @@ -40,9 +40,9 @@ pub fn parse_v0_message_accounts(message: &LoadedMessage) -> Vec mod test { use { super::*, + agave_reserved_account_keys::ReservedAccountKeys, solana_message::{v0, v0::LoadedAddresses, MessageHeader}, solana_pubkey::Pubkey, - solana_reserved_account_keys::ReservedAccountKeys, }; #[test]