diff --git a/Cargo.lock b/Cargo.lock index cdc64bbca..e40d2419e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2710,7 +2710,7 @@ dependencies = [ [[package]] name = "solana-account" -version = "2.2.1" +version = "3.0.0" dependencies = [ "bincode", "qualifier_attr", @@ -2775,7 +2775,7 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-interface" -version = "2.2.2" +version = "3.0.0" dependencies = [ "bincode", "bytemuck", @@ -3027,7 +3027,7 @@ dependencies = [ [[package]] name = "solana-epoch-schedule" -version = "2.2.1" +version = "3.0.0" dependencies = [ "serde", "serde_derive", @@ -3090,7 +3090,7 @@ dependencies = [ [[package]] name = "solana-fee-calculator" -version = "2.2.1" +version = "3.0.0" dependencies = [ "log", "serde", @@ -3251,7 +3251,7 @@ dependencies = [ [[package]] name = "solana-instructions-sysvar" -version = "2.2.2" +version = "3.0.0" dependencies = [ "bitflags 2.8.0", "qualifier_attr", @@ -3408,7 +3408,7 @@ version = "3.0.0" [[package]] name = "solana-nonce" -version = "2.2.1" +version = "3.0.0" dependencies = [ "bincode", "serde", @@ -3434,7 +3434,7 @@ dependencies = [ [[package]] name = "solana-offchain-message" -version = "2.2.1" +version = "3.0.0" dependencies = [ "num_enum", "solana-hash", @@ -3485,7 +3485,7 @@ dependencies = [ [[package]] name = "solana-poh-config" -version = "2.2.1" +version = "3.0.0" dependencies = [ "serde", "serde_derive", @@ -3504,7 +3504,7 @@ dependencies = [ [[package]] name = "solana-presigner" -version = "2.2.1" +version = "3.0.0" dependencies = [ "solana-keypair", "solana-pubkey", @@ -3609,14 +3609,14 @@ dependencies = [ [[package]] name = "solana-quic-definitions" -version = "2.3.1" +version = "3.0.0" dependencies = [ "solana-keypair", ] [[package]] name = "solana-rent" -version = "2.2.1" +version = "3.0.0" dependencies = [ "serde", "serde_derive", @@ -3751,7 +3751,7 @@ dependencies = [ [[package]] name = "solana-secp256k1-program" -version = "2.2.3" +version = "3.0.0" dependencies = [ "anyhow", "bincode", @@ -3955,7 +3955,7 @@ dependencies = [ [[package]] name = "solana-system-interface" -version = "1.0.0" +version = "2.0.0" dependencies = [ "anyhow", "borsh", @@ -3997,7 +3997,7 @@ dependencies = [ [[package]] name = "solana-sysvar" -version = "2.2.2" +version = "3.0.0" dependencies = [ "anyhow", "base64 0.22.1", @@ -4104,12 +4104,14 @@ version = "2.2.5" dependencies = [ "arbitrary", "bincode", + "cfg_eval", "itertools 0.12.1", "num-derive", "num-traits", "rand", "serde", "serde_derive", + "serde_with", "solana-clock", "solana-epoch-schedule", "solana-frozen-abi", diff --git a/Cargo.toml b/Cargo.toml index 87c4e3d9a..e8c0b2e52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -206,10 +206,10 @@ sha2 = "0.10.8" sha3 = "0.10.8" signal-hook = "0.3.17" siphasher = "0.3.11" -solana-account = { path = "account", version = "2.2.1" } +solana-account = { path = "account", version = "3.0.0" } solana-account-info = { path = "account-info", version = "3.0.0" } solana-address = { path = "address", version = "1.0.0" } -solana-address-lookup-table-interface = { path = "address-lookup-table-interface", version = "2.2.2" } +solana-address-lookup-table-interface = { path = "address-lookup-table-interface", version = "3.0.0" } solana-atomic-u64 = { path = "atomic-u64", version = "3.0.0" } solana-big-mod-exp = { path = "big-mod-exp", version = "3.0.0" } solana-bincode = { path = "bincode", version = "3.0.0" } @@ -229,11 +229,11 @@ solana-ed25519-program = { path = "ed25519-program", version = "3.0.0" } solana-epoch-info = { path = "epoch-info", version = "3.0.0" } solana-epoch-rewards = { path = "epoch-rewards", version = "3.0.0" } solana-epoch-rewards-hasher = { path = "epoch-rewards-hasher", version = "3.0.0" } -solana-epoch-schedule = { path = "epoch-schedule", version = "2.2.1" } +solana-epoch-schedule = { path = "epoch-schedule", version = "3.0.0" } solana-epoch-stake = { path = "epoch-stake", version = "3.0.0" } solana-example-mocks = { path = "example-mocks", version = "2.2.1" } solana-feature-gate-interface = { path = "feature-gate-interface", version = "2.2.1" } -solana-fee-calculator = { path = "fee-calculator", version = "2.2.1" } +solana-fee-calculator = { path = "fee-calculator", version = "3.0.0" } solana-fee-structure = { path = "fee-structure", version = "3.0.0" } solana-file-download = { path = "file-download", version = "3.0.0" } solana-frozen-abi = { path = "frozen-abi", version = "3.0.0" } @@ -244,7 +244,7 @@ solana-hash = { path = "hash", version = "3.0.0", default-features = false } solana-inflation = { path = "inflation", version = "3.0.0" } solana-instruction = { path = "instruction", version = "3.0.0", default-features = false } solana-instruction-error = { path = "instruction-error", version = "2.0.0" } -solana-instructions-sysvar = { path = "instructions-sysvar", version = "2.2.1" } +solana-instructions-sysvar = { path = "instructions-sysvar", version = "3.0.0" } solana-keccak-hasher = { path = "keccak-hasher", version = "3.0.0" } solana-keypair = { path = "keypair", version = "3.0.0" } solana-last-restart-slot = { path = "last-restart-slot", version = "3.0.0" } @@ -255,15 +255,15 @@ solana-logger = { path = "logger", version = "3.0.0" } solana-message = { path = "message", version = "2.2.1" } solana-msg = { path = "msg", version = "3.0.0" } solana-native-token = { path = "native-token", version = "3.0.0" } -solana-nonce = { path = "nonce", version = "2.2.1" } +solana-nonce = { path = "nonce", version = "3.0.0" } solana-nonce-account = { path = "nonce-account", version = "2.2.1" } -solana-offchain-message = { path = "offchain-message", version = "2.2.1" } +solana-offchain-message = { path = "offchain-message", version = "3.0.0" } solana-package-metadata = { path = "package-metadata", version = "3.0.0" } solana-package-metadata-macro = { path = "package-metadata-macro", version = "3.0.0" } solana-packet = { path = "packet", version = "3.0.0" } -solana-poh-config = { path = "poh-config", version = "2.2.1" } +solana-poh-config = { path = "poh-config", version = "3.0.0" } solana-precompile-error = { path = "precompile-error", version = "3.0.0" } -solana-presigner = { path = "presigner", version = "2.2.1" } +solana-presigner = { path = "presigner", version = "3.0.0" } solana-program = { path = "program", version = "2.2.1", default-features = false } solana-program-entrypoint = { path = "program-entrypoint", version = "3.0.0" } solana-program-error = { path = "program-error", version = "3.0.0" } @@ -271,8 +271,8 @@ solana-program-memory = { path = "program-memory", version = "3.0.0" } solana-program-option = { path = "program-option", version = "3.0.0" } solana-program-pack = { path = "program-pack", version = "3.0.0" } solana-pubkey = { path = "pubkey", version = "3.0.0", default-features = false } -solana-quic-definitions = { path = "quic-definitions", version = "2.2.1" } -solana-rent = { path = "rent", version = "2.2.1", default-features = false } +solana-quic-definitions = { path = "quic-definitions", version = "3.0.0" } +solana-rent = { path = "rent", version = "3.0.0", default-features = false } solana-rent-collector = { path = "rent-collector", version = "2.2.1" } solana-reward-info = { path = "reward-info", version = "3.0.0" } solana-sanitize = { path = "sanitize", version = "3.0.0" } @@ -280,7 +280,7 @@ solana-sdk = { path = "sdk", version = "2.2.1" } solana-sdk-ids = { path = "sdk-ids", version = "3.0.0" } solana-sdk-macro = { path = "sdk-macro", version = "3.0.0" } solana-sdk-wasm-js = { path = "sdk-wasm-js", version = "1.0.0" } -solana-secp256k1-program = { path = "secp256k1-program", version = "2.2.1" } +solana-secp256k1-program = { path = "secp256k1-program", version = "3.0.0" } solana-secp256k1-recover = { path = "secp256k1-recover", version = "3.0.0" } solana-secp256r1-program = { path = "secp256r1-program", version = "3.0.0", default-features = false } solana-seed-derivable = { path = "seed-derivable", version = "3.0.0" } @@ -297,9 +297,9 @@ solana-signer-store = { path = "signer-store", version = "0.1.0" } solana-slot-hashes = { path = "slot-hashes", version = "3.0.0" } solana-slot-history = { path = "slot-history", version = "3.0.0" } solana-stable-layout = { path = "stable-layout", version = "3.0.0" } -solana-system-interface = { path = "system-interface", version = "1.0" } +solana-system-interface = { path = "system-interface", version = "2.0" } solana-system-transaction = { path = "system-transaction", version = "2.2.1" } -solana-sysvar = { path = "sysvar", version = "2.2.1" } +solana-sysvar = { path = "sysvar", version = "3.0.0" } solana-sysvar-id = { path = "sysvar-id", version = "3.0.0" } solana-time-utils = { path = "time-utils", version = "3.0.0" } solana-transaction = { path = "transaction", version = "2.2.1" } diff --git a/account/Cargo.toml b/account/Cargo.toml index 2e04dc48b..7120be53b 100644 --- a/account/Cargo.toml +++ b/account/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-account" description = "Solana Account type" documentation = "https://docs.rs/solana-account" -version = "2.2.1" +version = "3.0.0" authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/address-lookup-table-interface/Cargo.toml b/address-lookup-table-interface/Cargo.toml index 9a416b119..fd28470fa 100644 --- a/address-lookup-table-interface/Cargo.toml +++ b/address-lookup-table-interface/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-address-lookup-table-interface" description = "Solana address lookup table interface." documentation = "https://docs.rs/solana-address-lookup-table-interface" -version = "2.2.2" +version = "3.0.0" rust-version = "1.81.0" authors = { workspace = true } repository = { workspace = true } diff --git a/epoch-schedule/Cargo.toml b/epoch-schedule/Cargo.toml index ef833bcef..04568e269 100644 --- a/epoch-schedule/Cargo.toml +++ b/epoch-schedule/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-epoch-schedule" description = "Configuration for Solana epochs and slots." documentation = "https://docs.rs/solana-epoch-schedule" -version = "2.2.1" +version = "3.0.0" authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/fee-calculator/Cargo.toml b/fee-calculator/Cargo.toml index b66c4da1d..6ba8e3861 100644 --- a/fee-calculator/Cargo.toml +++ b/fee-calculator/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-fee-calculator" description = "Solana transaction fee calculation" documentation = "https://docs.rs/solana-fee-calculator" -version = "2.2.1" +version = "3.0.0" authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/instructions-sysvar/Cargo.toml b/instructions-sysvar/Cargo.toml index 2cee4309f..1190d9026 100644 --- a/instructions-sysvar/Cargo.toml +++ b/instructions-sysvar/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-instructions-sysvar" description = "Type for instruction introspection during execution of Solana programs." documentation = "https://docs.rs/solana-instructions-sysvar" -version = "2.2.2" +version = "3.0.0" authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/nonce/Cargo.toml b/nonce/Cargo.toml index 8b053d170..bd3794d38 100644 --- a/nonce/Cargo.toml +++ b/nonce/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-nonce" description = "Solana durable transaction nonces." documentation = "https://docs.rs/solana-nonce" -version = "2.2.1" +version = "3.0.0" authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/offchain-message/Cargo.toml b/offchain-message/Cargo.toml index 20aabebda..cf7bbfcb1 100644 --- a/offchain-message/Cargo.toml +++ b/offchain-message/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-offchain-message" description = "Solana offchain message signing" documentation = "https://docs.rs/solana-offchain-message" -version = "2.2.1" +version = "3.0.0" authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/poh-config/Cargo.toml b/poh-config/Cargo.toml index 943a2876d..8f7fdb1af 100644 --- a/poh-config/Cargo.toml +++ b/poh-config/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-poh-config" description = "Definitions of Solana's proof of history." documentation = "https://docs.rs/solana-poh-config" -version = "2.2.1" +version = "3.0.0" authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/presigner/Cargo.toml b/presigner/Cargo.toml index 89ced3b1d..1e613d11b 100644 --- a/presigner/Cargo.toml +++ b/presigner/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-presigner" description = "A Solana `Signer` implementation representing an externally-constructed `Signature`." documentation = "https://docs.rs/solana-presigner" -version = "2.2.1" +version = "3.0.0" authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/quic-definitions/Cargo.toml b/quic-definitions/Cargo.toml index 8df3ff2b9..edefd1c91 100644 --- a/quic-definitions/Cargo.toml +++ b/quic-definitions/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-quic-definitions" description = "Definitions related to Solana over QUIC." documentation = "https://docs.rs/solana-quic-definitions" -version = "2.3.1" +version = "3.0.0" rust-version = "1.81.0" authors = { workspace = true } repository = { workspace = true } diff --git a/rent/Cargo.toml b/rent/Cargo.toml index 7dfa013c7..0b6690966 100644 --- a/rent/Cargo.toml +++ b/rent/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-rent" description = "Configuration for Solana network rent." documentation = "https://docs.rs/solana-rent" -version = "2.2.1" +version = "3.0.0" authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/secp256k1-program/Cargo.toml b/secp256k1-program/Cargo.toml index c4f9bd817..2fdfe66b9 100644 --- a/secp256k1-program/Cargo.toml +++ b/secp256k1-program/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-secp256k1-program" description = "Instructions for the Solana Secp256k1 native program." documentation = "https://docs.rs/solana-secp256k1-program" -version = "2.2.3" +version = "3.0.0" authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/system-interface/Cargo.toml b/system-interface/Cargo.toml index eac3abc9a..9acb03ec6 100644 --- a/system-interface/Cargo.toml +++ b/system-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solana-system-interface" -version = "1.0.0" +version = "2.0.0" description = "Instructions and constructors for the System program" readme = "README.md" authors = { workspace = true } diff --git a/sysvar/Cargo.toml b/sysvar/Cargo.toml index 1086bc5ea..704c6175f 100644 --- a/sysvar/Cargo.toml +++ b/sysvar/Cargo.toml @@ -2,7 +2,7 @@ name = "solana-sysvar" description = "Solana sysvar account types" documentation = "https://docs.rs/solana-sysvar" -version = "2.2.2" +version = "3.0.0" authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/vote-interface/Cargo.toml b/vote-interface/Cargo.toml index fba9deaf1..f1fdb16cc 100644 --- a/vote-interface/Cargo.toml +++ b/vote-interface/Cargo.toml @@ -36,8 +36,10 @@ frozen-abi = [ "solana-short-vec/frozen-abi", ] serde = [ + "dep:cfg_eval", "dep:serde", "dep:serde_derive", + "dep:serde_with", "dep:solana-serde-varint", "dep:solana-short-vec", "solana-hash/serde", @@ -47,10 +49,12 @@ serde = [ [dependencies] arbitrary = { workspace = true, features = ["derive"], optional = true } bincode = { workspace = true, optional = true } +cfg_eval = { workspace = true, optional = true } num-derive = { workspace = true } num-traits = { workspace = true } serde = { workspace = true, optional = true } serde_derive = { workspace = true, optional = true } +serde_with = { workspace = true, features = ["macros"], optional = true } solana-clock = { workspace = true } solana-frozen-abi = { workspace = true, features = [ "frozen-abi", diff --git a/vote-interface/src/state/mod.rs b/vote-interface/src/state/mod.rs index 85c028923..46795eff4 100644 --- a/vote-interface/src/state/mod.rs +++ b/vote-interface/src/state/mod.rs @@ -23,8 +23,15 @@ pub mod vote_state_versions; pub use vote_state_versions::*; pub mod vote_state_v3; pub use vote_state_v3::VoteStateV3; +pub mod vote_state_v4; +pub use vote_state_v4::VoteStateV4; mod vote_instruction_data; pub use vote_instruction_data::*; +#[cfg(any(target_os = "solana", feature = "bincode"))] +pub(crate) mod vote_state_deserialize; + +/// Size of a BLS public key in a compressed point representation +pub const BLS_PUBLIC_KEY_COMPRESSED_SIZE: usize = 48; // Maximum number of votes to keep around, tightly coupled with epoch_schedule::MINIMUM_SLOTS_PER_EPOCH pub const MAX_LOCKOUT_HISTORY: usize = 31; diff --git a/vote-interface/src/state/vote_state_deserialize.rs b/vote-interface/src/state/vote_state_deserialize.rs new file mode 100644 index 000000000..c90afa40c --- /dev/null +++ b/vote-interface/src/state/vote_state_deserialize.rs @@ -0,0 +1,205 @@ +use { + crate::{ + authorized_voters::AuthorizedVoters, + state::{ + BlockTimestamp, LandedVote, Lockout, VoteStateV3, MAX_EPOCH_CREDITS_HISTORY, MAX_ITEMS, + MAX_LOCKOUT_HISTORY, + }, + }, + solana_clock::Epoch, + solana_instruction_error::InstructionError, + solana_pubkey::Pubkey, + solana_serialize_utils::cursor::{ + read_bool, read_i64, read_option_u64, read_pubkey, read_pubkey_into, read_u32, read_u64, + read_u8, + }, + std::{collections::VecDeque, io::Cursor, ptr::addr_of_mut}, +}; + +// This is to reset vote_state to T::default() if deserialize fails or panics. +struct DropGuard { + vote_state: *mut T, +} + +impl Drop for DropGuard { + fn drop(&mut self) { + // Safety: + // + // Deserialize failed or panicked so at this point vote_state is uninitialized. We + // must write a new _valid_ value into it or after returning (or unwinding) from + // this function the caller is left with an uninitialized `&mut T`, which is UB + // (references must always be valid). + // + // This is always safe and doesn't leak memory because deserialize_into_ptr() writes + // into the fields that heap alloc only when it returns Ok(). + unsafe { + self.vote_state.write(T::default()); + } + } +} + +pub(crate) fn deserialize_into( + input: &[u8], + vote_state: &mut T, + deserialize_fn: impl FnOnce(&[u8], *mut T) -> Result<(), InstructionError>, +) -> Result<(), InstructionError> { + // Rebind vote_state to *mut T so that the &mut binding isn't accessible + // anymore, preventing accidental use after this point. + // + // NOTE: switch to ptr::from_mut() once platform-tools moves to rustc >= 1.76 + let vote_state = vote_state as *mut T; + + // Safety: vote_state is valid to_drop (see drop_in_place() docs). After + // dropping, the pointer is treated as uninitialized and only accessed + // through ptr::write, which is safe as per drop_in_place docs. + unsafe { + std::ptr::drop_in_place(vote_state); + } + + // This is to reset vote_state to T::default() if deserialize fails or panics. + let guard = DropGuard { vote_state }; + + let res = deserialize_fn(input, vote_state); + if res.is_ok() { + std::mem::forget(guard); + } + + res +} + +pub(super) fn deserialize_vote_state_into( + cursor: &mut Cursor<&[u8]>, + vote_state: *mut VoteStateV3, + has_latency: bool, +) -> Result<(), InstructionError> { + // General safety note: we must use add_or_mut! to access the `vote_state` fields as the value + // is assumed to be _uninitialized_, so creating references to the state or any of its inner + // fields is UB. + + read_pubkey_into( + cursor, + // Safety: if vote_state is non-null, node_pubkey is guaranteed to be valid too + unsafe { addr_of_mut!((*vote_state).node_pubkey) }, + )?; + read_pubkey_into( + cursor, + // Safety: if vote_state is non-null, authorized_withdrawer is guaranteed to be valid too + unsafe { addr_of_mut!((*vote_state).authorized_withdrawer) }, + )?; + let commission = read_u8(cursor)?; + let votes = read_votes(cursor, has_latency)?; + let root_slot = read_option_u64(cursor)?; + let authorized_voters = read_authorized_voters(cursor)?; + read_prior_voters_into(cursor, vote_state)?; + let epoch_credits = read_epoch_credits(cursor)?; + read_last_timestamp_into(cursor, vote_state)?; + + // Safety: if vote_state is non-null, all the fields are guaranteed to be + // valid pointers. + // + // Heap allocated collections - votes, authorized_voters and epoch_credits - + // are guaranteed not to leak after this point as the VoteStateV3 is fully + // initialized and will be regularly dropped. + unsafe { + addr_of_mut!((*vote_state).commission).write(commission); + addr_of_mut!((*vote_state).votes).write(votes); + addr_of_mut!((*vote_state).root_slot).write(root_slot); + addr_of_mut!((*vote_state).authorized_voters).write(authorized_voters); + addr_of_mut!((*vote_state).epoch_credits).write(epoch_credits); + } + + Ok(()) +} + +fn read_votes>( + cursor: &mut Cursor, + has_latency: bool, +) -> Result, InstructionError> { + let vote_count = read_u64(cursor)? as usize; + let mut votes = VecDeque::with_capacity(vote_count.min(MAX_LOCKOUT_HISTORY)); + + for _ in 0..vote_count { + let latency = if has_latency { read_u8(cursor)? } else { 0 }; + + let slot = read_u64(cursor)?; + let confirmation_count = read_u32(cursor)?; + let lockout = Lockout::new_with_confirmation_count(slot, confirmation_count); + + votes.push_back(LandedVote { latency, lockout }); + } + + Ok(votes) +} + +fn read_authorized_voters>( + cursor: &mut Cursor, +) -> Result { + let authorized_voter_count = read_u64(cursor)?; + let mut authorized_voters = AuthorizedVoters::default(); + + for _ in 0..authorized_voter_count { + let epoch = read_u64(cursor)?; + let authorized_voter = read_pubkey(cursor)?; + authorized_voters.insert(epoch, authorized_voter); + } + + Ok(authorized_voters) +} + +fn read_prior_voters_into>( + cursor: &mut Cursor, + vote_state: *mut VoteStateV3, +) -> Result<(), InstructionError> { + // Safety: if vote_state is non-null, prior_voters is guaranteed to be valid too + unsafe { + let prior_voters = addr_of_mut!((*vote_state).prior_voters); + let prior_voters_buf = addr_of_mut!((*prior_voters).buf) as *mut (Pubkey, Epoch, Epoch); + + for i in 0..MAX_ITEMS { + let prior_voter = read_pubkey(cursor)?; + let from_epoch = read_u64(cursor)?; + let until_epoch = read_u64(cursor)?; + + prior_voters_buf + .add(i) + .write((prior_voter, from_epoch, until_epoch)); + } + + (*vote_state).prior_voters.idx = read_u64(cursor)? as usize; + (*vote_state).prior_voters.is_empty = read_bool(cursor)?; + } + Ok(()) +} + +fn read_epoch_credits>( + cursor: &mut Cursor, +) -> Result, InstructionError> { + let epoch_credit_count = read_u64(cursor)? as usize; + let mut epoch_credits = Vec::with_capacity(epoch_credit_count.min(MAX_EPOCH_CREDITS_HISTORY)); + + for _ in 0..epoch_credit_count { + let epoch = read_u64(cursor)?; + let credits = read_u64(cursor)?; + let prev_credits = read_u64(cursor)?; + epoch_credits.push((epoch, credits, prev_credits)); + } + + Ok(epoch_credits) +} + +fn read_last_timestamp_into>( + cursor: &mut Cursor, + vote_state: *mut VoteStateV3, +) -> Result<(), InstructionError> { + let slot = read_u64(cursor)?; + let timestamp = read_i64(cursor)?; + + let last_timestamp = BlockTimestamp { slot, timestamp }; + + // Safety: if vote_state is non-null, last_timestamp is guaranteed to be valid too + unsafe { + addr_of_mut!((*vote_state).last_timestamp).write(last_timestamp); + } + + Ok(()) +} diff --git a/vote-interface/src/state/vote_state_v3.rs b/vote-interface/src/state/vote_state_v3.rs index 3ba8b26b6..aef392d16 100644 --- a/vote-interface/src/state/vote_state_v3.rs +++ b/vote-interface/src/state/vote_state_v3.rs @@ -145,49 +145,8 @@ impl VoteStateV3 { input: &[u8], vote_state: &mut VoteStateV3, ) -> Result<(), InstructionError> { - // Rebind vote_state to *mut VoteStateV3 so that the &mut binding isn't - // accessible anymore, preventing accidental use after this point. - // - // NOTE: switch to ptr::from_mut() once platform-tools moves to rustc >= 1.76 - let vote_state = vote_state as *mut VoteStateV3; - - // Safety: vote_state is valid to_drop (see drop_in_place() docs). After - // dropping, the pointer is treated as uninitialized and only accessed - // through ptr::write, which is safe as per drop_in_place docs. - unsafe { - std::ptr::drop_in_place(vote_state); - } - - // This is to reset vote_state to VoteStateV3::default() if deserialize fails or panics. - struct DropGuard { - vote_state: *mut VoteStateV3, - } - - impl Drop for DropGuard { - fn drop(&mut self) { - // Safety: - // - // Deserialize failed or panicked so at this point vote_state is uninitialized. We - // must write a new _valid_ value into it or after returning (or unwinding) from - // this function the caller is left with an uninitialized `&mut VoteStateV3`, which is - // UB (references must always be valid). - // - // This is always safe and doesn't leak memory because deserialize_into_ptr() writes - // into the fields that heap alloc only when it returns Ok(). - unsafe { - self.vote_state.write(VoteStateV3::default()); - } - } - } - - let guard = DropGuard { vote_state }; - - let res = VoteStateV3::deserialize_into_ptr(input, vote_state); - if res.is_ok() { - std::mem::forget(guard); - } - - res + use super::vote_state_deserialize; + vote_state_deserialize::deserialize_into(input, vote_state, Self::deserialize_into_ptr) } /// Deserializes the input `VoteStateVersions` buffer directly into the provided @@ -214,7 +173,7 @@ impl VoteStateV3 { input: &[u8], vote_state: *mut VoteStateV3, ) -> Result<(), InstructionError> { - use vote_state_deserialize::deserialize_vote_state_into; + use super::vote_state_deserialize::deserialize_vote_state_into; let mut cursor = std::io::Cursor::new(input); @@ -564,162 +523,3 @@ impl VoteStateV3 { && data[VERSION_OFFSET..DEFAULT_PRIOR_VOTERS_END] != [0; DEFAULT_PRIOR_VOTERS_OFFSET] } } - -#[cfg(any(target_os = "solana", feature = "bincode"))] -mod vote_state_deserialize { - use { - crate::{ - authorized_voters::AuthorizedVoters, - state::{ - BlockTimestamp, LandedVote, Lockout, VoteStateV3, MAX_EPOCH_CREDITS_HISTORY, - MAX_ITEMS, MAX_LOCKOUT_HISTORY, - }, - }, - solana_clock::Epoch, - solana_instruction_error::InstructionError, - solana_pubkey::Pubkey, - solana_serialize_utils::cursor::{ - read_bool, read_i64, read_option_u64, read_pubkey, read_pubkey_into, read_u32, - read_u64, read_u8, - }, - std::{collections::VecDeque, io::Cursor, ptr::addr_of_mut}, - }; - - pub(super) fn deserialize_vote_state_into( - cursor: &mut Cursor<&[u8]>, - vote_state: *mut VoteStateV3, - has_latency: bool, - ) -> Result<(), InstructionError> { - // General safety note: we must use add_or_mut! to access the `vote_state` fields as the value - // is assumed to be _uninitialized_, so creating references to the state or any of its inner - // fields is UB. - - read_pubkey_into( - cursor, - // Safety: if vote_state is non-null, node_pubkey is guaranteed to be valid too - unsafe { addr_of_mut!((*vote_state).node_pubkey) }, - )?; - read_pubkey_into( - cursor, - // Safety: if vote_state is non-null, authorized_withdrawer is guaranteed to be valid too - unsafe { addr_of_mut!((*vote_state).authorized_withdrawer) }, - )?; - let commission = read_u8(cursor)?; - let votes = read_votes(cursor, has_latency)?; - let root_slot = read_option_u64(cursor)?; - let authorized_voters = read_authorized_voters(cursor)?; - read_prior_voters_into(cursor, vote_state)?; - let epoch_credits = read_epoch_credits(cursor)?; - read_last_timestamp_into(cursor, vote_state)?; - - // Safety: if vote_state is non-null, all the fields are guaranteed to be - // valid pointers. - // - // Heap allocated collections - votes, authorized_voters and epoch_credits - - // are guaranteed not to leak after this point as the VoteStateV3 is fully - // initialized and will be regularly dropped. - unsafe { - addr_of_mut!((*vote_state).commission).write(commission); - addr_of_mut!((*vote_state).votes).write(votes); - addr_of_mut!((*vote_state).root_slot).write(root_slot); - addr_of_mut!((*vote_state).authorized_voters).write(authorized_voters); - addr_of_mut!((*vote_state).epoch_credits).write(epoch_credits); - } - - Ok(()) - } - - fn read_votes>( - cursor: &mut Cursor, - has_latency: bool, - ) -> Result, InstructionError> { - let vote_count = read_u64(cursor)? as usize; - let mut votes = VecDeque::with_capacity(vote_count.min(MAX_LOCKOUT_HISTORY)); - - for _ in 0..vote_count { - let latency = if has_latency { read_u8(cursor)? } else { 0 }; - - let slot = read_u64(cursor)?; - let confirmation_count = read_u32(cursor)?; - let lockout = Lockout::new_with_confirmation_count(slot, confirmation_count); - - votes.push_back(LandedVote { latency, lockout }); - } - - Ok(votes) - } - - fn read_authorized_voters>( - cursor: &mut Cursor, - ) -> Result { - let authorized_voter_count = read_u64(cursor)?; - let mut authorized_voters = AuthorizedVoters::default(); - - for _ in 0..authorized_voter_count { - let epoch = read_u64(cursor)?; - let authorized_voter = read_pubkey(cursor)?; - authorized_voters.insert(epoch, authorized_voter); - } - - Ok(authorized_voters) - } - - fn read_prior_voters_into>( - cursor: &mut Cursor, - vote_state: *mut VoteStateV3, - ) -> Result<(), InstructionError> { - // Safety: if vote_state is non-null, prior_voters is guaranteed to be valid too - unsafe { - let prior_voters = addr_of_mut!((*vote_state).prior_voters); - let prior_voters_buf = addr_of_mut!((*prior_voters).buf) as *mut (Pubkey, Epoch, Epoch); - - for i in 0..MAX_ITEMS { - let prior_voter = read_pubkey(cursor)?; - let from_epoch = read_u64(cursor)?; - let until_epoch = read_u64(cursor)?; - - prior_voters_buf - .add(i) - .write((prior_voter, from_epoch, until_epoch)); - } - - (*vote_state).prior_voters.idx = read_u64(cursor)? as usize; - (*vote_state).prior_voters.is_empty = read_bool(cursor)?; - } - Ok(()) - } - - fn read_epoch_credits>( - cursor: &mut Cursor, - ) -> Result, InstructionError> { - let epoch_credit_count = read_u64(cursor)? as usize; - let mut epoch_credits = - Vec::with_capacity(epoch_credit_count.min(MAX_EPOCH_CREDITS_HISTORY)); - - for _ in 0..epoch_credit_count { - let epoch = read_u64(cursor)?; - let credits = read_u64(cursor)?; - let prev_credits = read_u64(cursor)?; - epoch_credits.push((epoch, credits, prev_credits)); - } - - Ok(epoch_credits) - } - - fn read_last_timestamp_into>( - cursor: &mut Cursor, - vote_state: *mut VoteStateV3, - ) -> Result<(), InstructionError> { - let slot = read_u64(cursor)?; - let timestamp = read_i64(cursor)?; - - let last_timestamp = BlockTimestamp { slot, timestamp }; - - // Safety: if vote_state is non-null, last_timestamp is guaranteed to be valid too - unsafe { - addr_of_mut!((*vote_state).last_timestamp).write(last_timestamp); - } - - Ok(()) - } -} diff --git a/vote-interface/src/state/vote_state_v4.rs b/vote-interface/src/state/vote_state_v4.rs new file mode 100644 index 000000000..1ca30d395 --- /dev/null +++ b/vote-interface/src/state/vote_state_v4.rs @@ -0,0 +1,67 @@ +#[cfg(feature = "dev-context-only-utils")] +use arbitrary::Arbitrary; +#[cfg(feature = "serde")] +use serde_derive::{Deserialize, Serialize}; +#[cfg(feature = "serde")] +use serde_with::serde_as; +#[cfg(feature = "frozen-abi")] +use solana_frozen_abi_macro::{frozen_abi, AbiExample}; +use { + super::{BlockTimestamp, LandedVote, BLS_PUBLIC_KEY_COMPRESSED_SIZE}, + crate::authorized_voters::AuthorizedVoters, + solana_clock::{Epoch, Slot}, + solana_pubkey::Pubkey, + std::{collections::VecDeque, fmt::Debug}, +}; + +#[cfg_attr( + feature = "frozen-abi", + frozen_abi(digest = "2H9WgTh7LgdnpinvEwxzP3HF6SDuKp6qdwFmJk9jHDRP"), + derive(AbiExample) +)] +#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Debug, Default, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))] +pub struct VoteStateV4 { + /// The node that votes in this account. + pub node_pubkey: Pubkey, + /// The signer for withdrawals. + pub authorized_withdrawer: Pubkey, + + /// The collector account for inflation rewards. + pub inflation_rewards_collector: Pubkey, + /// The collector account for block revenue. + pub block_revenue_collector: Pubkey, + + /// Basis points (0-10,000) that represent how much of the inflation + /// rewards should be given to this vote account. + pub inflation_rewards_commission_bps: u16, + /// Basis points (0-10,000) that represent how much of the block revenue + /// should be given to this vote account. + pub block_revenue_commission_bps: u16, + + /// Reward amount pending distribution to stake delegators. + pub pending_delegator_rewards: u64, + + /// Compressed BLS pubkey for Alpenglow. + #[cfg_attr( + feature = "serde", + serde_as(as = "Option<[_; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>") + )] + pub bls_pubkey_compressed: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>, + + pub votes: VecDeque, + pub root_slot: Option, + + /// The signer for vote transactions. + /// Contains entries for the current epoch and the previous epoch. + pub authorized_voters: AuthorizedVoters, + + /// History of credits earned by the end of each epoch. + /// Each tuple is (Epoch, credits, prev_credits). + pub epoch_credits: Vec<(Epoch, u64, u64)>, + + /// Most recent timestamp submitted with a vote. + pub last_timestamp: BlockTimestamp, +}