checks account owner when initializing a vote-account (#25018)
A VoteAccount may only wrap an account if the account owner is solana_vote_program:id or equivalently this check returns true: solana_vote_program::check_id(account.owner())
This commit is contained in:
parent
d34b440a3c
commit
492f89a170
|
@ -1404,6 +1404,7 @@ pub mod test {
|
||||||
let mut account = AccountSharedData::from(Account {
|
let mut account = AccountSharedData::from(Account {
|
||||||
data: vec![0; VoteState::size_of()],
|
data: vec![0; VoteState::size_of()],
|
||||||
lamports: *lamports,
|
lamports: *lamports,
|
||||||
|
owner: solana_vote_program::id(),
|
||||||
..Account::default()
|
..Account::default()
|
||||||
});
|
});
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
|
@ -1417,7 +1418,7 @@ pub mod test {
|
||||||
.expect("serialize state");
|
.expect("serialize state");
|
||||||
(
|
(
|
||||||
solana_sdk::pubkey::new_rand(),
|
solana_sdk::pubkey::new_rand(),
|
||||||
(*lamports, VoteAccount::from(account)),
|
(*lamports, VoteAccount::try_from(account).unwrap()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
@ -695,7 +695,15 @@ impl ProgressMap {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use {super::*, solana_runtime::vote_account::VoteAccount};
|
use {super::*, solana_runtime::vote_account::VoteAccount, solana_sdk::account::Account};
|
||||||
|
|
||||||
|
fn new_test_vote_account() -> VoteAccount {
|
||||||
|
let account = Account {
|
||||||
|
owner: solana_vote_program::id(),
|
||||||
|
..Account::default()
|
||||||
|
};
|
||||||
|
VoteAccount::try_from(account).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_vote_pubkey() {
|
fn test_add_vote_pubkey() {
|
||||||
|
@ -730,7 +738,7 @@ mod test {
|
||||||
let epoch_vote_accounts: HashMap<_, _> = vote_account_pubkeys
|
let epoch_vote_accounts: HashMap<_, _> = vote_account_pubkeys
|
||||||
.iter()
|
.iter()
|
||||||
.skip(num_vote_accounts - staked_vote_accounts)
|
.skip(num_vote_accounts - staked_vote_accounts)
|
||||||
.map(|pubkey| (*pubkey, (1, VoteAccount::default())))
|
.map(|pubkey| (*pubkey, (1, new_test_vote_account())))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut stats = PropagatedStats::default();
|
let mut stats = PropagatedStats::default();
|
||||||
|
@ -772,7 +780,7 @@ mod test {
|
||||||
let epoch_vote_accounts: HashMap<_, _> = vote_account_pubkeys
|
let epoch_vote_accounts: HashMap<_, _> = vote_account_pubkeys
|
||||||
.iter()
|
.iter()
|
||||||
.skip(num_vote_accounts - staked_vote_accounts)
|
.skip(num_vote_accounts - staked_vote_accounts)
|
||||||
.map(|pubkey| (*pubkey, (1, VoteAccount::default())))
|
.map(|pubkey| (*pubkey, (1, new_test_vote_account())))
|
||||||
.collect();
|
.collect();
|
||||||
stats.add_node_pubkey_internal(&node_pubkey, &vote_account_pubkeys, &epoch_vote_accounts);
|
stats.add_node_pubkey_internal(&node_pubkey, &vote_account_pubkeys, &epoch_vote_accounts);
|
||||||
assert!(stats.propagated_node_ids.contains(&node_pubkey));
|
assert!(stats.propagated_node_ids.contains(&node_pubkey));
|
||||||
|
|
|
@ -3780,7 +3780,7 @@ pub mod tests {
|
||||||
VoteState::serialize(&versioned, vote_account.data_as_mut_slice()).unwrap();
|
VoteState::serialize(&versioned, vote_account.data_as_mut_slice()).unwrap();
|
||||||
(
|
(
|
||||||
solana_sdk::pubkey::new_rand(),
|
solana_sdk::pubkey::new_rand(),
|
||||||
(stake, VoteAccount::from(vote_account)),
|
(stake, VoteAccount::try_from(vote_account).unwrap()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
@ -28,7 +28,7 @@ pub mod shred_stats;
|
||||||
mod shredder;
|
mod shredder;
|
||||||
pub mod sigverify_shreds;
|
pub mod sigverify_shreds;
|
||||||
pub mod slot_stats;
|
pub mod slot_stats;
|
||||||
pub mod staking_utils;
|
mod staking_utils;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate solana_metrics;
|
extern crate solana_metrics;
|
||||||
|
|
|
@ -113,11 +113,12 @@ pub(crate) mod tests {
|
||||||
let account = AccountSharedData::new_data(
|
let account = AccountSharedData::new_data(
|
||||||
rng.gen(), // lamports
|
rng.gen(), // lamports
|
||||||
&VoteStateVersions::new_current(vote_state),
|
&VoteStateVersions::new_current(vote_state),
|
||||||
&Pubkey::new_unique(), // owner
|
&solana_vote_program::id(), // owner
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vote_pubkey = Pubkey::new_unique();
|
let vote_pubkey = Pubkey::new_unique();
|
||||||
(vote_pubkey, (stake, VoteAccount::from(account)))
|
let vote_account = VoteAccount::try_from(account).unwrap();
|
||||||
|
(vote_pubkey, (stake, vote_account))
|
||||||
});
|
});
|
||||||
let result = vote_accounts.collect::<VoteAccounts>().staked_nodes();
|
let result = vote_accounts.collect::<VoteAccounts>().staked_nodes();
|
||||||
assert_eq!(result.len(), 2);
|
assert_eq!(result.len(), 2);
|
||||||
|
|
|
@ -2910,7 +2910,7 @@ impl Bank {
|
||||||
{
|
{
|
||||||
vote_accounts_cache_miss_count.fetch_add(1, Relaxed);
|
vote_accounts_cache_miss_count.fetch_add(1, Relaxed);
|
||||||
}
|
}
|
||||||
Some(VoteAccount::from(account))
|
VoteAccount::try_from(account).ok()
|
||||||
};
|
};
|
||||||
let invalid_vote_keys = DashMap::<Pubkey, InvalidCacheEntryReason>::new();
|
let invalid_vote_keys = DashMap::<Pubkey, InvalidCacheEntryReason>::new();
|
||||||
let make_vote_delegations_entry = |vote_pubkey| {
|
let make_vote_delegations_entry = |vote_pubkey| {
|
||||||
|
|
|
@ -190,10 +190,8 @@ pub(crate) mod tests {
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(_, vote_accounts)| {
|
.flat_map(|(_, vote_accounts)| {
|
||||||
vote_accounts.iter().map(|v| {
|
vote_accounts.iter().map(|v| {
|
||||||
(
|
let vote_account = VoteAccount::try_from(v.account.clone()).unwrap();
|
||||||
v.vote_account,
|
(v.vote_account, (stake_per_account, vote_account))
|
||||||
(stake_per_account, VoteAccount::from(v.account.clone())),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
@ -30,8 +30,8 @@ pub enum Error {
|
||||||
InstructionError(#[from] InstructionError),
|
InstructionError(#[from] InstructionError),
|
||||||
#[error("Invalid delegation: {0:?}")]
|
#[error("Invalid delegation: {0:?}")]
|
||||||
InvalidDelegation(StakeState),
|
InvalidDelegation(StakeState),
|
||||||
#[error("Invalid stake account owner: {owner:?}")]
|
#[error("Invalid stake account owner: {0}")]
|
||||||
InvalidOwner { owner: Pubkey },
|
InvalidOwner(/*owner:*/ Pubkey),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> StakeAccount<T> {
|
impl<T> StakeAccount<T> {
|
||||||
|
@ -59,9 +59,7 @@ impl TryFrom<AccountSharedData> for StakeAccount<()> {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
fn try_from(account: AccountSharedData) -> Result<Self, Self::Error> {
|
fn try_from(account: AccountSharedData) -> Result<Self, Self::Error> {
|
||||||
if account.owner() != &solana_stake_program::id() {
|
if account.owner() != &solana_stake_program::id() {
|
||||||
return Err(Error::InvalidOwner {
|
return Err(Error::InvalidOwner(*account.owner()));
|
||||||
owner: *account.owner(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
let stake_state = account.state()?;
|
let stake_state = account.state()?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
|
@ -78,13 +78,20 @@ impl StakesCache {
|
||||||
debug_assert_ne!(account.lamports(), 0u64);
|
debug_assert_ne!(account.lamports(), 0u64);
|
||||||
if solana_vote_program::check_id(owner) {
|
if solana_vote_program::check_id(owner) {
|
||||||
if VoteState::is_correct_size_and_initialized(account.data()) {
|
if VoteState::is_correct_size_and_initialized(account.data()) {
|
||||||
let vote_account = VoteAccount::from(account.clone());
|
match VoteAccount::try_from(account.clone()) {
|
||||||
{
|
Ok(vote_account) => {
|
||||||
// Called to eagerly deserialize vote state
|
{
|
||||||
let _res = vote_account.vote_state();
|
// Called to eagerly deserialize vote state
|
||||||
|
let _res = vote_account.vote_state();
|
||||||
|
}
|
||||||
|
let mut stakes = self.0.write().unwrap();
|
||||||
|
stakes.upsert_vote_account(pubkey, vote_account);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
let mut stakes = self.0.write().unwrap();
|
||||||
|
stakes.remove_vote_account(pubkey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let mut stakes = self.0.write().unwrap();
|
|
||||||
stakes.upsert_vote_account(pubkey, vote_account);
|
|
||||||
} else {
|
} else {
|
||||||
let mut stakes = self.0.write().unwrap();
|
let mut stakes = self.0.write().unwrap();
|
||||||
stakes.remove_vote_account(pubkey)
|
stakes.remove_vote_account(pubkey)
|
||||||
|
|
|
@ -13,21 +13,31 @@ use {
|
||||||
iter::FromIterator,
|
iter::FromIterator,
|
||||||
sync::{Arc, Once, RwLock, RwLockReadGuard},
|
sync::{Arc, Once, RwLock, RwLockReadGuard},
|
||||||
},
|
},
|
||||||
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The value here does not matter. It will be overwritten
|
// The value here does not matter. It will be overwritten
|
||||||
// at the first call to VoteAccount::vote_state().
|
// at the first call to VoteAccount::vote_state().
|
||||||
const INVALID_VOTE_STATE: Result<VoteState, InstructionError> =
|
const INVALID_VOTE_STATE: Result<VoteState, Error> = Err(Error::InstructionError(
|
||||||
Err(InstructionError::InvalidAccountData);
|
InstructionError::InvalidAccountData,
|
||||||
|
));
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, AbiExample, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, AbiExample, Deserialize)]
|
||||||
#[serde(from = "Account")]
|
#[serde(try_from = "Account")]
|
||||||
pub struct VoteAccount(Arc<VoteAccountInner>);
|
pub struct VoteAccount(Arc<VoteAccountInner>);
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
InstructionError(#[from] InstructionError),
|
||||||
|
#[error("Invalid vote account owner: {0}")]
|
||||||
|
InvalidOwner(/*owner:*/ Pubkey),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, AbiExample)]
|
#[derive(Debug, AbiExample)]
|
||||||
struct VoteAccountInner {
|
struct VoteAccountInner {
|
||||||
account: Account,
|
account: Account,
|
||||||
vote_state: RwLock<Result<VoteState, InstructionError>>,
|
vote_state: RwLock<Result<VoteState, Error>>,
|
||||||
vote_state_once: Once,
|
vote_state_once: Once,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,11 +69,11 @@ impl VoteAccount {
|
||||||
self.0.account.owner()
|
self.0.account.owner()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vote_state(&self) -> RwLockReadGuard<Result<VoteState, InstructionError>> {
|
pub fn vote_state(&self) -> RwLockReadGuard<Result<VoteState, Error>> {
|
||||||
let inner = &self.0;
|
let inner = &self.0;
|
||||||
inner.vote_state_once.call_once(|| {
|
inner.vote_state_once.call_once(|| {
|
||||||
let vote_state = VoteState::deserialize(inner.account.data());
|
let vote_state = VoteState::deserialize(inner.account.data());
|
||||||
*inner.vote_state.write().unwrap() = vote_state;
|
*inner.vote_state.write().unwrap() = vote_state.map_err(Error::from);
|
||||||
});
|
});
|
||||||
inner.vote_state.read().unwrap()
|
inner.vote_state.read().unwrap()
|
||||||
}
|
}
|
||||||
|
@ -187,9 +197,10 @@ impl Serialize for VoteAccount {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AccountSharedData> for VoteAccount {
|
impl TryFrom<AccountSharedData> for VoteAccount {
|
||||||
fn from(account: AccountSharedData) -> Self {
|
type Error = Error;
|
||||||
Self::from(Account::from(account))
|
fn try_from(account: AccountSharedData) -> Result<Self, Self::Error> {
|
||||||
|
Self::try_from(Account::from(account))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,29 +210,25 @@ impl From<VoteAccount> for AccountSharedData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Account> for VoteAccount {
|
impl TryFrom<Account> for VoteAccount {
|
||||||
fn from(account: Account) -> Self {
|
type Error = Error;
|
||||||
Self(Arc::new(VoteAccountInner::from(account)))
|
fn try_from(account: Account) -> Result<Self, Self::Error> {
|
||||||
|
let vote_account = VoteAccountInner::try_from(account)?;
|
||||||
|
Ok(Self(Arc::new(vote_account)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Account> for VoteAccountInner {
|
impl TryFrom<Account> for VoteAccountInner {
|
||||||
fn from(account: Account) -> Self {
|
type Error = Error;
|
||||||
Self {
|
fn try_from(account: Account) -> Result<Self, Self::Error> {
|
||||||
|
if !solana_vote_program::check_id(account.owner()) {
|
||||||
|
return Err(Error::InvalidOwner(*account.owner()));
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
account,
|
account,
|
||||||
vote_state: RwLock::new(INVALID_VOTE_STATE),
|
vote_state: RwLock::new(INVALID_VOTE_STATE),
|
||||||
vote_state_once: Once::new(),
|
vote_state_once: Once::new(),
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for VoteAccountInner {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
account: Account::default(),
|
|
||||||
vote_state: RwLock::new(INVALID_VOTE_STATE),
|
|
||||||
vote_state_once: Once::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +363,7 @@ mod tests {
|
||||||
let account = Account::new_data(
|
let account = Account::new_data(
|
||||||
rng.gen(), // lamports
|
rng.gen(), // lamports
|
||||||
&VoteStateVersions::new_current(vote_state.clone()),
|
&VoteStateVersions::new_current(vote_state.clone()),
|
||||||
&Pubkey::new_unique(), // owner
|
&solana_vote_program::id(), // owner
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
(account, vote_state)
|
(account, vote_state)
|
||||||
|
@ -371,7 +378,8 @@ mod tests {
|
||||||
let node = nodes[rng.gen_range(0, nodes.len())];
|
let node = nodes[rng.gen_range(0, nodes.len())];
|
||||||
let (account, _) = new_rand_vote_account(rng, Some(node));
|
let (account, _) = new_rand_vote_account(rng, Some(node));
|
||||||
let stake = rng.gen_range(0, 997);
|
let stake = rng.gen_range(0, 997);
|
||||||
(Pubkey::new_unique(), (stake, VoteAccount::from(account)))
|
let vote_account = VoteAccount::try_from(account).unwrap();
|
||||||
|
(Pubkey::new_unique(), (stake, vote_account))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,7 +407,7 @@ mod tests {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let (account, vote_state) = new_rand_vote_account(&mut rng, None);
|
let (account, vote_state) = new_rand_vote_account(&mut rng, None);
|
||||||
let lamports = account.lamports();
|
let lamports = account.lamports();
|
||||||
let vote_account = VoteAccount::from(account);
|
let vote_account = VoteAccount::try_from(account).unwrap();
|
||||||
assert_eq!(lamports, vote_account.lamports());
|
assert_eq!(lamports, vote_account.lamports());
|
||||||
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
||||||
// 2nd call to .vote_state() should return the cached value.
|
// 2nd call to .vote_state() should return the cached value.
|
||||||
|
@ -410,7 +418,7 @@ mod tests {
|
||||||
fn test_vote_account_serialize() {
|
fn test_vote_account_serialize() {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let (account, vote_state) = new_rand_vote_account(&mut rng, None);
|
let (account, vote_state) = new_rand_vote_account(&mut rng, None);
|
||||||
let vote_account = VoteAccount::from(account.clone());
|
let vote_account = VoteAccount::try_from(account.clone()).unwrap();
|
||||||
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
||||||
// Assert than VoteAccount has the same wire format as Account.
|
// Assert than VoteAccount has the same wire format as Account.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -424,7 +432,7 @@ mod tests {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let (account, vote_state) = new_rand_vote_account(&mut rng, None);
|
let (account, vote_state) = new_rand_vote_account(&mut rng, None);
|
||||||
let data = bincode::serialize(&account).unwrap();
|
let data = bincode::serialize(&account).unwrap();
|
||||||
let vote_account = VoteAccount::from(account);
|
let vote_account = VoteAccount::try_from(account).unwrap();
|
||||||
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
||||||
let other_vote_account: VoteAccount = bincode::deserialize(&data).unwrap();
|
let other_vote_account: VoteAccount = bincode::deserialize(&data).unwrap();
|
||||||
assert_eq!(vote_account, other_vote_account);
|
assert_eq!(vote_account, other_vote_account);
|
||||||
|
@ -438,7 +446,7 @@ mod tests {
|
||||||
fn test_vote_account_round_trip() {
|
fn test_vote_account_round_trip() {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let (account, vote_state) = new_rand_vote_account(&mut rng, None);
|
let (account, vote_state) = new_rand_vote_account(&mut rng, None);
|
||||||
let vote_account = VoteAccount::from(account);
|
let vote_account = VoteAccount::try_from(account).unwrap();
|
||||||
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
||||||
let data = bincode::serialize(&vote_account).unwrap();
|
let data = bincode::serialize(&vote_account).unwrap();
|
||||||
let other_vote_account: VoteAccount = bincode::deserialize(&data).unwrap();
|
let other_vote_account: VoteAccount = bincode::deserialize(&data).unwrap();
|
||||||
|
|
Loading…
Reference in New Issue