Add SystemInstruction::CreateAccountWithSeed (#7390)

* address generator

* coverage

* fixups

* remove ascii restriction

* illustrate that utf-8 doesn't expand the space
This commit is contained in:
Rob Walker
2019-12-12 11:12:09 -08:00
committed by GitHub
parent 2db28cae41
commit ea0ba19089
2 changed files with 279 additions and 44 deletions

View File

@ -1,23 +1,55 @@
use log::*; use log::*;
use solana_sdk::account::KeyedAccount;
use solana_sdk::instruction::InstructionError;
use solana_sdk::instruction_processor_utils::{limited_deserialize, next_keyed_account};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::system_instruction::{SystemError, SystemInstruction};
use solana_sdk::system_program;
use solana_sdk::sysvar;
// 10 MB use solana_sdk::{
const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024; account::KeyedAccount,
instruction::InstructionError,
instruction_processor_utils::{limited_deserialize, next_keyed_account},
pubkey::Pubkey,
system_instruction::{
create_address_with_seed, SystemError, SystemInstruction, MAX_PERMITTED_DATA_LENGTH,
},
system_program, sysvar,
};
fn create_system_account( fn create_account_with_seed(
from: &mut KeyedAccount,
to: &mut KeyedAccount,
seed: &str,
lamports: u64,
data_length: u64,
program_id: &Pubkey,
) -> Result<(), InstructionError> {
// `from` is the source of the derived address, the caller must have
// signed, even if no lamports will be transferred
if from.signer_key().is_none() {
debug!("CreateAccountWithSeed: from must sign");
return Err(InstructionError::MissingRequiredSignature);
}
// re-derive the address, must match `to`
let address = create_address_with_seed(from.unsigned_key(), seed, program_id)?;
if to.unsigned_key() != &address {
debug!(
"CreateAccountWithSeed: invalid argument; generated {} does not match to {}",
address,
to.unsigned_key()
);
return Err(SystemError::AddressWithSeedMismatch.into());
}
// all of finish_create_account's rules apply
finish_create_account(from, to, lamports, data_length, program_id)
}
fn create_account(
from: &mut KeyedAccount, from: &mut KeyedAccount,
to: &mut KeyedAccount, to: &mut KeyedAccount,
lamports: u64, lamports: u64,
data_length: u64, data_length: u64,
program_id: &Pubkey, program_id: &Pubkey,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
// if lamports == 0, the from account isn't touched // if lamports == 0, the `from` account isn't touched
if lamports != 0 && from.signer_key().is_none() { if lamports != 0 && from.signer_key().is_none() {
debug!("CreateAccount: from must sign"); debug!("CreateAccount: from must sign");
return Err(InstructionError::MissingRequiredSignature); return Err(InstructionError::MissingRequiredSignature);
@ -28,7 +60,17 @@ fn create_system_account(
return Err(InstructionError::MissingRequiredSignature); return Err(InstructionError::MissingRequiredSignature);
} }
// if it looks like the to account is already in use, bail finish_create_account(from, to, lamports, data_length, program_id)
}
fn finish_create_account(
from: &mut KeyedAccount,
to: &mut KeyedAccount,
lamports: u64,
data_length: u64,
program_id: &Pubkey,
) -> Result<(), InstructionError> {
// if it looks like the `to` account is already in use, bail
if to.account.lamports != 0 if to.account.lamports != 0
|| !to.account.data.is_empty() || !to.account.data.is_empty()
|| !system_program::check_id(&to.account.owner) || !system_program::check_id(&to.account.owner)
@ -61,11 +103,18 @@ fn create_system_account(
return Err(SystemError::InvalidAccountDataLength.into()); return Err(SystemError::InvalidAccountDataLength.into());
} }
assign_account_to_program(to, program_id)?; // guard against sysvars being assigned
if sysvar::check_id(&program_id) {
debug!("Assign: program id {} invalid", program_id);
return Err(SystemError::InvalidProgramId.into());
}
to.account.owner = *program_id;
from.account.lamports -= lamports; from.account.lamports -= lamports;
to.account.lamports += lamports; to.account.lamports += lamports;
to.account.data = vec![0; data_length as usize]; to.account.data = vec![0; data_length as usize];
to.account.executable = false; to.account.executable = false;
Ok(()) Ok(())
} }
@ -134,7 +183,17 @@ pub fn process_instruction(
} => { } => {
let from = next_keyed_account(keyed_accounts_iter)?; let from = next_keyed_account(keyed_accounts_iter)?;
let to = next_keyed_account(keyed_accounts_iter)?; let to = next_keyed_account(keyed_accounts_iter)?;
create_system_account(from, to, lamports, space, &program_id) create_account(from, to, lamports, space, &program_id)
}
SystemInstruction::CreateAccountWithSeed {
seed,
lamports,
space,
program_id,
} => {
let from = next_keyed_account(keyed_accounts_iter)?;
let to = next_keyed_account(keyed_accounts_iter)?;
create_account_with_seed(from, to, &seed, lamports, space, &program_id)
} }
SystemInstruction::Assign { program_id } => { SystemInstruction::Assign { program_id } => {
let account = next_keyed_account(keyed_accounts_iter)?; let account = next_keyed_account(keyed_accounts_iter)?;
@ -163,38 +222,120 @@ mod tests {
use solana_sdk::transaction::TransactionError; use solana_sdk::transaction::TransactionError;
#[test] #[test]
fn test_create_system_account() { fn test_create_account() {
let new_program_owner = Pubkey::new(&[9; 32]); let new_program_owner = Pubkey::new(&[9; 32]);
let from = Pubkey::new_rand(); let from = Pubkey::new_rand();
let mut from_account = Account::new(100, 0, &system_program::id());
let to = Pubkey::new_rand(); let to = Pubkey::new_rand();
let mut from_account = Account::new(100, 0, &system_program::id());
let mut to_account = Account::new(0, 0, &Pubkey::default()); let mut to_account = Account::new(0, 0, &Pubkey::default());
assert_eq!( assert_eq!(
create_system_account( process_instruction(
&Pubkey::default(),
&mut [
KeyedAccount::new(&from, true, &mut from_account),
KeyedAccount::new(&to, true, &mut to_account)
],
&bincode::serialize(&SystemInstruction::CreateAccount {
lamports: 50,
space: 2,
program_id: new_program_owner
})
.unwrap()
),
Ok(())
);
assert_eq!(from_account.lamports, 50);
assert_eq!(to_account.lamports, 50);
assert_eq!(to_account.owner, new_program_owner);
assert_eq!(to_account.data, [0, 0]);
}
#[test]
fn test_create_account_with_seed() {
let new_program_owner = Pubkey::new(&[9; 32]);
let from = Pubkey::new_rand();
let seed = "shiny pepper";
let to = create_address_with_seed(&from, seed, &new_program_owner).unwrap();
let mut from_account = Account::new(100, 0, &system_program::id());
let mut to_account = Account::new(0, 0, &Pubkey::default());
assert_eq!(
process_instruction(
&Pubkey::default(),
&mut [
KeyedAccount::new(&from, true, &mut from_account),
KeyedAccount::new(&to, false, &mut to_account)
],
&bincode::serialize(&SystemInstruction::CreateAccountWithSeed {
seed: seed.to_string(),
lamports: 50,
space: 2,
program_id: new_program_owner
})
.unwrap()
),
Ok(())
);
assert_eq!(from_account.lamports, 50);
assert_eq!(to_account.lamports, 50);
assert_eq!(to_account.owner, new_program_owner);
assert_eq!(to_account.data, [0, 0]);
}
#[test]
fn test_create_account_with_seed_mismatch() {
let new_program_owner = Pubkey::new(&[9; 32]);
let from = Pubkey::new_rand();
let seed = "dull boy";
let to = Pubkey::new_rand();
let mut from_account = Account::new(100, 0, &system_program::id());
let mut to_account = Account::new(0, 0, &Pubkey::default());
assert_eq!(
create_account_with_seed(
&mut KeyedAccount::new(&from, true, &mut from_account), &mut KeyedAccount::new(&from, true, &mut from_account),
&mut KeyedAccount::new(&to, true, &mut to_account), &mut KeyedAccount::new(&to, false, &mut to_account),
seed,
50, 50,
2, 2,
&new_program_owner, &new_program_owner,
), ),
Ok(()) Err(SystemError::AddressWithSeedMismatch.into())
); );
assert_eq!(from_account.lamports, 100);
assert_eq!(to_account, Account::default());
}
#[test]
fn test_create_account_with_seed_missing_sig() {
let new_program_owner = Pubkey::new(&[9; 32]);
let from = Pubkey::new_rand();
let seed = "dull boy";
let to = create_address_with_seed(&from, seed, &new_program_owner).unwrap();
let from_lamports = from_account.lamports; let mut from_account = Account::new(100, 0, &system_program::id());
let to_lamports = to_account.lamports; let mut to_account = Account::new(0, 0, &Pubkey::default());
let to_owner = to_account.owner;
let to_data = to_account.data.clone(); assert_eq!(
assert_eq!(from_lamports, 50); create_account_with_seed(
assert_eq!(to_lamports, 50); &mut KeyedAccount::new(&from, false, &mut from_account),
assert_eq!(to_owner, new_program_owner); &mut KeyedAccount::new(&to, false, &mut to_account),
assert_eq!(to_data, [0, 0]); seed,
50,
2,
&new_program_owner,
),
Err(InstructionError::MissingRequiredSignature)
);
assert_eq!(from_account.lamports, 100);
assert_eq!(to_account, Account::default());
} }
#[test] #[test]
fn test_create_with_zero_lamports() { fn test_create_with_zero_lamports() {
// Attempt to create account with zero lamports // create account with zero lamports tranferred
let new_program_owner = Pubkey::new(&[9; 32]); let new_program_owner = Pubkey::new(&[9; 32]);
let from = Pubkey::new_rand(); let from = Pubkey::new_rand();
let mut from_account = Account::new(100, 0, &Pubkey::new_rand()); // not from system account let mut from_account = Account::new(100, 0, &Pubkey::new_rand()); // not from system account
@ -203,7 +344,7 @@ mod tests {
let mut to_account = Account::new(0, 0, &Pubkey::default()); let mut to_account = Account::new(0, 0, &Pubkey::default());
assert_eq!( assert_eq!(
create_system_account( create_account(
&mut KeyedAccount::new(&from, false, &mut from_account), // no signer &mut KeyedAccount::new(&from, false, &mut from_account), // no signer
&mut KeyedAccount::new(&to, true, &mut to_account), &mut KeyedAccount::new(&to, true, &mut to_account),
0, 0,
@ -234,7 +375,7 @@ mod tests {
let mut to_account = Account::new(0, 0, &Pubkey::default()); let mut to_account = Account::new(0, 0, &Pubkey::default());
let unchanged_account = to_account.clone(); let unchanged_account = to_account.clone();
let result = create_system_account( let result = create_account(
&mut KeyedAccount::new(&from, true, &mut from_account), &mut KeyedAccount::new(&from, true, &mut from_account),
&mut KeyedAccount::new(&to, true, &mut to_account), &mut KeyedAccount::new(&to, true, &mut to_account),
150, 150,
@ -255,7 +396,7 @@ mod tests {
let to_account_key = Pubkey::new_rand(); let to_account_key = Pubkey::new_rand();
// Trying to request more data length than permitted will result in failure // Trying to request more data length than permitted will result in failure
let result = create_system_account( let result = create_account(
&mut KeyedAccount::new(&from_account_key, true, &mut from_account), &mut KeyedAccount::new(&from_account_key, true, &mut from_account),
&mut KeyedAccount::new(&to_account_key, true, &mut to_account), &mut KeyedAccount::new(&to_account_key, true, &mut to_account),
50, 50,
@ -269,7 +410,7 @@ mod tests {
); );
// Trying to request equal or less data length than permitted will be successful // Trying to request equal or less data length than permitted will be successful
let result = create_system_account( let result = create_account(
&mut KeyedAccount::new(&from_account_key, true, &mut from_account), &mut KeyedAccount::new(&from_account_key, true, &mut from_account),
&mut KeyedAccount::new(&to_account_key, true, &mut to_account), &mut KeyedAccount::new(&to_account_key, true, &mut to_account),
50, 50,
@ -293,7 +434,7 @@ mod tests {
let mut owned_account = Account::new(0, 0, &original_program_owner); let mut owned_account = Account::new(0, 0, &original_program_owner);
let unchanged_account = owned_account.clone(); let unchanged_account = owned_account.clone();
let result = create_system_account( let result = create_account(
&mut KeyedAccount::new(&from, true, &mut from_account), &mut KeyedAccount::new(&from, true, &mut from_account),
&mut KeyedAccount::new(&owned_key, true, &mut owned_account), &mut KeyedAccount::new(&owned_key, true, &mut owned_account),
50, 50,
@ -308,7 +449,7 @@ mod tests {
let mut owned_account = Account::new(10, 0, &Pubkey::default()); let mut owned_account = Account::new(10, 0, &Pubkey::default());
let unchanged_account = owned_account.clone(); let unchanged_account = owned_account.clone();
let result = create_system_account( let result = create_account(
&mut KeyedAccount::new(&from, true, &mut from_account), &mut KeyedAccount::new(&from, true, &mut from_account),
&mut KeyedAccount::new(&owned_key, true, &mut owned_account), &mut KeyedAccount::new(&owned_key, true, &mut owned_account),
50, 50,
@ -333,7 +474,7 @@ mod tests {
let unchanged_account = owned_account.clone(); let unchanged_account = owned_account.clone();
// Haven't signed from account // Haven't signed from account
let result = create_system_account( let result = create_account(
&mut KeyedAccount::new(&from, false, &mut from_account), &mut KeyedAccount::new(&from, false, &mut from_account),
&mut KeyedAccount::new(&owned_key, true, &mut owned_account), &mut KeyedAccount::new(&owned_key, true, &mut owned_account),
50, 50,
@ -345,7 +486,7 @@ mod tests {
assert_eq!(owned_account, unchanged_account); assert_eq!(owned_account, unchanged_account);
// Haven't signed to account // Haven't signed to account
let result = create_system_account( let result = create_account(
&mut KeyedAccount::new(&from, true, &mut from_account), &mut KeyedAccount::new(&from, true, &mut from_account),
&mut KeyedAccount::new(&owned_key, false, &mut owned_account), &mut KeyedAccount::new(&owned_key, false, &mut owned_account),
50, 50,
@ -357,7 +498,7 @@ mod tests {
assert_eq!(owned_account, unchanged_account); assert_eq!(owned_account, unchanged_account);
// support creation/assignment with zero lamports (ephemeral account) // support creation/assignment with zero lamports (ephemeral account)
let result = create_system_account( let result = create_account(
&mut KeyedAccount::new(&from, false, &mut from_account), &mut KeyedAccount::new(&from, false, &mut from_account),
&mut KeyedAccount::new(&owned_key, true, &mut owned_account), &mut KeyedAccount::new(&owned_key, true, &mut owned_account),
0, 0,
@ -379,7 +520,7 @@ mod tests {
let mut to_account = Account::default(); let mut to_account = Account::default();
// fail to create a sysvar::id() owned account // fail to create a sysvar::id() owned account
let result = create_system_account( let result = create_account(
&mut KeyedAccount::new(&from, true, &mut from_account), &mut KeyedAccount::new(&from, true, &mut from_account),
&mut KeyedAccount::new(&to, true, &mut to_account), &mut KeyedAccount::new(&to, true, &mut to_account),
50, 50,
@ -392,7 +533,7 @@ mod tests {
let mut to_account = Account::default(); let mut to_account = Account::default();
// fail to create an account with a sysvar id // fail to create an account with a sysvar id
let result = create_system_account( let result = create_account(
&mut KeyedAccount::new(&from, true, &mut from_account), &mut KeyedAccount::new(&from, true, &mut from_account),
&mut KeyedAccount::new(&to, true, &mut to_account), &mut KeyedAccount::new(&to, true, &mut to_account),
50, 50,
@ -419,7 +560,7 @@ mod tests {
}; };
let unchanged_account = populated_account.clone(); let unchanged_account = populated_account.clone();
let result = create_system_account( let result = create_account(
&mut KeyedAccount::new(&from, true, &mut from_account), &mut KeyedAccount::new(&from, true, &mut from_account),
&mut KeyedAccount::new(&populated_key, true, &mut populated_account), &mut KeyedAccount::new(&populated_key, true, &mut populated_account),
50, 50,
@ -447,9 +588,13 @@ mod tests {
); );
assert_eq!( assert_eq!(
assign_account_to_program( process_instruction(
&mut KeyedAccount::new(&from, true, &mut from_account), &Pubkey::default(),
&new_program_owner, &mut [KeyedAccount::new(&from, true, &mut from_account)],
&bincode::serialize(&SystemInstruction::Assign {
program_id: new_program_owner
})
.unwrap()
), ),
Ok(()) Ok(())
); );

View File

@ -1,3 +1,4 @@
use crate::hash::hashv;
use crate::instruction::{AccountMeta, Instruction}; use crate::instruction::{AccountMeta, Instruction};
use crate::instruction_processor_utils::DecodeError; use crate::instruction_processor_utils::DecodeError;
use crate::pubkey::Pubkey; use crate::pubkey::Pubkey;
@ -11,6 +12,9 @@ pub enum SystemError {
InvalidProgramId, InvalidProgramId,
InvalidAccountId, InvalidAccountId,
InvalidAccountDataLength, InvalidAccountDataLength,
InvalidSeed,
MaxSeedLengthExceeded,
AddressWithSeedMismatch,
} }
impl<T> DecodeError<T> for SystemError { impl<T> DecodeError<T> for SystemError {
@ -26,6 +30,12 @@ impl std::fmt::Display for SystemError {
} }
impl std::error::Error for SystemError {} impl std::error::Error for SystemError {}
/// maximum length of derived address seed
pub const MAX_ADDRESS_SEED_LEN: usize = 32;
/// maximum permitted size of data: 10 MB
pub const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum SystemInstruction { pub enum SystemInstruction {
/// Create a new account /// Create a new account
@ -46,6 +56,20 @@ pub enum SystemInstruction {
/// * Transaction::keys[0] - source /// * Transaction::keys[0] - source
/// * Transaction::keys[1] - destination /// * Transaction::keys[1] - destination
Transfer { lamports: u64 }, Transfer { lamports: u64 },
/// Create a new account at an address derived from
/// the from account and a seed
/// * Transaction::keys[0] - source
/// * Transaction::keys[1] - new account key
/// * seed - string of ascii chars, no longer than MAX_ADDRESS_SEED_LEN
/// * lamports - number of lamports to transfer to the new account
/// * space - memory to allocate if greater then zero
/// * program_id - the program id of the new account
CreateAccountWithSeed {
seed: String,
lamports: u64,
space: u64,
program_id: Pubkey,
},
} }
pub fn create_account( pub fn create_account(
@ -101,6 +125,20 @@ pub fn transfer_many(from_pubkey: &Pubkey, to_lamports: &[(Pubkey, u64)]) -> Vec
.collect() .collect()
} }
pub fn create_address_with_seed(
from_pubkey: &Pubkey,
seed: &str,
program_id: &Pubkey,
) -> Result<Pubkey, SystemError> {
if seed.len() > MAX_ADDRESS_SEED_LEN {
return Err(SystemError::MaxSeedLengthExceeded);
}
Ok(Pubkey::new(
hashv(&[from_pubkey.as_ref(), seed.as_ref(), program_id.as_ref()]).as_ref(),
))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -109,6 +147,58 @@ mod tests {
instruction.accounts.iter().map(|x| x.pubkey).collect() instruction.accounts.iter().map(|x| x.pubkey).collect()
} }
#[test]
fn test_create_address_with_seed() {
assert!(create_address_with_seed(&Pubkey::new_rand(), "", &Pubkey::new_rand()).is_ok());
assert_eq!(
create_address_with_seed(
&Pubkey::new_rand(),
std::str::from_utf8(&[127; MAX_ADDRESS_SEED_LEN + 1]).unwrap(),
&Pubkey::new_rand()
),
Err(SystemError::MaxSeedLengthExceeded)
);
assert!(create_address_with_seed(
&Pubkey::new_rand(),
"\
\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
",
&Pubkey::new_rand()
)
.is_ok());
// utf-8 abuse ;)
assert_eq!(
create_address_with_seed(
&Pubkey::new_rand(),
"\
x\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
",
&Pubkey::new_rand()
),
Err(SystemError::MaxSeedLengthExceeded)
);
assert!(create_address_with_seed(
&Pubkey::new_rand(),
std::str::from_utf8(&[0; MAX_ADDRESS_SEED_LEN]).unwrap(),
&Pubkey::new_rand(),
)
.is_ok());
assert!(create_address_with_seed(&Pubkey::new_rand(), "", &Pubkey::new_rand(),).is_ok());
assert_eq!(
create_address_with_seed(
&Pubkey::default(),
"limber chicken: 4/45",
&Pubkey::default(),
),
Ok("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"
.parse()
.unwrap())
);
}
#[test] #[test]
fn test_move_many() { fn test_move_many() {
let alice_pubkey = Pubkey::new_rand(); let alice_pubkey = Pubkey::new_rand();