Durable Nonce: Re-introduce Initialize instruction (#7353)

Toward an Authorized Noncer
This commit is contained in:
Trent Nelson
2019-12-08 10:16:55 -07:00
committed by GitHub
parent deb7ac549c
commit 059e631f41
3 changed files with 201 additions and 101 deletions

View File

@ -161,7 +161,7 @@ mod tests {
assert_eq!(state, NonceState::Uninitialized); assert_eq!(state, NonceState::Uninitialized);
let recent_blockhashes = create_test_recent_blockhashes(0); let recent_blockhashes = create_test_recent_blockhashes(0);
nonce_account nonce_account
.nonce(&recent_blockhashes, &Rent::default(), &signers) .initialize(&recent_blockhashes, &Rent::default(), &signers)
.unwrap(); .unwrap();
assert!(verify_nonce(&nonce_account.account, &recent_blockhashes[0])); assert!(verify_nonce(&nonce_account.account, &recent_blockhashes[0]));
}); });
@ -184,7 +184,7 @@ mod tests {
assert_eq!(state, NonceState::Uninitialized); assert_eq!(state, NonceState::Uninitialized);
let recent_blockhashes = create_test_recent_blockhashes(0); let recent_blockhashes = create_test_recent_blockhashes(0);
nonce_account nonce_account
.nonce(&recent_blockhashes, &Rent::default(), &signers) .initialize(&recent_blockhashes, &Rent::default(), &signers)
.unwrap(); .unwrap();
assert!(!verify_nonce( assert!(!verify_nonce(
&nonce_account.account, &nonce_account.account,

View File

@ -56,6 +56,15 @@ pub enum NonceInstruction {
/// The `u64` parameter is the lamports to withdraw, which must leave the /// The `u64` parameter is the lamports to withdraw, which must leave the
/// account balance above the rent exempt reserve or at zero. /// account balance above the rent exempt reserve or at zero.
Withdraw(u64), Withdraw(u64),
/// `Initialize` drives state of Uninitalized NonceAccount to Initialized,
/// setting the nonce value.
///
/// Expects 3 Accounts:
/// 0 - A NonceAccount in the Uninitialized state
/// 1 - RecentBlockHashes sysvar
/// 2 - Rent sysvar
Initialize,
} }
pub fn create_nonce_account( pub fn create_nonce_account(
@ -71,10 +80,22 @@ pub fn create_nonce_account(
NonceState::size() as u64, NonceState::size() as u64,
&id(), &id(),
), ),
nonce(nonce_pubkey), initialize(nonce_pubkey),
] ]
} }
pub fn initialize(nonce_pubkey: &Pubkey) -> Instruction {
Instruction::new(
id(),
&NonceInstruction::Initialize,
vec![
AccountMeta::new(*nonce_pubkey, true),
AccountMeta::new_readonly(recent_blockhashes::id(), false),
AccountMeta::new_readonly(rent::id(), false),
],
)
}
pub fn nonce(nonce_pubkey: &Pubkey) -> Instruction { pub fn nonce(nonce_pubkey: &Pubkey) -> Instruction {
Instruction::new( Instruction::new(
id(), id(),
@ -113,7 +134,6 @@ pub fn process_instruction(
match limited_deserialize(data)? { match limited_deserialize(data)? {
NonceInstruction::Nonce => me.nonce( NonceInstruction::Nonce => me.nonce(
&RecentBlockhashes::from_keyed_account(next_keyed_account(keyed_accounts)?)?, &RecentBlockhashes::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
&Rent::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
&signers, &signers,
), ),
NonceInstruction::Withdraw(lamports) => { NonceInstruction::Withdraw(lamports) => {
@ -126,13 +146,22 @@ pub fn process_instruction(
&signers, &signers,
) )
} }
NonceInstruction::Initialize => me.initialize(
&RecentBlockhashes::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
&Rent::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
&signers,
),
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{account::Account, hash::Hash, nonce_state, system_program, sysvar}; use crate::{
account::Account,
hash::{hash, Hash},
nonce_state, system_program, sysvar,
};
use bincode::serialize; use bincode::serialize;
fn process_instruction(instruction: &Instruction) -> Result<(), InstructionError> { fn process_instruction(instruction: &Instruction) -> Result<(), InstructionError> {
@ -232,50 +261,43 @@ mod tests {
); );
} }
#[test]
fn test_process_nonce_ix_bad_rent_state_fail() {
assert_eq!(
super::process_instruction(
&Pubkey::default(),
&mut [
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),),
KeyedAccount::new(
&sysvar::recent_blockhashes::id(),
false,
&mut sysvar::recent_blockhashes::create_account(1),
),
KeyedAccount::new(&sysvar::rent::id(), false, &mut Account::default(),),
],
&serialize(&NonceInstruction::Nonce).unwrap(),
),
Err(InstructionError::InvalidArgument),
);
}
#[test] #[test]
fn test_process_nonce_ix_ok() { fn test_process_nonce_ix_ok() {
let mut nonce_acc = nonce_state::create_account(1_000_000);
super::process_instruction(
&Pubkey::default(),
&mut [
KeyedAccount::new(&Pubkey::default(), true, &mut nonce_acc),
KeyedAccount::new(
&sysvar::recent_blockhashes::id(),
false,
&mut sysvar::recent_blockhashes::create_account_with_data(
1,
vec![(0u64, &Hash::default()); 32].into_iter(),
),
),
KeyedAccount::new(
&sysvar::rent::id(),
false,
&mut sysvar::rent::create_account(1, &Rent::default()),
),
],
&serialize(&NonceInstruction::Initialize).unwrap(),
)
.unwrap();
assert_eq!( assert_eq!(
super::process_instruction( super::process_instruction(
&Pubkey::default(), &Pubkey::default(),
&mut [ &mut [
KeyedAccount::new( KeyedAccount::new(&Pubkey::default(), true, &mut nonce_acc,),
&Pubkey::default(),
true,
&mut nonce_state::create_account(1_000_000),
),
KeyedAccount::new( KeyedAccount::new(
&sysvar::recent_blockhashes::id(), &sysvar::recent_blockhashes::id(),
false, false,
&mut sysvar::recent_blockhashes::create_account_with_data( &mut sysvar::recent_blockhashes::create_account_with_data(
1, 1,
vec![(0u64, &Hash::default()); 32].into_iter(), vec![(0u64, &hash(&serialize(&0).unwrap())); 32].into_iter(),
), ),
), ),
KeyedAccount::new(
&sysvar::rent::id(),
false,
&mut sysvar::rent::create_account(1, &Rent::default()),
),
], ],
&serialize(&NonceInstruction::Nonce).unwrap(), &serialize(&NonceInstruction::Nonce).unwrap(),
), ),
@ -339,27 +361,6 @@ mod tests {
); );
} }
#[test]
fn test_process_withdraw_ix_bad_rent_state_fail() {
assert_eq!(
super::process_instruction(
&Pubkey::default(),
&mut [
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),),
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default(),),
KeyedAccount::new(
&sysvar::recent_blockhashes::id(),
false,
&mut Account::default(),
),
KeyedAccount::new(&sysvar::rent::id(), false, &mut Account::default(),),
],
&serialize(&NonceInstruction::Withdraw(42)).unwrap(),
),
Err(InstructionError::InvalidArgument),
);
}
#[test] #[test]
fn test_process_withdraw_ix_ok() { fn test_process_withdraw_ix_ok() {
assert_eq!( assert_eq!(

View File

@ -44,7 +44,6 @@ pub trait NonceAccount {
fn nonce( fn nonce(
&mut self, &mut self,
recent_blockhashes: &RecentBlockhashes, recent_blockhashes: &RecentBlockhashes,
rent: &Rent,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
fn withdraw( fn withdraw(
@ -55,13 +54,18 @@ pub trait NonceAccount {
rent: &Rent, rent: &Rent,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
fn initialize(
&mut self,
recent_blockhashes: &RecentBlockhashes,
rent: &Rent,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>;
} }
impl<'a> NonceAccount for KeyedAccount<'a> { impl<'a> NonceAccount for KeyedAccount<'a> {
fn nonce( fn nonce(
&mut self, &mut self,
recent_blockhashes: &RecentBlockhashes, recent_blockhashes: &RecentBlockhashes,
rent: &Rent,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if recent_blockhashes.is_empty() { if recent_blockhashes.is_empty() {
@ -79,13 +83,7 @@ impl<'a> NonceAccount for KeyedAccount<'a> {
} }
meta meta
} }
NonceState::Uninitialized => { _ => return Err(NonceError::BadAccountState.into()),
let min_balance = rent.minimum_balance(self.account.data.len());
if self.account.lamports < min_balance {
return Err(InstructionError::InsufficientFunds);
}
Meta::new()
}
}; };
self.set_state(&NonceState::Initialized(meta, recent_blockhashes[0])) self.set_state(&NonceState::Initialized(meta, recent_blockhashes[0]))
@ -129,6 +127,34 @@ impl<'a> NonceAccount for KeyedAccount<'a> {
Ok(()) Ok(())
} }
fn initialize(
&mut self,
recent_blockhashes: &RecentBlockhashes,
rent: &Rent,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
if recent_blockhashes.is_empty() {
return Err(NonceError::NoRecentBlockhashes.into());
}
if !signers.contains(self.unsigned_key()) {
return Err(InstructionError::MissingRequiredSignature);
}
let meta = match self.state()? {
NonceState::Uninitialized => {
let min_balance = rent.minimum_balance(self.account.data.len());
if self.account.lamports < min_balance {
return Err(InstructionError::InsufficientFunds);
}
Meta::new()
}
_ => return Err(NonceError::BadAccountState.into()),
};
self.set_state(&NonceState::Initialized(meta, recent_blockhashes[0]))
}
} }
pub fn create_account(lamports: u64) -> Account { pub fn create_account(lamports: u64) -> Account {
@ -189,24 +215,20 @@ mod test {
assert_eq!(state, NonceState::Uninitialized); assert_eq!(state, NonceState::Uninitialized);
let recent_blockhashes = create_test_recent_blockhashes(95); let recent_blockhashes = create_test_recent_blockhashes(95);
keyed_account keyed_account
.nonce(&recent_blockhashes, &rent, &signers) .initialize(&recent_blockhashes, &rent, &signers)
.unwrap(); .unwrap();
let state: NonceState = keyed_account.state().unwrap(); let state: NonceState = keyed_account.state().unwrap();
let stored = recent_blockhashes[0]; let stored = recent_blockhashes[0];
// First nonce instruction drives state from Uninitialized to Initialized // First nonce instruction drives state from Uninitialized to Initialized
assert_eq!(state, NonceState::Initialized(meta, stored)); assert_eq!(state, NonceState::Initialized(meta, stored));
let recent_blockhashes = create_test_recent_blockhashes(63); let recent_blockhashes = create_test_recent_blockhashes(63);
keyed_account keyed_account.nonce(&recent_blockhashes, &signers).unwrap();
.nonce(&recent_blockhashes, &rent, &signers)
.unwrap();
let state: NonceState = keyed_account.state().unwrap(); let state: NonceState = keyed_account.state().unwrap();
let stored = recent_blockhashes[0]; let stored = recent_blockhashes[0];
// Second nonce instruction consumes and replaces stored nonce // Second nonce instruction consumes and replaces stored nonce
assert_eq!(state, NonceState::Initialized(meta, stored)); assert_eq!(state, NonceState::Initialized(meta, stored));
let recent_blockhashes = create_test_recent_blockhashes(31); let recent_blockhashes = create_test_recent_blockhashes(31);
keyed_account keyed_account.nonce(&recent_blockhashes, &signers).unwrap();
.nonce(&recent_blockhashes, &rent, &signers)
.unwrap();
let state: NonceState = keyed_account.state().unwrap(); let state: NonceState = keyed_account.state().unwrap();
let stored = recent_blockhashes[0]; let stored = recent_blockhashes[0];
// Third nonce instruction for fun and profit // Third nonce instruction for fun and profit
@ -237,21 +259,6 @@ mod test {
}) })
} }
#[test]
fn nonce_inx_uninitialized_account_not_signer_fail() {
let rent = Rent {
lamports_per_byte_year: 42,
..Rent::default()
};
let min_lamports = rent.minimum_balance(NonceState::size());
with_test_keyed_account(min_lamports + 42, false, |nonce_account| {
let signers = HashSet::new();
let recent_blockhashes = create_test_recent_blockhashes(0);
let result = nonce_account.nonce(&recent_blockhashes, &rent, &signers);
assert_eq!(result, Err(InstructionError::MissingRequiredSignature),);
})
}
#[test] #[test]
fn nonce_inx_initialized_account_not_signer_fail() { fn nonce_inx_initialized_account_not_signer_fail() {
let rent = Rent { let rent = Rent {
@ -266,7 +273,7 @@ mod test {
let recent_blockhashes = create_test_recent_blockhashes(31); let recent_blockhashes = create_test_recent_blockhashes(31);
let stored = recent_blockhashes[0]; let stored = recent_blockhashes[0];
nonce_account nonce_account
.nonce(&recent_blockhashes, &rent, &signers) .initialize(&recent_blockhashes, &rent, &signers)
.unwrap(); .unwrap();
let pubkey = nonce_account.account.owner.clone(); let pubkey = nonce_account.account.owner.clone();
let mut nonce_account = KeyedAccount::new(&pubkey, false, nonce_account.account); let mut nonce_account = KeyedAccount::new(&pubkey, false, nonce_account.account);
@ -274,7 +281,7 @@ mod test {
assert_eq!(state, NonceState::Initialized(meta, stored)); assert_eq!(state, NonceState::Initialized(meta, stored));
let signers = HashSet::new(); let signers = HashSet::new();
let recent_blockhashes = create_test_recent_blockhashes(0); let recent_blockhashes = create_test_recent_blockhashes(0);
let result = nonce_account.nonce(&recent_blockhashes, &rent, &signers); let result = nonce_account.nonce(&recent_blockhashes, &signers);
assert_eq!(result, Err(InstructionError::MissingRequiredSignature),); assert_eq!(result, Err(InstructionError::MissingRequiredSignature),);
}) })
} }
@ -289,8 +296,12 @@ mod test {
with_test_keyed_account(min_lamports + 42, true, |keyed_account| { with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
let mut signers = HashSet::new(); let mut signers = HashSet::new();
signers.insert(keyed_account.signer_key().unwrap().clone()); signers.insert(keyed_account.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(0);
keyed_account
.initialize(&recent_blockhashes, &rent, &signers)
.unwrap();
let recent_blockhashes = RecentBlockhashes::from_iter(vec![].into_iter()); let recent_blockhashes = RecentBlockhashes::from_iter(vec![].into_iter());
let result = keyed_account.nonce(&recent_blockhashes, &rent, &signers); let result = keyed_account.nonce(&recent_blockhashes, &signers);
assert_eq!(result, Err(NonceError::NoRecentBlockhashes.into())); assert_eq!(result, Err(NonceError::NoRecentBlockhashes.into()));
}) })
} }
@ -307,26 +318,26 @@ mod test {
signers.insert(keyed_account.signer_key().unwrap().clone()); signers.insert(keyed_account.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(63); let recent_blockhashes = create_test_recent_blockhashes(63);
keyed_account keyed_account
.nonce(&recent_blockhashes, &rent, &signers) .initialize(&recent_blockhashes, &rent, &signers)
.unwrap(); .unwrap();
let result = keyed_account.nonce(&recent_blockhashes, &rent, &signers); let result = keyed_account.nonce(&recent_blockhashes, &signers);
assert_eq!(result, Err(NonceError::NotExpired.into())); assert_eq!(result, Err(NonceError::NotExpired.into()));
}) })
} }
#[test] #[test]
fn nonce_inx_uninitialized_acc_insuff_funds_fail() { fn nonce_inx_uninitialized_account_fail() {
let rent = Rent { let rent = Rent {
lamports_per_byte_year: 42, lamports_per_byte_year: 42,
..Rent::default() ..Rent::default()
}; };
let min_lamports = rent.minimum_balance(NonceState::size()); let min_lamports = rent.minimum_balance(NonceState::size());
with_test_keyed_account(min_lamports - 42, true, |keyed_account| { with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
let mut signers = HashSet::new(); let mut signers = HashSet::new();
signers.insert(keyed_account.signer_key().unwrap().clone()); signers.insert(keyed_account.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(63); let recent_blockhashes = create_test_recent_blockhashes(63);
let result = keyed_account.nonce(&recent_blockhashes, &rent, &signers); let result = keyed_account.nonce(&recent_blockhashes, &signers);
assert_eq!(result, Err(InstructionError::InsufficientFunds)); assert_eq!(result, Err(NonceError::BadAccountState.into()));
}) })
} }
@ -480,7 +491,7 @@ mod test {
signers.insert(nonce_keyed.signer_key().unwrap().clone()); signers.insert(nonce_keyed.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(31); let recent_blockhashes = create_test_recent_blockhashes(31);
nonce_keyed nonce_keyed
.nonce(&recent_blockhashes, &rent, &signers) .initialize(&recent_blockhashes, &rent, &signers)
.unwrap(); .unwrap();
let state: NonceState = nonce_keyed.state().unwrap(); let state: NonceState = nonce_keyed.state().unwrap();
let stored = recent_blockhashes[0]; let stored = recent_blockhashes[0];
@ -536,7 +547,7 @@ mod test {
signers.insert(nonce_keyed.signer_key().unwrap().clone()); signers.insert(nonce_keyed.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(0); let recent_blockhashes = create_test_recent_blockhashes(0);
nonce_keyed nonce_keyed
.nonce(&recent_blockhashes, &rent, &signers) .initialize(&recent_blockhashes, &rent, &signers)
.unwrap(); .unwrap();
with_test_keyed_account(42, false, |mut to_keyed| { with_test_keyed_account(42, false, |mut to_keyed| {
let mut signers = HashSet::new(); let mut signers = HashSet::new();
@ -566,7 +577,7 @@ mod test {
signers.insert(nonce_keyed.signer_key().unwrap().clone()); signers.insert(nonce_keyed.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(95); let recent_blockhashes = create_test_recent_blockhashes(95);
nonce_keyed nonce_keyed
.nonce(&recent_blockhashes, &rent, &signers) .initialize(&recent_blockhashes, &rent, &signers)
.unwrap(); .unwrap();
with_test_keyed_account(42, false, |mut to_keyed| { with_test_keyed_account(42, false, |mut to_keyed| {
let recent_blockhashes = create_test_recent_blockhashes(63); let recent_blockhashes = create_test_recent_blockhashes(63);
@ -597,7 +608,7 @@ mod test {
signers.insert(nonce_keyed.signer_key().unwrap().clone()); signers.insert(nonce_keyed.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(95); let recent_blockhashes = create_test_recent_blockhashes(95);
nonce_keyed nonce_keyed
.nonce(&recent_blockhashes, &rent, &signers) .initialize(&recent_blockhashes, &rent, &signers)
.unwrap(); .unwrap();
with_test_keyed_account(42, false, |mut to_keyed| { with_test_keyed_account(42, false, |mut to_keyed| {
let recent_blockhashes = create_test_recent_blockhashes(63); let recent_blockhashes = create_test_recent_blockhashes(63);
@ -615,4 +626,92 @@ mod test {
}) })
}) })
} }
#[test]
fn initialize_inx_ok() {
let rent = Rent {
lamports_per_byte_year: 42,
..Rent::default()
};
let min_lamports = rent.minimum_balance(NonceState::size());
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
let state: NonceState = keyed_account.state().unwrap();
assert_eq!(state, NonceState::Uninitialized);
let mut signers = HashSet::new();
signers.insert(keyed_account.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(0);
let stored = recent_blockhashes[0];
let result = keyed_account.initialize(&recent_blockhashes, &rent, &signers);
assert_eq!(result, Ok(()));
let state: NonceState = keyed_account.state().unwrap();
assert_eq!(state, NonceState::Initialized(Meta::new(), stored));
})
}
#[test]
fn initialize_inx_empty_recent_blockhashes_fail() {
let rent = Rent {
lamports_per_byte_year: 42,
..Rent::default()
};
let min_lamports = rent.minimum_balance(NonceState::size());
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
let mut signers = HashSet::new();
signers.insert(keyed_account.signer_key().unwrap().clone());
let recent_blockhashes = RecentBlockhashes::from_iter(vec![].into_iter());
let result = keyed_account.initialize(&recent_blockhashes, &rent, &signers);
assert_eq!(result, Err(NonceError::NoRecentBlockhashes.into()));
})
}
#[test]
fn initialize_inx_not_signer_fail() {
let rent = Rent {
lamports_per_byte_year: 42,
..Rent::default()
};
let min_lamports = rent.minimum_balance(NonceState::size());
with_test_keyed_account(min_lamports + 42, false, |keyed_account| {
let signers = HashSet::new();
let recent_blockhashes = create_test_recent_blockhashes(0);
let result = keyed_account.initialize(&recent_blockhashes, &rent, &signers);
assert_eq!(result, Err(InstructionError::MissingRequiredSignature));
})
}
#[test]
fn initialize_inx_initialized_account_fail() {
let rent = Rent {
lamports_per_byte_year: 42,
..Rent::default()
};
let min_lamports = rent.minimum_balance(NonceState::size());
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
let mut signers = HashSet::new();
signers.insert(keyed_account.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(31);
keyed_account
.initialize(&recent_blockhashes, &rent, &signers)
.unwrap();
let recent_blockhashes = create_test_recent_blockhashes(0);
let result = keyed_account.initialize(&recent_blockhashes, &rent, &signers);
assert_eq!(result, Err(NonceError::BadAccountState.into()));
})
}
#[test]
fn initialize_inx_uninitialized_acc_insuff_funds_fail() {
let rent = Rent {
lamports_per_byte_year: 42,
..Rent::default()
};
let min_lamports = rent.minimum_balance(NonceState::size());
with_test_keyed_account(min_lamports - 42, true, |keyed_account| {
let mut signers = HashSet::new();
signers.insert(keyed_account.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(63);
let result = keyed_account.initialize(&recent_blockhashes, &rent, &signers);
assert_eq!(result, Err(InstructionError::InsufficientFunds));
})
}
} }