Transactions with multiple programs. (#1381)
Transactions contain a vector of instructions that are executed atomically. Bench shows a 2.3x speed up when using 5 instructions per tx.
This commit is contained in:
parent
a5f07638ec
commit
e7de7c32db
|
@ -63,8 +63,8 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||||
let from: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
let from: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||||
let to: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
let to: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||||
new.keys[0] = Pubkey::new(&from[0..32]);
|
new.account_keys[0] = Pubkey::new(&from[0..32]);
|
||||||
new.keys[1] = Pubkey::new(&to[0..32]);
|
new.account_keys[1] = Pubkey::new(&to[0..32]);
|
||||||
new.signature = Signature::new(&sig[0..64]);
|
new.signature = Signature::new(&sig[0..64]);
|
||||||
new
|
new
|
||||||
}).collect();
|
}).collect();
|
||||||
|
@ -72,7 +72,7 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||||
transactions.iter().for_each(|tx| {
|
transactions.iter().for_each(|tx| {
|
||||||
let fund = Transaction::system_move(
|
let fund = Transaction::system_move(
|
||||||
&mint.keypair(),
|
&mint.keypair(),
|
||||||
tx.keys[0],
|
tx.account_keys[0],
|
||||||
mint_total / txes as i64,
|
mint_total / txes as i64,
|
||||||
mint.last_id(),
|
mint.last_id(),
|
||||||
0,
|
0,
|
||||||
|
@ -108,3 +108,90 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||||
bank.register_entry_id(&mint.last_id());
|
bank.register_entry_id(&mint.last_id());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
|
||||||
|
//use solana::logger;
|
||||||
|
//logger::setup();
|
||||||
|
let progs = 5;
|
||||||
|
let txes = 1000 * NUM_THREADS;
|
||||||
|
let mint_total = 1_000_000_000_000;
|
||||||
|
let mint = Mint::new(mint_total);
|
||||||
|
|
||||||
|
let (verified_sender, verified_receiver) = channel();
|
||||||
|
let bank = Arc::new(Bank::new(&mint));
|
||||||
|
let dummy = Transaction::system_move(
|
||||||
|
&mint.keypair(),
|
||||||
|
mint.keypair().pubkey(),
|
||||||
|
1,
|
||||||
|
mint.last_id(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
let transactions: Vec<_> = (0..txes)
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|_| {
|
||||||
|
let mut new = dummy.clone();
|
||||||
|
let from: Vec<u8> = (0..32).map(|_| thread_rng().gen()).collect();
|
||||||
|
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||||
|
let to: Vec<u8> = (0..32).map(|_| thread_rng().gen()).collect();
|
||||||
|
new.account_keys[0] = Pubkey::new(&from[0..32]);
|
||||||
|
new.account_keys[1] = Pubkey::new(&to[0..32]);
|
||||||
|
let prog = new.instructions[0].clone();
|
||||||
|
for i in 1..progs {
|
||||||
|
//generate programs that spend to random keys
|
||||||
|
let to: Vec<u8> = (0..32).map(|_| thread_rng().gen()).collect();
|
||||||
|
let to_key = Pubkey::new(&to[0..32]);
|
||||||
|
new.account_keys.push(to_key);
|
||||||
|
assert_eq!(new.account_keys.len(), i + 2);
|
||||||
|
new.instructions.push(prog.clone());
|
||||||
|
assert_eq!(new.instructions.len(), i + 1);
|
||||||
|
new.instructions[i].accounts[1] = 1 + i as u8;
|
||||||
|
assert_eq!(new.key(i, 1), Some(&to_key));
|
||||||
|
assert_eq!(
|
||||||
|
new.account_keys[new.instructions[i].accounts[1] as usize],
|
||||||
|
to_key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(new.instructions.len(), progs);
|
||||||
|
new.signature = Signature::new(&sig[0..64]);
|
||||||
|
new
|
||||||
|
}).collect();
|
||||||
|
transactions.iter().for_each(|tx| {
|
||||||
|
let fund = Transaction::system_move(
|
||||||
|
&mint.keypair(),
|
||||||
|
tx.account_keys[0],
|
||||||
|
mint_total / txes as i64,
|
||||||
|
mint.last_id(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
assert!(bank.process_transaction(&fund).is_ok());
|
||||||
|
});
|
||||||
|
//sanity check, make sure all the transactions can execute sequentially
|
||||||
|
transactions.iter().for_each(|tx| {
|
||||||
|
let res = bank.process_transaction(&tx);
|
||||||
|
assert!(res.is_ok(), "sanity test transactions");
|
||||||
|
});
|
||||||
|
bank.clear_signatures();
|
||||||
|
//sanity check, make sure all the transactions can execute in parallel
|
||||||
|
let res = bank.process_transactions(&transactions);
|
||||||
|
for r in res {
|
||||||
|
assert!(r.is_ok(), "sanity parallel execution");
|
||||||
|
}
|
||||||
|
bank.clear_signatures();
|
||||||
|
let verified: Vec<_> = to_packets_chunked(&transactions.clone(), 96)
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| {
|
||||||
|
let len = x.read().unwrap().packets.len();
|
||||||
|
(x, iter::repeat(1).take(len).collect())
|
||||||
|
}).collect();
|
||||||
|
let (_stage, signal_receiver) = BankingStage::new(&bank, verified_receiver, Default::default());
|
||||||
|
bencher.iter(move || {
|
||||||
|
for v in verified.chunks(verified.len() / NUM_THREADS) {
|
||||||
|
verified_sender.send(v.to_vec()).unwrap();
|
||||||
|
}
|
||||||
|
check_txs(&signal_receiver, txes);
|
||||||
|
bank.clear_signatures();
|
||||||
|
// make sure the transactions are still valid
|
||||||
|
bank.register_entry_id(&mint.last_id());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
273
src/bank.rs
273
src/bank.rs
|
@ -6,7 +6,6 @@
|
||||||
use bincode::deserialize;
|
use bincode::deserialize;
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
use budget_program::BudgetState;
|
use budget_program::BudgetState;
|
||||||
use budget_transaction::BudgetTransaction;
|
|
||||||
use counter::Counter;
|
use counter::Counter;
|
||||||
use dynamic_program::DynamicProgram;
|
use dynamic_program::DynamicProgram;
|
||||||
use entry::Entry;
|
use entry::Entry;
|
||||||
|
@ -68,23 +67,23 @@ pub enum BankError {
|
||||||
/// Proof of History verification failed.
|
/// Proof of History verification failed.
|
||||||
LedgerVerificationFailed,
|
LedgerVerificationFailed,
|
||||||
/// Contract's transaction token balance does not equal the balance after the transaction
|
/// Contract's transaction token balance does not equal the balance after the transaction
|
||||||
UnbalancedTransaction,
|
UnbalancedTransaction(u8),
|
||||||
/// Contract's transactions resulted in an account with a negative balance
|
/// Contract's transactions resulted in an account with a negative balance
|
||||||
/// The difference from InsufficientFundsForFee is that the transaction was executed by the
|
/// The difference from InsufficientFundsForFee is that the transaction was executed by the
|
||||||
/// contract
|
/// contract
|
||||||
ResultWithNegativeTokens,
|
ResultWithNegativeTokens(u8),
|
||||||
|
|
||||||
/// Contract id is unknown
|
/// Contract id is unknown
|
||||||
UnknownContractId,
|
UnknownContractId(u8),
|
||||||
|
|
||||||
/// Contract modified an accounts contract id
|
/// Contract modified an accounts contract id
|
||||||
ModifiedContractId,
|
ModifiedContractId(u8),
|
||||||
|
|
||||||
/// Contract spent the tokens of an account that doesn't belong to it
|
/// Contract spent the tokens of an account that doesn't belong to it
|
||||||
ExternalAccountTokenSpend,
|
ExternalAccountTokenSpend(u8),
|
||||||
|
|
||||||
/// The program returned an error
|
/// The program returned an error
|
||||||
ProgramRuntimeError,
|
ProgramRuntimeError(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, BankError>;
|
pub type Result<T> = result::Result<T, BankError>;
|
||||||
|
@ -94,7 +93,6 @@ type SignatureStatusMap = HashMap<Signature, Result<()>>;
|
||||||
struct ErrorCounters {
|
struct ErrorCounters {
|
||||||
account_not_found_validator: usize,
|
account_not_found_validator: usize,
|
||||||
account_not_found_leader: usize,
|
account_not_found_leader: usize,
|
||||||
account_not_found_vote: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The state of all accounts and contracts after processing its entries.
|
/// The state of all accounts and contracts after processing its entries.
|
||||||
|
@ -294,24 +292,18 @@ impl Bank {
|
||||||
error_counters: &mut ErrorCounters,
|
error_counters: &mut ErrorCounters,
|
||||||
) -> Result<Vec<Account>> {
|
) -> Result<Vec<Account>> {
|
||||||
// Copy all the accounts
|
// Copy all the accounts
|
||||||
if accounts.get(&tx.keys[0]).is_none() {
|
if accounts.get(&tx.account_keys[0]).is_none() {
|
||||||
if !self.is_leader {
|
if !self.is_leader {
|
||||||
error_counters.account_not_found_validator += 1;
|
error_counters.account_not_found_validator += 1;
|
||||||
} else {
|
} else {
|
||||||
error_counters.account_not_found_leader += 1;
|
error_counters.account_not_found_leader += 1;
|
||||||
}
|
}
|
||||||
if BudgetState::check_id(&tx.program_id) {
|
|
||||||
use budget_instruction::Instruction;
|
|
||||||
if let Some(Instruction::NewVote(_vote)) = tx.instruction() {
|
|
||||||
error_counters.account_not_found_vote += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(BankError::AccountNotFound)
|
Err(BankError::AccountNotFound)
|
||||||
} else if accounts.get(&tx.keys[0]).unwrap().tokens < tx.fee {
|
} else if accounts.get(&tx.account_keys[0]).unwrap().tokens < tx.fee {
|
||||||
Err(BankError::InsufficientFundsForFee)
|
Err(BankError::InsufficientFundsForFee)
|
||||||
} else {
|
} else {
|
||||||
let mut called_accounts: Vec<Account> = tx
|
let mut called_accounts: Vec<Account> = tx
|
||||||
.keys
|
.account_keys
|
||||||
.iter()
|
.iter()
|
||||||
.map(|key| accounts.get(key).cloned().unwrap_or_default())
|
.map(|key| accounts.get(key).cloned().unwrap_or_default())
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -335,7 +327,8 @@ impl Bank {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_transaction(
|
pub fn verify_transaction(
|
||||||
tx: &Transaction,
|
program_index: usize,
|
||||||
|
tx_program_id: &Pubkey,
|
||||||
pre_program_id: &Pubkey,
|
pre_program_id: &Pubkey,
|
||||||
pre_tokens: i64,
|
pre_tokens: i64,
|
||||||
account: &Account,
|
account: &Account,
|
||||||
|
@ -343,84 +336,138 @@ impl Bank {
|
||||||
// Verify the transaction
|
// Verify the transaction
|
||||||
// make sure that program_id is still the same or this was just assigned by the system call contract
|
// make sure that program_id is still the same or this was just assigned by the system call contract
|
||||||
if !((*pre_program_id == account.program_id)
|
if !((*pre_program_id == account.program_id)
|
||||||
|| (SystemProgram::check_id(&tx.program_id)
|
|| (SystemProgram::check_id(&tx_program_id)
|
||||||
&& SystemProgram::check_id(&pre_program_id)))
|
&& SystemProgram::check_id(&pre_program_id)))
|
||||||
{
|
{
|
||||||
//TODO, this maybe redundant bpf should be able to guarantee this property
|
//TODO, this maybe redundant bpf should be able to guarantee this property
|
||||||
return Err(BankError::ModifiedContractId);
|
return Err(BankError::ModifiedContractId(program_index as u8));
|
||||||
}
|
}
|
||||||
// For accounts unassigned to the contract, the individual balance of each accounts cannot decrease.
|
// For accounts unassigned to the contract, the individual balance of each accounts cannot decrease.
|
||||||
if tx.program_id != account.program_id && pre_tokens > account.tokens {
|
if *tx_program_id != account.program_id && pre_tokens > account.tokens {
|
||||||
return Err(BankError::ExternalAccountTokenSpend);
|
return Err(BankError::ExternalAccountTokenSpend(program_index as u8));
|
||||||
}
|
}
|
||||||
if account.tokens < 0 {
|
if account.tokens < 0 {
|
||||||
return Err(BankError::ResultWithNegativeTokens);
|
return Err(BankError::ResultWithNegativeTokens(program_index as u8));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loaded_contract(&self, tx: &Transaction, accounts: &mut [Account]) -> bool {
|
fn loaded_contract(
|
||||||
|
&self,
|
||||||
|
tx_program_id: &Pubkey,
|
||||||
|
tx: &Transaction,
|
||||||
|
program_index: usize,
|
||||||
|
accounts: &mut [&mut Account],
|
||||||
|
) -> bool {
|
||||||
let loaded_contracts = self.loaded_contracts.write().unwrap();
|
let loaded_contracts = self.loaded_contracts.write().unwrap();
|
||||||
match loaded_contracts.get(&tx.program_id) {
|
match loaded_contracts.get(&tx_program_id) {
|
||||||
Some(dc) => {
|
Some(dc) => {
|
||||||
let mut infos: Vec<_> = (&tx.keys)
|
let mut infos: Vec<_> = (&tx.account_keys)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(accounts)
|
.zip(accounts)
|
||||||
.map(|(key, account)| KeyedAccount { key, account })
|
.map(|(key, account)| KeyedAccount { key, account })
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
dc.call(&mut infos, &tx.userdata);
|
dc.call(&mut infos, tx.userdata(program_index));
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a transaction.
|
/// Execute a function with a subset of accounts as writable references.
|
||||||
/// This method calls the contract's process_transaction method and verifies that the result of
|
/// Since the subset can point to the same references, in any order there is no way
|
||||||
/// the contract does not violate the bank's accounting rules.
|
/// for the borrow checker to track them with regards to the original set.
|
||||||
|
fn with_subset<F, A>(accounts: &mut [Account], ixes: &[u8], func: F) -> A
|
||||||
|
where
|
||||||
|
F: Fn(&mut [&mut Account]) -> A,
|
||||||
|
{
|
||||||
|
let mut subset: Vec<&mut Account> = ixes
|
||||||
|
.iter()
|
||||||
|
.map(|ix| {
|
||||||
|
let ptr = &mut accounts[*ix as usize] as *mut Account;
|
||||||
|
// lifetime of this unsafe is only within the scope of the closure
|
||||||
|
// there is no way to reorder them without breaking borrow checker rules
|
||||||
|
unsafe { &mut *ptr }
|
||||||
|
}).collect();
|
||||||
|
func(&mut subset)
|
||||||
|
}
|
||||||
|
/// Execute an instruction
|
||||||
|
/// This method calls the instruction's program entry pont method and verifies that the result of
|
||||||
|
/// the call does not violate the bank's accounting rules.
|
||||||
/// The accounts are committed back to the bank only if this function returns Ok(_).
|
/// The accounts are committed back to the bank only if this function returns Ok(_).
|
||||||
fn execute_transaction(&self, tx: &Transaction, accounts: &mut [Account]) -> Result<()> {
|
fn execute_instruction(
|
||||||
let pre_total: i64 = accounts.iter().map(|a| a.tokens).sum();
|
&self,
|
||||||
let pre_data: Vec<_> = accounts
|
tx: &Transaction,
|
||||||
|
program_index: usize,
|
||||||
|
program_accounts: &mut [&mut Account],
|
||||||
|
) -> Result<()> {
|
||||||
|
let tx_program_id = tx.program_id(program_index);
|
||||||
|
// TODO: the runtime should be checking read/write access to memory
|
||||||
|
// we are trusting the hard coded contracts not to clobber or allocate
|
||||||
|
let pre_total: i64 = program_accounts.iter().map(|a| a.tokens).sum();
|
||||||
|
let pre_data: Vec<_> = program_accounts
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|a| (a.program_id, a.tokens))
|
.map(|a| (a.program_id, a.tokens))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Call the contract method
|
// Call the contract method
|
||||||
// It's up to the contract to implement its own rules on moving funds
|
// It's up to the contract to implement its own rules on moving funds
|
||||||
if SystemProgram::check_id(&tx.program_id) {
|
if SystemProgram::check_id(&tx_program_id) {
|
||||||
SystemProgram::process_transaction(&tx, accounts, &self.loaded_contracts)
|
SystemProgram::process_transaction(
|
||||||
} else if BudgetState::check_id(&tx.program_id) {
|
&tx,
|
||||||
// TODO: the runtime should be checking read/write access to memory
|
program_index,
|
||||||
// we are trusting the hard coded contracts not to clobber or allocate
|
program_accounts,
|
||||||
if BudgetState::process_transaction(&tx, accounts).is_err() {
|
&self.loaded_contracts,
|
||||||
return Err(BankError::ProgramRuntimeError);
|
)
|
||||||
|
} else if BudgetState::check_id(&tx_program_id) {
|
||||||
|
if BudgetState::process_transaction(&tx, program_index, program_accounts).is_err() {
|
||||||
|
return Err(BankError::ProgramRuntimeError(program_index as u8));
|
||||||
}
|
}
|
||||||
} else if StorageProgram::check_id(&tx.program_id) {
|
} else if StorageProgram::check_id(&tx_program_id) {
|
||||||
if StorageProgram::process_transaction(&tx, accounts).is_err() {
|
if StorageProgram::process_transaction(&tx, program_index, program_accounts).is_err() {
|
||||||
return Err(BankError::ProgramRuntimeError);
|
return Err(BankError::ProgramRuntimeError(program_index as u8));
|
||||||
}
|
}
|
||||||
} else if TicTacToeProgram::check_id(&tx.program_id) {
|
} else if TicTacToeProgram::check_id(&tx_program_id) {
|
||||||
if TicTacToeProgram::process_transaction(&tx, accounts).is_err() {
|
if TicTacToeProgram::process_transaction(&tx, program_index, program_accounts).is_err()
|
||||||
return Err(BankError::ProgramRuntimeError);
|
{
|
||||||
|
return Err(BankError::ProgramRuntimeError(program_index as u8));
|
||||||
}
|
}
|
||||||
} else if self.loaded_contract(&tx, accounts) {
|
} else if self.loaded_contract(tx_program_id, tx, program_index, program_accounts) {
|
||||||
} else {
|
} else {
|
||||||
return Err(BankError::UnknownContractId);
|
return Err(BankError::UnknownContractId(program_index as u8));
|
||||||
}
|
}
|
||||||
// Verify the transaction
|
// Verify the transaction
|
||||||
for ((pre_program_id, pre_tokens), post_account) in pre_data.iter().zip(accounts.iter()) {
|
for ((pre_program_id, pre_tokens), post_account) in
|
||||||
Self::verify_transaction(&tx, pre_program_id, *pre_tokens, post_account)?;
|
pre_data.iter().zip(program_accounts.iter())
|
||||||
|
{
|
||||||
|
Self::verify_transaction(
|
||||||
|
program_index,
|
||||||
|
&tx_program_id,
|
||||||
|
pre_program_id,
|
||||||
|
*pre_tokens,
|
||||||
|
post_account,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
// The total sum of all the tokens in all the pages cannot change.
|
// The total sum of all the tokens in all the pages cannot change.
|
||||||
let post_total: i64 = accounts.iter().map(|a| a.tokens).sum();
|
let post_total: i64 = program_accounts.iter().map(|a| a.tokens).sum();
|
||||||
if pre_total != post_total {
|
if pre_total != post_total {
|
||||||
Err(BankError::UnbalancedTransaction)
|
Err(BankError::UnbalancedTransaction(program_index as u8))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Execute a transaction.
|
||||||
|
/// This method calls each instruction in the transaction over the set of loaded Accounts
|
||||||
|
/// The accounts are committed back to the bank only if every instruction succeeds
|
||||||
|
fn execute_transaction(&self, tx: &Transaction, tx_accounts: &mut [Account]) -> Result<()> {
|
||||||
|
for (program_index, prog) in tx.instructions.iter().enumerate() {
|
||||||
|
Self::with_subset(tx_accounts, &prog.accounts, |program_accounts| {
|
||||||
|
self.execute_instruction(tx, program_index, program_accounts)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn store_accounts(
|
pub fn store_accounts(
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
|
@ -435,7 +482,7 @@ impl Bank {
|
||||||
|
|
||||||
let tx = &txs[i];
|
let tx = &txs[i];
|
||||||
let acc = racc.as_ref().unwrap();
|
let acc = racc.as_ref().unwrap();
|
||||||
for (key, account) in tx.keys.iter().zip(acc.iter()) {
|
for (key, account) in tx.account_keys.iter().zip(acc.iter()) {
|
||||||
//purge if 0
|
//purge if 0
|
||||||
if account.tokens == 0 {
|
if account.tokens == 0 {
|
||||||
accounts.remove(&key);
|
accounts.remove(&key);
|
||||||
|
@ -507,10 +554,6 @@ impl Bank {
|
||||||
"bank-appy_debits-account_not_found-leader",
|
"bank-appy_debits-account_not_found-leader",
|
||||||
error_counters.account_not_found_leader
|
error_counters.account_not_found_leader
|
||||||
);
|
);
|
||||||
inc_new_counter_info!(
|
|
||||||
"bank-appy_debits-vote_account_not_found",
|
|
||||||
error_counters.account_not_found_vote
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let cur_tx_count = self.transaction_count.load(Ordering::Relaxed);
|
let cur_tx_count = self.transaction_count.load(Ordering::Relaxed);
|
||||||
|
@ -611,8 +654,8 @@ impl Bank {
|
||||||
.expect("invalid ledger: need at least 2 entries");
|
.expect("invalid ledger: need at least 2 entries");
|
||||||
{
|
{
|
||||||
let tx = &entry1.transactions[0];
|
let tx = &entry1.transactions[0];
|
||||||
assert!(SystemProgram::check_id(&tx.program_id), "Invalid ledger");
|
assert!(SystemProgram::check_id(tx.program_id(0)), "Invalid ledger");
|
||||||
let instruction: SystemProgram = deserialize(&tx.userdata).unwrap();
|
let instruction: SystemProgram = deserialize(tx.userdata(0)).unwrap();
|
||||||
let deposit = if let SystemProgram::Move { tokens } = instruction {
|
let deposit = if let SystemProgram::Move { tokens } = instruction {
|
||||||
Some(tokens)
|
Some(tokens)
|
||||||
} else {
|
} else {
|
||||||
|
@ -620,7 +663,9 @@ impl Bank {
|
||||||
}.expect("invalid ledger, needs to start with a contract");
|
}.expect("invalid ledger, needs to start with a contract");
|
||||||
{
|
{
|
||||||
let mut accounts = self.accounts.write().unwrap();
|
let mut accounts = self.accounts.write().unwrap();
|
||||||
let account = accounts.entry(tx.keys[0]).or_insert_with(Account::default);
|
let account = accounts
|
||||||
|
.entry(tx.account_keys[0])
|
||||||
|
.or_insert_with(Account::default);
|
||||||
account.tokens += deposit;
|
account.tokens += deposit;
|
||||||
trace!("applied genesis payment {:?} => {:?}", deposit, account);
|
trace!("applied genesis payment {:?} => {:?}", deposit, account);
|
||||||
}
|
}
|
||||||
|
@ -700,6 +745,14 @@ impl Bank {
|
||||||
self.get_signature_status(signature) != Err(BankError::SignatureNotFound)
|
self.get_signature_status(signature) != Err(BankError::SignatureNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_signature(&self, last_id: &Hash, signature: &Signature) -> Option<Result<()>> {
|
||||||
|
self.last_ids_sigs
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get(last_id)
|
||||||
|
.and_then(|sigs| sigs.0.get(signature).cloned())
|
||||||
|
}
|
||||||
|
|
||||||
/// Hash the `accounts` HashMap. This represents a validator's interpretation
|
/// Hash the `accounts` HashMap. This represents a validator's interpretation
|
||||||
/// of the ledger up to the `last_id`, to be sent back to the leader when voting.
|
/// of the ledger up to the `last_id`, to be sent back to the leader when voting.
|
||||||
pub fn hash_internal_state(&self) -> Hash {
|
pub fn hash_internal_state(&self) -> Hash {
|
||||||
|
@ -732,6 +785,7 @@ mod tests {
|
||||||
use signature::{GenKeys, KeypairUtil};
|
use signature::{GenKeys, KeypairUtil};
|
||||||
use std;
|
use std;
|
||||||
use std::io::{BufReader, Cursor, Seek, SeekFrom};
|
use std::io::{BufReader, Cursor, Seek, SeekFrom};
|
||||||
|
use transaction::Instruction;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bank_new() {
|
fn test_bank_new() {
|
||||||
|
@ -757,6 +811,84 @@ mod tests {
|
||||||
assert_eq!(bank.transaction_count(), 2);
|
assert_eq!(bank.transaction_count(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_one_tx_two_out_atomic_fail() {
|
||||||
|
let mint = Mint::new(1);
|
||||||
|
let key1 = Keypair::new().pubkey();
|
||||||
|
let key2 = Keypair::new().pubkey();
|
||||||
|
let bank = Bank::new(&mint);
|
||||||
|
|
||||||
|
let spend = SystemProgram::Move { tokens: 1 };
|
||||||
|
let instructions = vec![
|
||||||
|
Instruction {
|
||||||
|
program_id: 0,
|
||||||
|
userdata: serialize(&spend).unwrap(),
|
||||||
|
accounts: vec![0, 1],
|
||||||
|
},
|
||||||
|
Instruction {
|
||||||
|
program_id: 0,
|
||||||
|
userdata: serialize(&spend).unwrap(),
|
||||||
|
accounts: vec![0, 2],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let t1 = Transaction::new_with_instructions(
|
||||||
|
&mint.keypair(),
|
||||||
|
&[key1, key2],
|
||||||
|
mint.last_id(),
|
||||||
|
0,
|
||||||
|
vec![SystemProgram::id()],
|
||||||
|
instructions,
|
||||||
|
);
|
||||||
|
let res = bank.process_transactions(&vec![t1.clone()]);
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
assert_eq!(res[0], Err(BankError::ResultWithNegativeTokens(1)));
|
||||||
|
assert_eq!(bank.get_balance(&mint.pubkey()), 1);
|
||||||
|
assert_eq!(bank.get_balance(&key1), 0);
|
||||||
|
assert_eq!(bank.get_balance(&key2), 0);
|
||||||
|
assert_eq!(
|
||||||
|
bank.get_signature(&t1.last_id, &t1.signature),
|
||||||
|
Some(Err(BankError::ResultWithNegativeTokens(1)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_one_tx_two_out_atomic_pass() {
|
||||||
|
let mint = Mint::new(2);
|
||||||
|
let key1 = Keypair::new().pubkey();
|
||||||
|
let key2 = Keypair::new().pubkey();
|
||||||
|
let bank = Bank::new(&mint);
|
||||||
|
|
||||||
|
let spend = SystemProgram::Move { tokens: 1 };
|
||||||
|
let instructions = vec![
|
||||||
|
Instruction {
|
||||||
|
program_id: 0,
|
||||||
|
userdata: serialize(&spend).unwrap(),
|
||||||
|
accounts: vec![0, 1],
|
||||||
|
},
|
||||||
|
Instruction {
|
||||||
|
program_id: 0,
|
||||||
|
userdata: serialize(&spend).unwrap(),
|
||||||
|
accounts: vec![0, 2],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let t1 = Transaction::new_with_instructions(
|
||||||
|
&mint.keypair(),
|
||||||
|
&[key1, key2],
|
||||||
|
mint.last_id(),
|
||||||
|
0,
|
||||||
|
vec![SystemProgram::id()],
|
||||||
|
instructions,
|
||||||
|
);
|
||||||
|
let res = bank.process_transactions(&vec![t1.clone()]);
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
assert!(res[0].is_ok());
|
||||||
|
assert_eq!(bank.get_balance(&mint.pubkey()), 0);
|
||||||
|
assert_eq!(bank.get_balance(&key1), 1);
|
||||||
|
assert_eq!(bank.get_balance(&key2), 1);
|
||||||
|
assert_eq!(bank.get_signature(&t1.last_id, &t1.signature), Some(Ok(())));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_negative_tokens() {
|
fn test_negative_tokens() {
|
||||||
logger::setup();
|
logger::setup();
|
||||||
|
@ -765,7 +897,7 @@ mod tests {
|
||||||
let bank = Bank::new(&mint);
|
let bank = Bank::new(&mint);
|
||||||
let res = bank.transfer(-1, &mint.keypair(), pubkey, mint.last_id());
|
let res = bank.transfer(-1, &mint.keypair(), pubkey, mint.last_id());
|
||||||
println!("{:?}", bank.get_account(&pubkey));
|
println!("{:?}", bank.get_account(&pubkey));
|
||||||
assert_matches!(res, Err(BankError::ResultWithNegativeTokens));
|
assert_matches!(res, Err(BankError::ResultWithNegativeTokens(0)));
|
||||||
assert_eq!(bank.transaction_count(), 0);
|
assert_eq!(bank.transaction_count(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -796,7 +928,7 @@ mod tests {
|
||||||
assert!(bank.has_signature(&signature));
|
assert!(bank.has_signature(&signature));
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
bank.get_signature_status(&signature),
|
bank.get_signature_status(&signature),
|
||||||
Err(BankError::ResultWithNegativeTokens)
|
Err(BankError::ResultWithNegativeTokens(0))
|
||||||
);
|
);
|
||||||
|
|
||||||
// The tokens didn't move, but the from address paid the transaction fee.
|
// The tokens didn't move, but the from address paid the transaction fee.
|
||||||
|
@ -829,7 +961,7 @@ mod tests {
|
||||||
assert_eq!(bank.get_balance(&pubkey), 1_000);
|
assert_eq!(bank.get_balance(&pubkey), 1_000);
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
bank.transfer(10_001, &mint.keypair(), pubkey, mint.last_id()),
|
bank.transfer(10_001, &mint.keypair(), pubkey, mint.last_id()),
|
||||||
Err(BankError::ResultWithNegativeTokens)
|
Err(BankError::ResultWithNegativeTokens(0))
|
||||||
);
|
);
|
||||||
assert_eq!(bank.transaction_count(), 1);
|
assert_eq!(bank.transaction_count(), 1);
|
||||||
|
|
||||||
|
@ -1126,19 +1258,4 @@ mod tests {
|
||||||
def_bank.set_finality(90);
|
def_bank.set_finality(90);
|
||||||
assert_eq!(def_bank.finality(), 90);
|
assert_eq!(def_bank.finality(), 90);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_storage_tx() {
|
|
||||||
let mint = Mint::new(1);
|
|
||||||
let bank = Bank::new(&mint);
|
|
||||||
let tx = Transaction::new(
|
|
||||||
&mint.keypair(),
|
|
||||||
&[],
|
|
||||||
StorageProgram::id(),
|
|
||||||
vec![], // <--- attack! Panic on bad userdata?
|
|
||||||
mint.last_id(),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
assert!(bank.process_transaction(&tx).is_err());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,7 +224,7 @@ impl BankingStage {
|
||||||
.zip(vers)
|
.zip(vers)
|
||||||
.filter_map(|(tx, ver)| match tx {
|
.filter_map(|(tx, ver)| match tx {
|
||||||
None => None,
|
None => None,
|
||||||
Some((tx, _addr)) => if tx.verify_plan() && ver != 0 {
|
Some((tx, _addr)) => if tx.verify_refs() && tx.verify_plan() && ver != 0 {
|
||||||
Some(tx)
|
Some(tx)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -11,13 +11,13 @@ use transaction::Transaction;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub enum BudgetError {
|
pub enum BudgetError {
|
||||||
InsufficientFunds(Pubkey),
|
InsufficientFunds,
|
||||||
ContractAlreadyExists(Pubkey),
|
ContractAlreadyExists,
|
||||||
ContractNotPending(Pubkey),
|
ContractNotPending,
|
||||||
SourceIsPendingContract(Pubkey),
|
SourceIsPendingContract,
|
||||||
UninitializedContract(Pubkey),
|
UninitializedContract,
|
||||||
NegativeTokens,
|
NegativeTokens,
|
||||||
DestinationMissing(Pubkey),
|
DestinationMissing,
|
||||||
FailedWitness,
|
FailedWitness,
|
||||||
UserdataTooSmall,
|
UserdataTooSmall,
|
||||||
UserdataDeserializeFailure,
|
UserdataDeserializeFailure,
|
||||||
|
@ -47,19 +47,21 @@ impl BudgetState {
|
||||||
/// will progress one step.
|
/// will progress one step.
|
||||||
fn apply_signature(
|
fn apply_signature(
|
||||||
&mut self,
|
&mut self,
|
||||||
keys: &[Pubkey],
|
tx: &Transaction,
|
||||||
account: &mut [Account],
|
program_index: usize,
|
||||||
|
account: &mut [&mut Account],
|
||||||
) -> Result<(), BudgetError> {
|
) -> Result<(), BudgetError> {
|
||||||
let mut final_payment = None;
|
let mut final_payment = None;
|
||||||
if let Some(ref mut budget) = self.pending_budget {
|
if let Some(ref mut budget) = self.pending_budget {
|
||||||
budget.apply_witness(&Witness::Signature, &keys[0]);
|
let key = tx.key(program_index, 0).unwrap();
|
||||||
|
budget.apply_witness(&Witness::Signature, key);
|
||||||
final_payment = budget.final_payment();
|
final_payment = budget.final_payment();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(payment) = final_payment {
|
if let Some(payment) = final_payment {
|
||||||
if keys.len() < 2 || payment.to != keys[2] {
|
if Some(&payment.to) != tx.key(program_index, 2) {
|
||||||
trace!("destination missing");
|
trace!("destination missing");
|
||||||
return Err(BudgetError::DestinationMissing(payment.to));
|
return Err(BudgetError::DestinationMissing);
|
||||||
}
|
}
|
||||||
self.pending_budget = None;
|
self.pending_budget = None;
|
||||||
account[1].tokens -= payment.tokens;
|
account[1].tokens -= payment.tokens;
|
||||||
|
@ -72,22 +74,24 @@ impl BudgetState {
|
||||||
/// will progress one step.
|
/// will progress one step.
|
||||||
fn apply_timestamp(
|
fn apply_timestamp(
|
||||||
&mut self,
|
&mut self,
|
||||||
keys: &[Pubkey],
|
tx: &Transaction,
|
||||||
accounts: &mut [Account],
|
program_index: usize,
|
||||||
|
accounts: &mut [&mut Account],
|
||||||
dt: DateTime<Utc>,
|
dt: DateTime<Utc>,
|
||||||
) -> Result<(), BudgetError> {
|
) -> Result<(), BudgetError> {
|
||||||
// Check to see if any timelocked transactions can be completed.
|
// Check to see if any timelocked transactions can be completed.
|
||||||
let mut final_payment = None;
|
let mut final_payment = None;
|
||||||
|
|
||||||
if let Some(ref mut budget) = self.pending_budget {
|
if let Some(ref mut budget) = self.pending_budget {
|
||||||
budget.apply_witness(&Witness::Timestamp(dt), &keys[0]);
|
let key = tx.key(program_index, 0).unwrap();
|
||||||
|
budget.apply_witness(&Witness::Timestamp(dt), key);
|
||||||
final_payment = budget.final_payment();
|
final_payment = budget.final_payment();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(payment) = final_payment {
|
if let Some(payment) = final_payment {
|
||||||
if keys.len() < 2 || payment.to != keys[2] {
|
if Some(&payment.to) != tx.key(program_index, 2) {
|
||||||
trace!("destination missing");
|
trace!("destination missing");
|
||||||
return Err(BudgetError::DestinationMissing(payment.to));
|
return Err(BudgetError::DestinationMissing);
|
||||||
}
|
}
|
||||||
self.pending_budget = None;
|
self.pending_budget = None;
|
||||||
accounts[1].tokens -= payment.tokens;
|
accounts[1].tokens -= payment.tokens;
|
||||||
|
@ -99,15 +103,14 @@ impl BudgetState {
|
||||||
/// Deduct tokens from the source account if it has sufficient funds and the contract isn't
|
/// Deduct tokens from the source account if it has sufficient funds and the contract isn't
|
||||||
/// pending
|
/// pending
|
||||||
fn apply_debits_to_budget_state(
|
fn apply_debits_to_budget_state(
|
||||||
tx: &Transaction,
|
accounts: &mut [&mut Account],
|
||||||
accounts: &mut [Account],
|
|
||||||
instruction: &Instruction,
|
instruction: &Instruction,
|
||||||
) -> Result<(), BudgetError> {
|
) -> Result<(), BudgetError> {
|
||||||
{
|
{
|
||||||
// if the source account userdata is not empty, this is a pending contract
|
// if the source account userdata is not empty, this is a pending contract
|
||||||
if !accounts[0].userdata.is_empty() {
|
if !accounts[0].userdata.is_empty() {
|
||||||
trace!("source is pending");
|
trace!("source is pending");
|
||||||
return Err(BudgetError::SourceIsPendingContract(tx.keys[0]));
|
return Err(BudgetError::SourceIsPendingContract);
|
||||||
}
|
}
|
||||||
if let Instruction::NewContract(contract) = &instruction {
|
if let Instruction::NewContract(contract) = &instruction {
|
||||||
if contract.tokens < 0 {
|
if contract.tokens < 0 {
|
||||||
|
@ -117,7 +120,7 @@ impl BudgetState {
|
||||||
|
|
||||||
if accounts[0].tokens < contract.tokens {
|
if accounts[0].tokens < contract.tokens {
|
||||||
trace!("insufficient funds");
|
trace!("insufficient funds");
|
||||||
return Err(BudgetError::InsufficientFunds(tx.keys[0]));
|
return Err(BudgetError::InsufficientFunds);
|
||||||
} else {
|
} else {
|
||||||
accounts[0].tokens -= contract.tokens;
|
accounts[0].tokens -= contract.tokens;
|
||||||
}
|
}
|
||||||
|
@ -130,7 +133,8 @@ impl BudgetState {
|
||||||
/// Note: It is safe to apply credits from multiple transactions in parallel.
|
/// Note: It is safe to apply credits from multiple transactions in parallel.
|
||||||
fn apply_credits_to_budget_state(
|
fn apply_credits_to_budget_state(
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
accounts: &mut [Account],
|
program_index: usize,
|
||||||
|
accounts: &mut [&mut Account],
|
||||||
instruction: &Instruction,
|
instruction: &Instruction,
|
||||||
) -> Result<(), BudgetError> {
|
) -> Result<(), BudgetError> {
|
||||||
match instruction {
|
match instruction {
|
||||||
|
@ -143,7 +147,7 @@ impl BudgetState {
|
||||||
let existing = Self::deserialize(&accounts[1].userdata).ok();
|
let existing = Self::deserialize(&accounts[1].userdata).ok();
|
||||||
if Some(true) == existing.map(|x| x.initialized) {
|
if Some(true) == existing.map(|x| x.initialized) {
|
||||||
trace!("contract already exists");
|
trace!("contract already exists");
|
||||||
Err(BudgetError::ContractAlreadyExists(tx.keys[1]))
|
Err(BudgetError::ContractAlreadyExists)
|
||||||
} else {
|
} else {
|
||||||
let mut state = BudgetState::default();
|
let mut state = BudgetState::default();
|
||||||
state.pending_budget = Some(budget);
|
state.pending_budget = Some(budget);
|
||||||
|
@ -156,35 +160,35 @@ impl BudgetState {
|
||||||
Instruction::ApplyTimestamp(dt) => {
|
Instruction::ApplyTimestamp(dt) => {
|
||||||
if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) {
|
if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) {
|
||||||
if !state.is_pending() {
|
if !state.is_pending() {
|
||||||
Err(BudgetError::ContractNotPending(tx.keys[1]))
|
Err(BudgetError::ContractNotPending)
|
||||||
} else if !state.initialized {
|
} else if !state.initialized {
|
||||||
trace!("contract is uninitialized");
|
trace!("contract is uninitialized");
|
||||||
Err(BudgetError::UninitializedContract(tx.keys[1]))
|
Err(BudgetError::UninitializedContract)
|
||||||
} else {
|
} else {
|
||||||
trace!("apply timestamp");
|
trace!("apply timestamp");
|
||||||
state.apply_timestamp(&tx.keys, accounts, *dt)?;
|
state.apply_timestamp(tx, program_index, accounts, *dt)?;
|
||||||
trace!("apply timestamp committed");
|
trace!("apply timestamp committed");
|
||||||
state.serialize(&mut accounts[1].userdata)
|
state.serialize(&mut accounts[1].userdata)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(BudgetError::UninitializedContract(tx.keys[1]))
|
Err(BudgetError::UninitializedContract)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Instruction::ApplySignature => {
|
Instruction::ApplySignature => {
|
||||||
if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) {
|
if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) {
|
||||||
if !state.is_pending() {
|
if !state.is_pending() {
|
||||||
Err(BudgetError::ContractNotPending(tx.keys[1]))
|
Err(BudgetError::ContractNotPending)
|
||||||
} else if !state.initialized {
|
} else if !state.initialized {
|
||||||
trace!("contract is uninitialized");
|
trace!("contract is uninitialized");
|
||||||
Err(BudgetError::UninitializedContract(tx.keys[1]))
|
Err(BudgetError::UninitializedContract)
|
||||||
} else {
|
} else {
|
||||||
trace!("apply signature");
|
trace!("apply signature");
|
||||||
state.apply_signature(&tx.keys, accounts)?;
|
state.apply_signature(tx, program_index, accounts)?;
|
||||||
trace!("apply signature committed");
|
trace!("apply signature committed");
|
||||||
state.serialize(&mut accounts[1].userdata)
|
state.serialize(&mut accounts[1].userdata)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(BudgetError::UninitializedContract(tx.keys[1]))
|
Err(BudgetError::UninitializedContract)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Instruction::NewVote(_vote) => {
|
Instruction::NewVote(_vote) => {
|
||||||
|
@ -237,14 +241,19 @@ impl BudgetState {
|
||||||
/// be spent from this account .
|
/// be spent from this account .
|
||||||
pub fn process_transaction(
|
pub fn process_transaction(
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
accounts: &mut [Account],
|
program_index: usize,
|
||||||
|
accounts: &mut [&mut Account],
|
||||||
) -> Result<(), BudgetError> {
|
) -> Result<(), BudgetError> {
|
||||||
if let Ok(instruction) = deserialize(&tx.userdata) {
|
if let Ok(instruction) = deserialize(tx.userdata(program_index)) {
|
||||||
trace!("process_transaction: {:?}", instruction);
|
trace!("process_transaction: {:?}", instruction);
|
||||||
Self::apply_debits_to_budget_state(tx, accounts, &instruction)
|
Self::apply_debits_to_budget_state(accounts, &instruction).and_then(|_| {
|
||||||
.and_then(|_| Self::apply_credits_to_budget_state(tx, accounts, &instruction))
|
Self::apply_credits_to_budget_state(tx, program_index, accounts, &instruction)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
info!("Invalid transaction userdata: {:?}", tx.userdata);
|
info!(
|
||||||
|
"Invalid transaction userdata: {:?}",
|
||||||
|
tx.userdata(program_index)
|
||||||
|
);
|
||||||
Err(BudgetError::UserdataDeserializeFailure)
|
Err(BudgetError::UserdataDeserializeFailure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,6 +284,10 @@ mod test {
|
||||||
use solana_program_interface::pubkey::Pubkey;
|
use solana_program_interface::pubkey::Pubkey;
|
||||||
use transaction::Transaction;
|
use transaction::Transaction;
|
||||||
|
|
||||||
|
fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<(), BudgetError> {
|
||||||
|
let mut refs: Vec<&mut Account> = accounts.iter_mut().collect();
|
||||||
|
BudgetState::process_transaction(&tx, 0, &mut refs[..])
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serializer() {
|
fn test_serializer() {
|
||||||
let mut a = Account::new(0, 512, BudgetState::id());
|
let mut a = Account::new(0, 512, BudgetState::id());
|
||||||
|
@ -303,16 +316,16 @@ mod test {
|
||||||
];
|
];
|
||||||
let from = Keypair::new();
|
let from = Keypair::new();
|
||||||
let contract = Keypair::new();
|
let contract = Keypair::new();
|
||||||
|
let userdata = vec![1, 2, 3];
|
||||||
let tx = Transaction::new(
|
let tx = Transaction::new(
|
||||||
&from,
|
&from,
|
||||||
&[contract.pubkey()],
|
&[contract.pubkey()],
|
||||||
BudgetState::id(),
|
BudgetState::id(),
|
||||||
vec![1, 2, 3], // <== garbage instruction
|
userdata,
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
assert!(BudgetState::process_transaction(&tx, &mut accounts).is_err());
|
assert!(process_transaction(&tx, &mut accounts).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -340,7 +353,7 @@ mod test {
|
||||||
1,
|
1,
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
);
|
);
|
||||||
BudgetState::process_transaction(&tx, &mut accounts).unwrap();
|
process_transaction(&tx, &mut accounts).unwrap();
|
||||||
assert_eq!(accounts[from_account].tokens, 0);
|
assert_eq!(accounts[from_account].tokens, 0);
|
||||||
assert_eq!(accounts[contract_account].tokens, 1);
|
assert_eq!(accounts[contract_account].tokens, 1);
|
||||||
let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap();
|
let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap();
|
||||||
|
@ -355,8 +368,8 @@ mod test {
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
BudgetState::process_transaction(&tx, &mut accounts),
|
process_transaction(&tx, &mut accounts),
|
||||||
Err(BudgetError::DestinationMissing(to.pubkey()))
|
Err(BudgetError::DestinationMissing)
|
||||||
);
|
);
|
||||||
assert_eq!(accounts[from_account].tokens, 0);
|
assert_eq!(accounts[from_account].tokens, 0);
|
||||||
assert_eq!(accounts[contract_account].tokens, 1);
|
assert_eq!(accounts[contract_account].tokens, 1);
|
||||||
|
@ -374,7 +387,7 @@ mod test {
|
||||||
dt,
|
dt,
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
);
|
);
|
||||||
BudgetState::process_transaction(&tx, &mut accounts).unwrap();
|
process_transaction(&tx, &mut accounts).unwrap();
|
||||||
assert_eq!(accounts[from_account].tokens, 0);
|
assert_eq!(accounts[from_account].tokens, 0);
|
||||||
assert_eq!(accounts[contract_account].tokens, 0);
|
assert_eq!(accounts[contract_account].tokens, 0);
|
||||||
assert_eq!(accounts[to_account].tokens, 1);
|
assert_eq!(accounts[to_account].tokens, 1);
|
||||||
|
@ -384,8 +397,8 @@ mod test {
|
||||||
|
|
||||||
// try to replay the timestamp contract
|
// try to replay the timestamp contract
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
BudgetState::process_transaction(&tx, &mut accounts),
|
process_transaction(&tx, &mut accounts),
|
||||||
Err(BudgetError::ContractNotPending(contract.pubkey()))
|
Err(BudgetError::ContractNotPending)
|
||||||
);
|
);
|
||||||
assert_eq!(accounts[from_account].tokens, 0);
|
assert_eq!(accounts[from_account].tokens, 0);
|
||||||
assert_eq!(accounts[contract_account].tokens, 0);
|
assert_eq!(accounts[contract_account].tokens, 0);
|
||||||
|
@ -415,7 +428,7 @@ mod test {
|
||||||
1,
|
1,
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
);
|
);
|
||||||
BudgetState::process_transaction(&tx, &mut accounts).unwrap();
|
process_transaction(&tx, &mut accounts).unwrap();
|
||||||
assert_eq!(accounts[from_account].tokens, 0);
|
assert_eq!(accounts[from_account].tokens, 0);
|
||||||
assert_eq!(accounts[contract_account].tokens, 1);
|
assert_eq!(accounts[contract_account].tokens, 1);
|
||||||
let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap();
|
let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap();
|
||||||
|
@ -426,7 +439,7 @@ mod test {
|
||||||
Transaction::budget_new_signature(&to, contract.pubkey(), to.pubkey(), Hash::default());
|
Transaction::budget_new_signature(&to, contract.pubkey(), to.pubkey(), Hash::default());
|
||||||
// unit test hack, the `from account` is passed instead of the `to` account to avoid
|
// unit test hack, the `from account` is passed instead of the `to` account to avoid
|
||||||
// creating more account vectors
|
// creating more account vectors
|
||||||
BudgetState::process_transaction(&tx, &mut accounts).unwrap();
|
process_transaction(&tx, &mut accounts).unwrap();
|
||||||
// nothing should be changed because apply witness didn't finalize a payment
|
// nothing should be changed because apply witness didn't finalize a payment
|
||||||
assert_eq!(accounts[from_account].tokens, 0);
|
assert_eq!(accounts[from_account].tokens, 0);
|
||||||
assert_eq!(accounts[contract_account].tokens, 1);
|
assert_eq!(accounts[contract_account].tokens, 1);
|
||||||
|
@ -440,7 +453,7 @@ mod test {
|
||||||
from.pubkey(),
|
from.pubkey(),
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
);
|
);
|
||||||
BudgetState::process_transaction(&tx, &mut accounts).unwrap();
|
process_transaction(&tx, &mut accounts).unwrap();
|
||||||
assert_eq!(accounts[from_account].tokens, 0);
|
assert_eq!(accounts[from_account].tokens, 0);
|
||||||
assert_eq!(accounts[contract_account].tokens, 0);
|
assert_eq!(accounts[contract_account].tokens, 0);
|
||||||
assert_eq!(accounts[pay_account].tokens, 1);
|
assert_eq!(accounts[pay_account].tokens, 1);
|
||||||
|
@ -453,8 +466,8 @@ mod test {
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
BudgetState::process_transaction(&tx, &mut accounts),
|
process_transaction(&tx, &mut accounts),
|
||||||
Err(BudgetError::ContractNotPending(contract.pubkey()))
|
Err(BudgetError::ContractNotPending)
|
||||||
);
|
);
|
||||||
assert_eq!(accounts[from_account].tokens, 0);
|
assert_eq!(accounts[from_account].tokens, 0);
|
||||||
assert_eq!(accounts[contract_account].tokens, 0);
|
assert_eq!(accounts[contract_account].tokens, 0);
|
||||||
|
@ -482,7 +495,7 @@ mod test {
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(BudgetState::process_transaction(&tx, &mut accounts).is_err());
|
assert!(process_transaction(&tx, &mut accounts).is_err());
|
||||||
assert!(BudgetState::deserialize(&accounts[1].userdata).is_err());
|
assert!(BudgetState::deserialize(&accounts[1].userdata).is_err());
|
||||||
|
|
||||||
let tx = Transaction::budget_new_timestamp(
|
let tx = Transaction::budget_new_timestamp(
|
||||||
|
@ -492,7 +505,7 @@ mod test {
|
||||||
Utc::now(),
|
Utc::now(),
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
);
|
);
|
||||||
assert!(BudgetState::process_transaction(&tx, &mut accounts).is_err());
|
assert!(process_transaction(&tx, &mut accounts).is_err());
|
||||||
assert!(BudgetState::deserialize(&accounts[1].userdata).is_err());
|
assert!(BudgetState::deserialize(&accounts[1].userdata).is_err());
|
||||||
|
|
||||||
// Success if there was no panic...
|
// Success if there was no panic...
|
||||||
|
@ -517,7 +530,7 @@ mod test {
|
||||||
|
|
||||||
let tx = Transaction::budget_new(&keypair, to, 192, Hash::default());
|
let tx = Transaction::budget_new(&keypair, to, 192, Hash::default());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx.userdata,
|
tx.userdata(0).to_vec(),
|
||||||
vec![
|
vec![
|
||||||
0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
1, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 6, 5, 4, 1,
|
1, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 6, 5, 4, 1,
|
||||||
|
@ -536,7 +549,7 @@ mod test {
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx.userdata,
|
tx.userdata(0).to_vec(),
|
||||||
vec![
|
vec![
|
||||||
0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0,
|
||||||
0, 50, 48, 49, 54, 45, 48, 55, 45, 48, 56, 84, 48, 57, 58, 49, 48, 58, 49, 49, 90,
|
0, 50, 48, 49, 54, 45, 48, 55, 45, 48, 56, 84, 48, 57, 58, 49, 48, 58, 49, 49, 90,
|
||||||
|
@ -561,10 +574,10 @@ mod test {
|
||||||
);
|
);
|
||||||
let mut expected_userdata = vec![1, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0];
|
let mut expected_userdata = vec![1, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0];
|
||||||
expected_userdata.extend(date_iso8601.as_bytes());
|
expected_userdata.extend(date_iso8601.as_bytes());
|
||||||
assert_eq!(tx.userdata, expected_userdata);
|
assert_eq!(tx.userdata(0).to_vec(), expected_userdata);
|
||||||
|
|
||||||
// ApplySignature
|
// ApplySignature
|
||||||
let tx = Transaction::budget_new_signature(&keypair, keypair.pubkey(), to, Hash::default());
|
let tx = Transaction::budget_new_signature(&keypair, keypair.pubkey(), to, Hash::default());
|
||||||
assert_eq!(tx.userdata, vec![2, 0, 0, 0]);
|
assert_eq!(tx.userdata(0).to_vec(), vec![2, 0, 0, 0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ pub trait BudgetTransaction {
|
||||||
|
|
||||||
fn vote(&self) -> Option<(Pubkey, Vote, Hash)>;
|
fn vote(&self) -> Option<(Pubkey, Vote, Hash)>;
|
||||||
|
|
||||||
fn instruction(&self) -> Option<Instruction>;
|
fn instruction(&self, program_index: usize) -> Option<Instruction>;
|
||||||
|
|
||||||
fn verify_plan(&self) -> bool;
|
fn verify_plan(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
@ -204,34 +204,42 @@ impl BudgetTransaction for Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vote(&self) -> Option<(Pubkey, Vote, Hash)> {
|
fn vote(&self) -> Option<(Pubkey, Vote, Hash)> {
|
||||||
if let Some(Instruction::NewVote(vote)) = self.instruction() {
|
if self.instructions.len() > 1 {
|
||||||
Some((*self.from(), vote, self.last_id))
|
error!("expecting only 1 Instruction per vote");
|
||||||
|
None
|
||||||
|
} else if let Some(Instruction::NewVote(vote)) = self.instruction(0) {
|
||||||
|
Some((self.account_keys[0], vote, self.last_id))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instruction(&self) -> Option<Instruction> {
|
fn instruction(&self, program_index: usize) -> Option<Instruction> {
|
||||||
deserialize(&self.userdata).ok()
|
deserialize(&self.userdata(program_index)).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify only the payment plan.
|
/// Verify only the payment plan.
|
||||||
fn verify_plan(&self) -> bool {
|
fn verify_plan(&self) -> bool {
|
||||||
if let Some(Instruction::NewContract(contract)) = self.instruction() {
|
for pix in 0..self.instructions.len() {
|
||||||
self.fee >= 0
|
if let Some(Instruction::NewContract(contract)) = self.instruction(pix) {
|
||||||
|
if !(self.fee >= 0
|
||||||
&& self.fee <= contract.tokens
|
&& self.fee <= contract.tokens
|
||||||
&& contract.budget.verify(contract.tokens - self.fee)
|
&& contract.budget.verify(contract.tokens - self.fee))
|
||||||
} else {
|
{
|
||||||
true
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use bincode::{deserialize, serialize};
|
use bincode::{deserialize, serialize};
|
||||||
use signature::KeypairUtil;
|
use signature::KeypairUtil;
|
||||||
|
use transaction;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_claim() {
|
fn test_claim() {
|
||||||
|
@ -269,13 +277,18 @@ mod tests {
|
||||||
});
|
});
|
||||||
let instruction = Instruction::NewContract(Contract { budget, tokens: 0 });
|
let instruction = Instruction::NewContract(Contract { budget, tokens: 0 });
|
||||||
let userdata = serialize(&instruction).unwrap();
|
let userdata = serialize(&instruction).unwrap();
|
||||||
|
let instructions = vec![transaction::Instruction {
|
||||||
|
program_id: 0,
|
||||||
|
userdata,
|
||||||
|
accounts: vec![],
|
||||||
|
}];
|
||||||
let claim0 = Transaction {
|
let claim0 = Transaction {
|
||||||
keys: vec![],
|
account_keys: vec![],
|
||||||
last_id: Default::default(),
|
last_id: Default::default(),
|
||||||
signature: Default::default(),
|
signature: Default::default(),
|
||||||
program_id: Default::default(),
|
program_keys: vec![],
|
||||||
|
instructions,
|
||||||
fee: 0,
|
fee: 0,
|
||||||
userdata,
|
|
||||||
};
|
};
|
||||||
let buf = serialize(&claim0).unwrap();
|
let buf = serialize(&claim0).unwrap();
|
||||||
let claim1: Transaction = deserialize(&buf).unwrap();
|
let claim1: Transaction = deserialize(&buf).unwrap();
|
||||||
|
@ -288,14 +301,14 @@ mod tests {
|
||||||
let keypair = Keypair::new();
|
let keypair = Keypair::new();
|
||||||
let pubkey = keypair.pubkey();
|
let pubkey = keypair.pubkey();
|
||||||
let mut tx = Transaction::budget_new(&keypair, pubkey, 42, zero);
|
let mut tx = Transaction::budget_new(&keypair, pubkey, 42, zero);
|
||||||
let mut instruction = tx.instruction().unwrap();
|
let mut instruction = tx.instruction(0).unwrap();
|
||||||
if let Instruction::NewContract(ref mut contract) = instruction {
|
if let Instruction::NewContract(ref mut contract) = instruction {
|
||||||
contract.tokens = 1_000_000; // <-- attack, part 1!
|
contract.tokens = 1_000_000; // <-- attack, part 1!
|
||||||
if let Budget::Pay(ref mut payment) = contract.budget {
|
if let Budget::Pay(ref mut payment) = contract.budget {
|
||||||
payment.tokens = contract.tokens; // <-- attack, part 2!
|
payment.tokens = contract.tokens; // <-- attack, part 2!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tx.userdata = serialize(&instruction).unwrap();
|
tx.instructions[0].userdata = serialize(&instruction).unwrap();
|
||||||
assert!(tx.verify_plan());
|
assert!(tx.verify_plan());
|
||||||
assert!(!tx.verify_signature());
|
assert!(!tx.verify_signature());
|
||||||
}
|
}
|
||||||
|
@ -308,13 +321,13 @@ mod tests {
|
||||||
let pubkey1 = keypair1.pubkey();
|
let pubkey1 = keypair1.pubkey();
|
||||||
let zero = Hash::default();
|
let zero = Hash::default();
|
||||||
let mut tx = Transaction::budget_new(&keypair0, pubkey1, 42, zero);
|
let mut tx = Transaction::budget_new(&keypair0, pubkey1, 42, zero);
|
||||||
let mut instruction = tx.instruction();
|
let mut instruction = tx.instruction(0);
|
||||||
if let Some(Instruction::NewContract(ref mut contract)) = instruction {
|
if let Some(Instruction::NewContract(ref mut contract)) = instruction {
|
||||||
if let Budget::Pay(ref mut payment) = contract.budget {
|
if let Budget::Pay(ref mut payment) = contract.budget {
|
||||||
payment.to = thief_keypair.pubkey(); // <-- attack!
|
payment.to = thief_keypair.pubkey(); // <-- attack!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tx.userdata = serialize(&instruction).unwrap();
|
tx.instructions[0].userdata = serialize(&instruction).unwrap();
|
||||||
assert!(tx.verify_plan());
|
assert!(tx.verify_plan());
|
||||||
assert!(!tx.verify_signature());
|
assert!(!tx.verify_signature());
|
||||||
}
|
}
|
||||||
|
@ -325,23 +338,23 @@ mod tests {
|
||||||
let keypair1 = Keypair::new();
|
let keypair1 = Keypair::new();
|
||||||
let zero = Hash::default();
|
let zero = Hash::default();
|
||||||
let mut tx = Transaction::budget_new(&keypair0, keypair1.pubkey(), 1, zero);
|
let mut tx = Transaction::budget_new(&keypair0, keypair1.pubkey(), 1, zero);
|
||||||
let mut instruction = tx.instruction().unwrap();
|
let mut instruction = tx.instruction(0).unwrap();
|
||||||
if let Instruction::NewContract(ref mut contract) = instruction {
|
if let Instruction::NewContract(ref mut contract) = instruction {
|
||||||
if let Budget::Pay(ref mut payment) = contract.budget {
|
if let Budget::Pay(ref mut payment) = contract.budget {
|
||||||
payment.tokens = 2; // <-- attack!
|
payment.tokens = 2; // <-- attack!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tx.userdata = serialize(&instruction).unwrap();
|
tx.instructions[0].userdata = serialize(&instruction).unwrap();
|
||||||
assert!(!tx.verify_plan());
|
assert!(!tx.verify_plan());
|
||||||
|
|
||||||
// Also, ensure all branchs of the plan spend all tokens
|
// Also, ensure all branchs of the plan spend all tokens
|
||||||
let mut instruction = tx.instruction().unwrap();
|
let mut instruction = tx.instruction(0).unwrap();
|
||||||
if let Instruction::NewContract(ref mut contract) = instruction {
|
if let Instruction::NewContract(ref mut contract) = instruction {
|
||||||
if let Budget::Pay(ref mut payment) = contract.budget {
|
if let Budget::Pay(ref mut payment) = contract.budget {
|
||||||
payment.tokens = 0; // <-- whoops!
|
payment.tokens = 0; // <-- whoops!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tx.userdata = serialize(&instruction).unwrap();
|
tx.instructions[0].userdata = serialize(&instruction).unwrap();
|
||||||
assert!(!tx.verify_plan());
|
assert!(!tx.verify_plan());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,8 +76,9 @@ mod tests {
|
||||||
fn test_create_transactions() {
|
fn test_create_transactions() {
|
||||||
let mut transactions = Mint::new(100).create_transactions().into_iter();
|
let mut transactions = Mint::new(100).create_transactions().into_iter();
|
||||||
let tx = transactions.next().unwrap();
|
let tx = transactions.next().unwrap();
|
||||||
assert!(SystemProgram::check_id(&tx.program_id));
|
assert_eq!(tx.instructions.len(), 1);
|
||||||
let instruction: SystemProgram = deserialize(&tx.userdata).unwrap();
|
assert!(SystemProgram::check_id(tx.program_id(0)));
|
||||||
|
let instruction: SystemProgram = deserialize(tx.userdata(0)).unwrap();
|
||||||
if let SystemProgram::Move { tokens } = instruction {
|
if let SystemProgram::Move { tokens } = instruction {
|
||||||
assert_eq!(tokens, 100);
|
assert_eq!(tokens, 100);
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,7 +173,7 @@ impl RpcSol for RpcSolImpl {
|
||||||
Ok(
|
Ok(
|
||||||
match meta.request_processor.get_signature_status(signature) {
|
match meta.request_processor.get_signature_status(signature) {
|
||||||
Ok(_) => RpcSignatureStatus::Confirmed,
|
Ok(_) => RpcSignatureStatus::Confirmed,
|
||||||
Err(BankError::ProgramRuntimeError) => RpcSignatureStatus::ProgramRuntimeError,
|
Err(BankError::ProgramRuntimeError(_)) => RpcSignatureStatus::ProgramRuntimeError,
|
||||||
Err(BankError::SignatureNotFound) => RpcSignatureStatus::SignatureNotFound,
|
Err(BankError::SignatureNotFound) => RpcSignatureStatus::SignatureNotFound,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
trace!("mapping {:?} to GenericFailure", err);
|
trace!("mapping {:?} to GenericFailure", err);
|
||||||
|
|
|
@ -33,9 +33,10 @@ impl StorageProgram {
|
||||||
|
|
||||||
pub fn process_transaction(
|
pub fn process_transaction(
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
_accounts: &mut [Account],
|
pix: usize,
|
||||||
|
_accounts: &mut [&mut Account],
|
||||||
) -> Result<(), StorageError> {
|
) -> Result<(), StorageError> {
|
||||||
if let Ok(syscall) = deserialize(&tx.userdata) {
|
if let Ok(syscall) = deserialize(tx.userdata(pix)) {
|
||||||
match syscall {
|
match syscall {
|
||||||
StorageProgram::SubmitMiningProof { sha_state } => {
|
StorageProgram::SubmitMiningProof { sha_state } => {
|
||||||
info!("Mining proof submitted with state {}", sha_state[0]);
|
info!("Mining proof submitted with state {}", sha_state[0]);
|
||||||
|
@ -49,4 +50,22 @@ impl StorageProgram {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {}
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use signature::Keypair;
|
||||||
|
use signature::KeypairUtil;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_tx() {
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
let tx = Transaction::new(
|
||||||
|
&keypair,
|
||||||
|
&[],
|
||||||
|
StorageProgram::id(),
|
||||||
|
vec![],
|
||||||
|
Default::default(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
assert!(StorageProgram::process_transaction(&tx, 0, &mut []).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -49,10 +49,11 @@ impl SystemProgram {
|
||||||
}
|
}
|
||||||
pub fn process_transaction(
|
pub fn process_transaction(
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
accounts: &mut [Account],
|
pix: usize,
|
||||||
|
accounts: &mut [&mut Account],
|
||||||
loaded_programs: &RwLock<HashMap<Pubkey, DynamicProgram>>,
|
loaded_programs: &RwLock<HashMap<Pubkey, DynamicProgram>>,
|
||||||
) {
|
) {
|
||||||
if let Ok(syscall) = deserialize(&tx.userdata) {
|
if let Ok(syscall) = deserialize(tx.userdata(pix)) {
|
||||||
trace!("process_transaction: {:?}", syscall);
|
trace!("process_transaction: {:?}", syscall);
|
||||||
match syscall {
|
match syscall {
|
||||||
SystemProgram::CreateAccount {
|
SystemProgram::CreateAccount {
|
||||||
|
@ -91,12 +92,13 @@ impl SystemProgram {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("Invalid transaction userdata: {:?}", tx.userdata);
|
info!("Invalid transaction userdata: {:?}", tx.userdata(pix));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use super::*;
|
||||||
use hash::Hash;
|
use hash::Hash;
|
||||||
use signature::{Keypair, KeypairUtil};
|
use signature::{Keypair, KeypairUtil};
|
||||||
use solana_program_interface::account::Account;
|
use solana_program_interface::account::Account;
|
||||||
|
@ -107,6 +109,14 @@ mod test {
|
||||||
use system_transaction::SystemTransaction;
|
use system_transaction::SystemTransaction;
|
||||||
use transaction::Transaction;
|
use transaction::Transaction;
|
||||||
|
|
||||||
|
fn process_transaction(
|
||||||
|
tx: &Transaction,
|
||||||
|
accounts: &mut [Account],
|
||||||
|
loaded_programs: &RwLock<HashMap<Pubkey, DynamicProgram>>,
|
||||||
|
) {
|
||||||
|
let mut refs: Vec<&mut Account> = accounts.iter_mut().collect();
|
||||||
|
SystemProgram::process_transaction(&tx, 0, &mut refs[..], loaded_programs)
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_noop() {
|
fn test_create_noop() {
|
||||||
let from = Keypair::new();
|
let from = Keypair::new();
|
||||||
|
@ -114,7 +124,7 @@ mod test {
|
||||||
let mut accounts = vec![Account::default(), Account::default()];
|
let mut accounts = vec![Account::default(), Account::default()];
|
||||||
let tx = Transaction::system_new(&from, to.pubkey(), 0, Hash::default());
|
let tx = Transaction::system_new(&from, to.pubkey(), 0, Hash::default());
|
||||||
let hash = RwLock::new(HashMap::new());
|
let hash = RwLock::new(HashMap::new());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert_eq!(accounts[0].tokens, 0);
|
assert_eq!(accounts[0].tokens, 0);
|
||||||
assert_eq!(accounts[1].tokens, 0);
|
assert_eq!(accounts[1].tokens, 0);
|
||||||
}
|
}
|
||||||
|
@ -126,7 +136,7 @@ mod test {
|
||||||
accounts[0].tokens = 1;
|
accounts[0].tokens = 1;
|
||||||
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
||||||
let hash = RwLock::new(HashMap::new());
|
let hash = RwLock::new(HashMap::new());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert_eq!(accounts[0].tokens, 0);
|
assert_eq!(accounts[0].tokens, 0);
|
||||||
assert_eq!(accounts[1].tokens, 1);
|
assert_eq!(accounts[1].tokens, 1);
|
||||||
}
|
}
|
||||||
|
@ -139,7 +149,7 @@ mod test {
|
||||||
accounts[0].program_id = from.pubkey();
|
accounts[0].program_id = from.pubkey();
|
||||||
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
||||||
let hash = RwLock::new(HashMap::new());
|
let hash = RwLock::new(HashMap::new());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert_eq!(accounts[0].tokens, 1);
|
assert_eq!(accounts[0].tokens, 1);
|
||||||
assert_eq!(accounts[1].tokens, 0);
|
assert_eq!(accounts[1].tokens, 0);
|
||||||
}
|
}
|
||||||
|
@ -151,7 +161,7 @@ mod test {
|
||||||
let tx =
|
let tx =
|
||||||
Transaction::system_create(&from, to.pubkey(), Hash::default(), 0, 1, to.pubkey(), 0);
|
Transaction::system_create(&from, to.pubkey(), Hash::default(), 0, 1, to.pubkey(), 0);
|
||||||
let hash = RwLock::new(HashMap::new());
|
let hash = RwLock::new(HashMap::new());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert!(accounts[0].userdata.is_empty());
|
assert!(accounts[0].userdata.is_empty());
|
||||||
assert_eq!(accounts[1].userdata.len(), 1);
|
assert_eq!(accounts[1].userdata.len(), 1);
|
||||||
assert_eq!(accounts[1].program_id, to.pubkey());
|
assert_eq!(accounts[1].program_id, to.pubkey());
|
||||||
|
@ -172,7 +182,7 @@ mod test {
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
let hash = RwLock::new(HashMap::new());
|
let hash = RwLock::new(HashMap::new());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert!(accounts[1].userdata.is_empty());
|
assert!(accounts[1].userdata.is_empty());
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -191,7 +201,7 @@ mod test {
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
let hash = RwLock::new(HashMap::new());
|
let hash = RwLock::new(HashMap::new());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert!(accounts[1].userdata.is_empty());
|
assert!(accounts[1].userdata.is_empty());
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -210,7 +220,7 @@ mod test {
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
let hash = RwLock::new(HashMap::new());
|
let hash = RwLock::new(HashMap::new());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert_eq!(accounts[1].userdata.len(), 3);
|
assert_eq!(accounts[1].userdata.len(), 3);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -220,7 +230,7 @@ mod test {
|
||||||
let mut accounts = vec![Account::default()];
|
let mut accounts = vec![Account::default()];
|
||||||
let tx = Transaction::system_assign(&from, Hash::default(), program.pubkey(), 0);
|
let tx = Transaction::system_assign(&from, Hash::default(), program.pubkey(), 0);
|
||||||
let hash = RwLock::new(HashMap::new());
|
let hash = RwLock::new(HashMap::new());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert_eq!(accounts[0].program_id, program.pubkey());
|
assert_eq!(accounts[0].program_id, program.pubkey());
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -231,7 +241,7 @@ mod test {
|
||||||
accounts[0].tokens = 1;
|
accounts[0].tokens = 1;
|
||||||
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
||||||
let hash = RwLock::new(HashMap::new());
|
let hash = RwLock::new(HashMap::new());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert_eq!(accounts[0].tokens, 0);
|
assert_eq!(accounts[0].tokens, 0);
|
||||||
assert_eq!(accounts[1].tokens, 1);
|
assert_eq!(accounts[1].tokens, 1);
|
||||||
}
|
}
|
||||||
|
@ -254,7 +264,7 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx.userdata,
|
tx.userdata(0).to_vec(),
|
||||||
vec![
|
vec![
|
||||||
0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
@ -273,7 +283,7 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx.userdata,
|
tx.userdata(0).to_vec(),
|
||||||
vec![
|
vec![
|
||||||
0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
@ -288,7 +298,7 @@ mod test {
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx.userdata,
|
tx.userdata(0).to_vec(),
|
||||||
vec![
|
vec![
|
||||||
1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0
|
0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
@ -297,6 +307,9 @@ mod test {
|
||||||
|
|
||||||
// Move
|
// Move
|
||||||
let tx = Transaction::system_move(&keypair, keypair.pubkey(), 123, Hash::default(), 0);
|
let tx = Transaction::system_move(&keypair, keypair.pubkey(), 123, Hash::default(), 0);
|
||||||
assert_eq!(tx.userdata, vec![2, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0]);
|
assert_eq!(
|
||||||
|
tx.userdata(0).to_vec(),
|
||||||
|
vec![2, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,23 +55,25 @@ impl SystemTransaction for Transaction {
|
||||||
space,
|
space,
|
||||||
program_id,
|
program_id,
|
||||||
};
|
};
|
||||||
|
let userdata = serialize(&create).unwrap();
|
||||||
Transaction::new(
|
Transaction::new(
|
||||||
from_keypair,
|
from_keypair,
|
||||||
&[to],
|
&[to],
|
||||||
SystemProgram::id(),
|
SystemProgram::id(),
|
||||||
serialize(&create).unwrap(),
|
userdata,
|
||||||
last_id,
|
last_id,
|
||||||
fee,
|
fee,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/// Create and sign new SystemProgram::CreateAccount transaction
|
/// Create and sign new SystemProgram::CreateAccount transaction
|
||||||
fn system_assign(from_keypair: &Keypair, last_id: Hash, program_id: Pubkey, fee: i64) -> Self {
|
fn system_assign(from_keypair: &Keypair, last_id: Hash, program_id: Pubkey, fee: i64) -> Self {
|
||||||
let create = SystemProgram::Assign { program_id };
|
let assign = SystemProgram::Assign { program_id };
|
||||||
|
let userdata = serialize(&assign).unwrap();
|
||||||
Transaction::new(
|
Transaction::new(
|
||||||
from_keypair,
|
from_keypair,
|
||||||
&[],
|
&[],
|
||||||
SystemProgram::id(),
|
SystemProgram::id(),
|
||||||
serialize(&create).unwrap(),
|
userdata,
|
||||||
last_id,
|
last_id,
|
||||||
fee,
|
fee,
|
||||||
)
|
)
|
||||||
|
@ -88,12 +90,13 @@ impl SystemTransaction for Transaction {
|
||||||
last_id: Hash,
|
last_id: Hash,
|
||||||
fee: i64,
|
fee: i64,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let create = SystemProgram::Move { tokens };
|
let move_tokens = SystemProgram::Move { tokens };
|
||||||
|
let userdata = serialize(&move_tokens).unwrap();
|
||||||
Transaction::new(
|
Transaction::new(
|
||||||
from_keypair,
|
from_keypair,
|
||||||
&[to],
|
&[to],
|
||||||
SystemProgram::id(),
|
SystemProgram::id(),
|
||||||
serialize(&create).unwrap(),
|
userdata,
|
||||||
last_id,
|
last_id,
|
||||||
fee,
|
fee,
|
||||||
)
|
)
|
||||||
|
@ -107,11 +110,12 @@ impl SystemTransaction for Transaction {
|
||||||
name: String,
|
name: String,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let load = SystemProgram::Load { program_id, name };
|
let load = SystemProgram::Load { program_id, name };
|
||||||
|
let userdata = serialize(&load).unwrap();
|
||||||
Transaction::new(
|
Transaction::new(
|
||||||
from_keypair,
|
from_keypair,
|
||||||
&[],
|
&[],
|
||||||
SystemProgram::id(),
|
SystemProgram::id(),
|
||||||
serialize(&load).unwrap(),
|
userdata,
|
||||||
last_id,
|
last_id,
|
||||||
fee,
|
fee,
|
||||||
)
|
)
|
||||||
|
@ -152,7 +156,7 @@ mod tests {
|
||||||
assert_eq!(memfind(&tx_bytes, &sign_data), Some(SIGNED_DATA_OFFSET));
|
assert_eq!(memfind(&tx_bytes, &sign_data), Some(SIGNED_DATA_OFFSET));
|
||||||
assert_eq!(memfind(&tx_bytes, &tx.signature.as_ref()), Some(SIG_OFFSET));
|
assert_eq!(memfind(&tx_bytes, &tx.signature.as_ref()), Some(SIG_OFFSET));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
memfind(&tx_bytes, &tx.from().as_ref()),
|
memfind(&tx_bytes, &tx.account_keys[0].as_ref()),
|
||||||
Some(PUB_KEY_OFFSET)
|
Some(PUB_KEY_OFFSET)
|
||||||
);
|
);
|
||||||
assert!(tx.verify_signature());
|
assert!(tx.verify_signature());
|
||||||
|
@ -161,7 +165,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_userdata_layout() {
|
fn test_userdata_layout() {
|
||||||
let mut tx0 = test_tx();
|
let mut tx0 = test_tx();
|
||||||
tx0.userdata = vec![1, 2, 3];
|
tx0.instructions[0].userdata = vec![1, 2, 3];
|
||||||
let sign_data0a = tx0.get_sign_data();
|
let sign_data0a = tx0.get_sign_data();
|
||||||
let tx_bytes = serialize(&tx0).unwrap();
|
let tx_bytes = serialize(&tx0).unwrap();
|
||||||
assert!(tx_bytes.len() < PACKET_DATA_SIZE);
|
assert!(tx_bytes.len() < PACKET_DATA_SIZE);
|
||||||
|
@ -171,14 +175,14 @@ mod tests {
|
||||||
Some(SIG_OFFSET)
|
Some(SIG_OFFSET)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
memfind(&tx_bytes, &tx0.from().as_ref()),
|
memfind(&tx_bytes, &tx0.account_keys[0].as_ref()),
|
||||||
Some(PUB_KEY_OFFSET)
|
Some(PUB_KEY_OFFSET)
|
||||||
);
|
);
|
||||||
let tx1 = deserialize(&tx_bytes).unwrap();
|
let tx1 = deserialize(&tx_bytes).unwrap();
|
||||||
assert_eq!(tx0, tx1);
|
assert_eq!(tx0, tx1);
|
||||||
assert_eq!(tx1.userdata, vec![1, 2, 3]);
|
assert_eq!(tx1.instructions[0].userdata, vec![1, 2, 3]);
|
||||||
|
|
||||||
tx0.userdata = vec![1, 2, 4];
|
tx0.instructions[0].userdata = vec![1, 2, 4];
|
||||||
let sign_data0b = tx0.get_sign_data();
|
let sign_data0b = tx0.get_sign_data();
|
||||||
assert_ne!(sign_data0a, sign_data0b);
|
assert_ne!(sign_data0a, sign_data0b);
|
||||||
}
|
}
|
||||||
|
|
|
@ -540,11 +540,11 @@ mod tests {
|
||||||
let last_id = client.get_last_id();
|
let last_id = client.get_last_id();
|
||||||
|
|
||||||
let mut tr2 = Transaction::system_new(&alice.keypair(), bob_pubkey, 501, last_id);
|
let mut tr2 = Transaction::system_new(&alice.keypair(), bob_pubkey, 501, last_id);
|
||||||
let mut instruction2 = deserialize(&tr2.userdata).unwrap();
|
let mut instruction2 = deserialize(tr2.userdata(0)).unwrap();
|
||||||
if let SystemProgram::Move { ref mut tokens } = instruction2 {
|
if let SystemProgram::Move { ref mut tokens } = instruction2 {
|
||||||
*tokens = 502;
|
*tokens = 502;
|
||||||
}
|
}
|
||||||
tr2.userdata = serialize(&instruction2).unwrap();
|
tr2.instructions[0].userdata = serialize(&instruction2).unwrap();
|
||||||
let signature = client.transfer_signed(&tr2).unwrap();
|
let signature = client.transfer_signed(&tr2).unwrap();
|
||||||
client.poll_for_signature(&signature).unwrap();
|
client.poll_for_signature(&signature).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -263,7 +263,11 @@ impl TicTacToeProgram {
|
||||||
Pubkey::new(&TICTACTOE_PROGRAM_ID)
|
Pubkey::new(&TICTACTOE_PROGRAM_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<()> {
|
pub fn process_transaction(
|
||||||
|
tx: &Transaction,
|
||||||
|
pix: usize,
|
||||||
|
accounts: &mut [&mut Account],
|
||||||
|
) -> Result<()> {
|
||||||
// accounts[1] must always be the Tic-tac-toe game state account
|
// accounts[1] must always be the Tic-tac-toe game state account
|
||||||
if accounts.len() < 2 || !Self::check_id(&accounts[1].program_id) {
|
if accounts.len() < 2 || !Self::check_id(&accounts[1].program_id) {
|
||||||
error!("accounts[1] is not assigned to the TICTACTOE_PROGRAM_ID");
|
error!("accounts[1] is not assigned to the TICTACTOE_PROGRAM_ID");
|
||||||
|
@ -276,7 +280,7 @@ impl TicTacToeProgram {
|
||||||
|
|
||||||
let mut program_state = Self::deserialize(&accounts[1].userdata)?;
|
let mut program_state = Self::deserialize(&accounts[1].userdata)?;
|
||||||
|
|
||||||
let command = serde_cbor::from_slice::<Command>(&tx.userdata).map_err(|err| {
|
let command = serde_cbor::from_slice::<Command>(tx.userdata(pix)).map_err(|err| {
|
||||||
error!("{:?}", err);
|
error!("{:?}", err);
|
||||||
Error::InvalidUserdata
|
Error::InvalidUserdata
|
||||||
})?;
|
})?;
|
||||||
|
@ -288,14 +292,13 @@ impl TicTacToeProgram {
|
||||||
error!("accounts[0] is not assigned to the TICTACTOE_PROGRAM_ID");
|
error!("accounts[0] is not assigned to the TICTACTOE_PROGRAM_ID");
|
||||||
return Err(Error::InvalidArguments);
|
return Err(Error::InvalidArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
// player X public key is in keys[2]
|
// player X public key is in keys[2]
|
||||||
if accounts.len() < 3 {
|
if tx.key(pix, 2).is_none() {
|
||||||
Err(Error::InvalidArguments)?;
|
Err(Error::InvalidArguments)?;
|
||||||
}
|
}
|
||||||
program_state.dispatch_command(&command, &tx.keys[2])?;
|
program_state.dispatch_command(&command, tx.key(pix, 2).unwrap())?;
|
||||||
} else {
|
} else {
|
||||||
program_state.dispatch_command(&command, &tx.keys[0])?;
|
program_state.dispatch_command(&command, tx.key(pix, 0).unwrap())?;
|
||||||
}
|
}
|
||||||
program_state.serialize(&mut accounts[1].userdata)?;
|
program_state.serialize(&mut accounts[1].userdata)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -10,7 +10,20 @@ pub const SIGNED_DATA_OFFSET: usize = size_of::<Signature>();
|
||||||
pub const SIG_OFFSET: usize = 0;
|
pub const SIG_OFFSET: usize = 0;
|
||||||
pub const PUB_KEY_OFFSET: usize = size_of::<Signature>() + size_of::<u64>();
|
pub const PUB_KEY_OFFSET: usize = size_of::<Signature>() + size_of::<u64>();
|
||||||
|
|
||||||
/// An instruction signed by a client with `Pubkey`.
|
/// An instruction to execute a program under `program_id` with the
|
||||||
|
/// specified accounts and userdata
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Instruction {
|
||||||
|
/// The program code that executes this transaction is identified by the program_id.
|
||||||
|
/// this is an offset into the Transaction::program_keys field
|
||||||
|
pub program_id: u8,
|
||||||
|
/// Indices into the keys array of which accounts to load
|
||||||
|
pub accounts: Vec<u8>,
|
||||||
|
/// Userdata to be stored in the account
|
||||||
|
pub userdata: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An atomic transaction
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Transaction {
|
pub struct Transaction {
|
||||||
/// A digital signature of `keys`, `program_id`, `last_id`, `fee` and `userdata`, signed by `Pubkey`.
|
/// A digital signature of `keys`, `program_id`, `last_id`, `fee` and `userdata`, signed by `Pubkey`.
|
||||||
|
@ -18,13 +31,10 @@ pub struct Transaction {
|
||||||
|
|
||||||
/// The `Pubkeys` that are executing this transaction userdata. The meaning of each key is
|
/// The `Pubkeys` that are executing this transaction userdata. The meaning of each key is
|
||||||
/// program-specific.
|
/// program-specific.
|
||||||
/// * keys[0] - Typically this is the `caller` public key. `signature` is verified with keys[0].
|
/// * account_keys[0] - Typically this is the `caller` public key. `signature` is verified with account_keys[0].
|
||||||
/// In the future which key pays the fee and which keys have signatures would be configurable.
|
/// In the future which key pays the fee and which keys have signatures would be configurable.
|
||||||
/// * keys[1] - Typically this is the program context or the recipient of the tokens
|
/// * account_keys[1] - Typically this is the program context or the recipient of the tokens
|
||||||
pub keys: Vec<Pubkey>,
|
pub account_keys: Vec<Pubkey>,
|
||||||
|
|
||||||
/// The program code that executes this transaction is identified by the program_id.
|
|
||||||
pub program_id: Pubkey,
|
|
||||||
|
|
||||||
/// The ID of a recent ledger entry.
|
/// The ID of a recent ledger entry.
|
||||||
pub last_id: Hash,
|
pub last_id: Hash,
|
||||||
|
@ -32,18 +42,14 @@ pub struct Transaction {
|
||||||
/// The number of tokens paid for processing and storage of this transaction.
|
/// The number of tokens paid for processing and storage of this transaction.
|
||||||
pub fee: i64,
|
pub fee: i64,
|
||||||
|
|
||||||
/// Userdata to be stored in the account
|
/// Keys indentifying programs in the instructions vector.
|
||||||
pub userdata: Vec<u8>,
|
pub program_keys: Vec<Pubkey>,
|
||||||
|
/// Programs that will be executed in sequence and commited in one atomic transaction if all
|
||||||
|
/// succeed.
|
||||||
|
pub instructions: Vec<Instruction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
/// Create a signed transaction from the given `Instruction`.
|
|
||||||
/// * `from_keypair` - The key used to sign the transaction. This key is stored as keys[0]
|
|
||||||
/// * `transaction_keys` - The keys for the transaction. These are the program state
|
|
||||||
/// instances or token recipient keys.
|
|
||||||
/// * `userdata` - The input data that the program will execute with
|
|
||||||
/// * `last_id` - The PoH hash.
|
|
||||||
/// * `fee` - The transaction fee.
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
from_keypair: &Keypair,
|
from_keypair: &Keypair,
|
||||||
transaction_keys: &[Pubkey],
|
transaction_keys: &[Pubkey],
|
||||||
|
@ -52,36 +58,80 @@ impl Transaction {
|
||||||
last_id: Hash,
|
last_id: Hash,
|
||||||
fee: i64,
|
fee: i64,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let from = from_keypair.pubkey();
|
let program_keys = vec![program_id];
|
||||||
let mut keys = vec![from];
|
let instructions = vec![Instruction {
|
||||||
keys.extend_from_slice(transaction_keys);
|
program_id: 0,
|
||||||
let mut tx = Transaction {
|
userdata,
|
||||||
signature: Signature::default(),
|
accounts: (0..(transaction_keys.len() as u8 + 1))
|
||||||
keys,
|
.into_iter()
|
||||||
program_id,
|
.collect(),
|
||||||
|
}];
|
||||||
|
Self::new_with_instructions(
|
||||||
|
from_keypair,
|
||||||
|
transaction_keys,
|
||||||
last_id,
|
last_id,
|
||||||
fee,
|
fee,
|
||||||
userdata,
|
program_keys,
|
||||||
|
instructions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/// Create a signed transaction
|
||||||
|
/// * `from_keypair` - The key used to sign the transaction. This key is stored as keys[0]
|
||||||
|
/// * `account_keys` - The keys for the transaction. These are the program state
|
||||||
|
/// instances or token recipient keys.
|
||||||
|
/// * `last_id` - The PoH hash.
|
||||||
|
/// * `fee` - The transaction fee.
|
||||||
|
/// * `program_keys` - The keys that identify programs used in the `instruction` vector.
|
||||||
|
/// * `instructions` - The programs and their arguments that the transaction will execute atomically
|
||||||
|
pub fn new_with_instructions(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
keys: &[Pubkey],
|
||||||
|
last_id: Hash,
|
||||||
|
fee: i64,
|
||||||
|
program_keys: Vec<Pubkey>,
|
||||||
|
instructions: Vec<Instruction>,
|
||||||
|
) -> Self {
|
||||||
|
let from = from_keypair.pubkey();
|
||||||
|
let mut account_keys = vec![from];
|
||||||
|
account_keys.extend_from_slice(keys);
|
||||||
|
let mut tx = Transaction {
|
||||||
|
signature: Signature::default(),
|
||||||
|
account_keys,
|
||||||
|
last_id,
|
||||||
|
fee,
|
||||||
|
program_keys,
|
||||||
|
instructions,
|
||||||
};
|
};
|
||||||
tx.sign(from_keypair);
|
tx.sign(from_keypair);
|
||||||
tx
|
tx
|
||||||
}
|
}
|
||||||
|
pub fn userdata(&self, program_index: usize) -> &[u8] {
|
||||||
|
&self.instructions[program_index].userdata
|
||||||
|
}
|
||||||
|
pub fn key(&self, program_index: usize, kix: usize) -> Option<&Pubkey> {
|
||||||
|
self.instructions
|
||||||
|
.get(program_index)
|
||||||
|
.and_then(|p| p.accounts.get(kix))
|
||||||
|
.and_then(|ai| self.account_keys.get(*ai as usize))
|
||||||
|
}
|
||||||
|
pub fn program_id(&self, program_index: usize) -> &Pubkey {
|
||||||
|
&self.program_keys[self.instructions[program_index].program_id as usize]
|
||||||
|
}
|
||||||
/// Get the transaction data to sign.
|
/// Get the transaction data to sign.
|
||||||
pub fn get_sign_data(&self) -> Vec<u8> {
|
pub fn get_sign_data(&self) -> Vec<u8> {
|
||||||
let mut data = serialize(&(&self.keys)).expect("serialize keys");
|
let mut data = serialize(&self.account_keys).expect("serialize account_keys");
|
||||||
|
|
||||||
let program_id = serialize(&(&self.program_id)).expect("serialize program_id");
|
let last_id_data = serialize(&self.last_id).expect("serialize last_id");
|
||||||
data.extend_from_slice(&program_id);
|
|
||||||
|
|
||||||
let last_id_data = serialize(&(&self.last_id)).expect("serialize last_id");
|
|
||||||
data.extend_from_slice(&last_id_data);
|
data.extend_from_slice(&last_id_data);
|
||||||
|
|
||||||
let fee_data = serialize(&(&self.fee)).expect("serialize last_id");
|
let fee_data = serialize(&self.fee).expect("serialize last_id");
|
||||||
data.extend_from_slice(&fee_data);
|
data.extend_from_slice(&fee_data);
|
||||||
|
|
||||||
let userdata = serialize(&(&self.userdata)).expect("serialize userdata");
|
let program_keys = serialize(&self.program_keys).expect("serialize program_keys");
|
||||||
data.extend_from_slice(&userdata);
|
data.extend_from_slice(&program_keys);
|
||||||
|
|
||||||
|
let instructions = serialize(&self.instructions).expect("serialize instructions");
|
||||||
|
data.extend_from_slice(&instructions);
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,8 +148,23 @@ impl Transaction {
|
||||||
.verify(&self.from().as_ref(), &self.get_sign_data())
|
.verify(&self.from().as_ref(), &self.get_sign_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from(&self) -> &Pubkey {
|
/// Verify that references in the instructions are valid
|
||||||
&self.keys[0]
|
pub fn verify_refs(&self) -> bool {
|
||||||
|
for instruction in &self.instructions {
|
||||||
|
if (instruction.program_id as usize) >= self.program_keys.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for account_index in &instruction.accounts {
|
||||||
|
if (*account_index as usize) >= self.account_keys.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from(&self) -> &Pubkey {
|
||||||
|
&self.account_keys[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// a hash of a slice of transactions only needs to hash the signatures
|
// a hash of a slice of transactions only needs to hash the signatures
|
||||||
|
@ -118,6 +183,81 @@ mod tests {
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
use signature::GenKeys;
|
use signature::GenKeys;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refs() {
|
||||||
|
let key = Keypair::new();
|
||||||
|
let key1 = Keypair::new().pubkey();
|
||||||
|
let key2 = Keypair::new().pubkey();
|
||||||
|
let prog1 = Keypair::new().pubkey();
|
||||||
|
let prog2 = Keypair::new().pubkey();
|
||||||
|
let instructions = vec![
|
||||||
|
Instruction {
|
||||||
|
program_id: 0,
|
||||||
|
userdata: vec![],
|
||||||
|
accounts: vec![0, 1],
|
||||||
|
},
|
||||||
|
Instruction {
|
||||||
|
program_id: 1,
|
||||||
|
userdata: vec![],
|
||||||
|
accounts: vec![0, 2],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let tx = Transaction::new_with_instructions(
|
||||||
|
&key,
|
||||||
|
&[key1, key2],
|
||||||
|
Default::default(),
|
||||||
|
0,
|
||||||
|
vec![prog1, prog2],
|
||||||
|
instructions,
|
||||||
|
);
|
||||||
|
assert!(tx.verify_refs());
|
||||||
|
assert_eq!(tx.key(0, 0), Some(&key.pubkey()));
|
||||||
|
assert_eq!(tx.key(1, 0), Some(&key.pubkey()));
|
||||||
|
assert_eq!(tx.key(0, 1), Some(&key1));
|
||||||
|
assert_eq!(tx.key(1, 1), Some(&key2));
|
||||||
|
assert_eq!(tx.key(2, 0), None);
|
||||||
|
assert_eq!(tx.key(0, 2), None);
|
||||||
|
assert_eq!(*tx.program_id(0), prog1);
|
||||||
|
assert_eq!(*tx.program_id(1), prog2);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_refs_invalid_program_id() {
|
||||||
|
let key = Keypair::new();
|
||||||
|
let instructions = vec![Instruction {
|
||||||
|
program_id: 1,
|
||||||
|
userdata: vec![],
|
||||||
|
accounts: vec![],
|
||||||
|
}];
|
||||||
|
let tx = Transaction::new_with_instructions(
|
||||||
|
&key,
|
||||||
|
&[],
|
||||||
|
Default::default(),
|
||||||
|
0,
|
||||||
|
vec![],
|
||||||
|
instructions,
|
||||||
|
);
|
||||||
|
assert!(!tx.verify_refs());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_refs_invalid_account() {
|
||||||
|
let key = Keypair::new();
|
||||||
|
let instructions = vec![Instruction {
|
||||||
|
program_id: 0,
|
||||||
|
userdata: vec![],
|
||||||
|
accounts: vec![1],
|
||||||
|
}];
|
||||||
|
let tx = Transaction::new_with_instructions(
|
||||||
|
&key,
|
||||||
|
&[],
|
||||||
|
Default::default(),
|
||||||
|
0,
|
||||||
|
vec![Default::default()],
|
||||||
|
instructions,
|
||||||
|
);
|
||||||
|
assert_eq!(*tx.program_id(0), Default::default());
|
||||||
|
assert!(!tx.verify_refs());
|
||||||
|
}
|
||||||
|
|
||||||
/// Detect binary changes in the serialized contract userdata, which could have a downstream
|
/// Detect binary changes in the serialized contract userdata, which could have a downstream
|
||||||
/// affect on SDKs and DApps
|
/// affect on SDKs and DApps
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -144,18 +284,19 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serialize(&tx).unwrap(),
|
serialize(&tx).unwrap(),
|
||||||
vec![
|
vec![
|
||||||
88, 1, 212, 176, 31, 197, 35, 156, 135, 24, 30, 57, 204, 253, 224, 28, 89, 189, 53,
|
234, 139, 34, 5, 120, 28, 107, 203, 69, 25, 236, 200, 164, 1, 12, 47, 147, 53, 41,
|
||||||
64, 27, 148, 42, 199, 43, 236, 85, 182, 150, 64, 96, 53, 255, 235, 90, 197, 228, 6,
|
143, 23, 116, 230, 203, 59, 228, 153, 14, 22, 241, 103, 226, 186, 169, 181, 65, 49,
|
||||||
105, 22, 140, 209, 206, 221, 85, 117, 125, 126, 11, 1, 176, 130, 57, 236, 7, 155,
|
215, 44, 2, 61, 214, 113, 216, 184, 206, 147, 104, 140, 225, 138, 21, 172, 135,
|
||||||
127, 58, 130, 92, 230, 219, 254, 0, 3, 0, 0, 0, 0, 0, 0, 0, 32, 253, 186, 201, 177,
|
211, 80, 103, 80, 216, 106, 249, 86, 194, 1, 3, 0, 0, 0, 0, 0, 0, 0, 32, 253, 186,
|
||||||
11, 117, 135, 187, 167, 181, 188, 22, 59, 206, 105, 231, 150, 215, 30, 78, 212, 76,
|
201, 177, 11, 117, 135, 187, 167, 181, 188, 22, 59, 206, 105, 231, 150, 215, 30,
|
||||||
16, 252, 180, 72, 134, 137, 247, 161, 68, 32, 253, 186, 201, 177, 11, 117, 135,
|
78, 212, 76, 16, 252, 180, 72, 134, 137, 247, 161, 68, 32, 253, 186, 201, 177, 11,
|
||||||
187, 167, 181, 188, 22, 59, 206, 105, 231, 150, 215, 30, 78, 212, 76, 16, 252, 180,
|
117, 135, 187, 167, 181, 188, 22, 59, 206, 105, 231, 150, 215, 30, 78, 212, 76, 16,
|
||||||
72, 134, 137, 247, 161, 68, 1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
|
252, 180, 72, 134, 137, 247, 161, 68, 1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9,
|
||||||
9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4, 1, 1, 1, 2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1,
|
9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0,
|
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3
|
1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,14 @@ fn test_native_move_funds_succes_many_threads() {
|
||||||
thread.join().unwrap();
|
thread.join().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn process_transaction(
|
||||||
|
tx: &Transaction,
|
||||||
|
accounts: &mut [Account],
|
||||||
|
loaded_programs: &RwLock<HashMap<Pubkey, DynamicProgram>>,
|
||||||
|
) {
|
||||||
|
let mut refs: Vec<&mut Account> = accounts.iter_mut().collect();
|
||||||
|
SystemProgram::process_transaction(&tx, 0, &mut refs[..], loaded_programs)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_system_program_load_call() {
|
fn test_system_program_load_call() {
|
||||||
|
@ -158,7 +166,7 @@ fn test_system_program_load_call() {
|
||||||
"move_funds".to_string(),
|
"move_funds".to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts, &loaded_programs);
|
process_transaction(&tx, &mut accounts, &loaded_programs);
|
||||||
}
|
}
|
||||||
// then call the program
|
// then call the program
|
||||||
{
|
{
|
||||||
|
@ -211,7 +219,7 @@ fn test_system_program_load_call_many_threads() {
|
||||||
"move_funds".to_string(),
|
"move_funds".to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts, &loaded_programs);
|
process_transaction(&tx, &mut accounts, &loaded_programs);
|
||||||
}
|
}
|
||||||
// then call the program
|
// then call the program
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue