Migrate Budget DSL to use the Account state (#979)

* Migrate Budget DSL to use the Account state instead of global bank data structures.

* Serialize Instruction into Transaction::userdata.
* Store the pending set in the Account::userdata
* Enforce the token balance rules on contract execution. This becomes the entry point for generic contracts.
* This pr will have a performance impact on the bank. The next set of changes will fix this by locking each account during multi threaded execution of all the contracts.
* With this change a contract transaction needs to store its state under an address. That address could be the destination of the tokens, or any random address. For the latter, an extra step would be needed to claim the tokens which isn't implemented by budget_dsl at the moment.
* test tracking issue 1157
This commit is contained in:
anatoly yakovenko 2018-09-07 20:18:36 -07:00 committed by GitHub
parent ddd1871840
commit c34d911eaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 417 additions and 242 deletions

View File

@ -3,7 +3,7 @@
//! on behalf of the caller, and a low-level API for when they have
//! already been signed and verified.
use bincode::serialize;
use bincode::{deserialize, serialize};
use chrono::prelude::*;
use counter::Counter;
use entry::Entry;
@ -36,7 +36,7 @@ pub const MAX_ENTRY_IDS: usize = 1024 * 16;
pub const VERIFY_BLOCK_SIZE: usize = 16;
/// Reasons a transaction might be rejected.
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum BankError {
/// Attempt to debit from `Pubkey`, but no found no record of a prior credit.
AccountNotFound(Pubkey),
@ -61,6 +61,10 @@ pub enum BankError {
/// Proof of History verification failed.
LedgerVerificationFailed,
/// Contract's transaction token balance does not equal the balance after the transaction
UnbalancedTransaction(Signature),
/// ContractAlreadyPending
ContractAlreadyPending(Pubkey),
}
pub type Result<T> = result::Result<T, BankError>;
@ -73,17 +77,17 @@ pub struct Account {
/// A transaction can write to its userdata
pub userdata: Vec<u8>,
}
#[derive(Default)]
struct ErrorCounters {
account_not_found_validator: usize,
account_not_found_leader: usize,
account_not_found_vote: usize,
}
/// The state of all accounts and contracts after processing its entries.
pub struct Bank {
/// A map of account public keys to the balance in that account.
accounts: RwLock<HashMap<Pubkey, Account>>,
/// A map of smart contract transaction signatures to what remains of its payment
/// plan. Each transaction that targets the plan should cause it to be reduced.
/// Once it cannot be reduced, final payments are made and it is discarded.
pending: RwLock<HashMap<Signature, Plan>>,
/// A FIFO queue of `last_id` items, where each item is a set of signatures
/// that have been processed using that `last_id`. Rejected `last_id`
/// values are so old that the `last_id` has been pulled out of the queue.
@ -109,7 +113,6 @@ impl Default for Bank {
fn default() -> Self {
Bank {
accounts: RwLock::new(HashMap::new()),
pending: RwLock::new(HashMap::new()),
last_ids: RwLock::new(VecDeque::new()),
last_ids_sigs: RwLock::new(HashMap::new()),
transaction_count: AtomicUsize::new(0),
@ -129,7 +132,11 @@ impl Bank {
/// Create an Bank using a deposit.
pub fn new_from_deposit(deposit: &Payment) -> Self {
let bank = Self::default();
bank.apply_payment(deposit, &mut bank.accounts.write().unwrap());
{
let mut accounts = bank.accounts.write().unwrap();
let account = accounts.entry(deposit.to).or_insert_with(Account::default);
Self::apply_payment(deposit, account);
}
bank
}
@ -144,12 +151,10 @@ impl Bank {
bank
}
/// Commit funds to the `payment.to` party.
fn apply_payment(&self, payment: &Payment, accounts: &mut HashMap<Pubkey, Account>) {
accounts
.entry(payment.to)
.or_insert_with(Account::default)
.tokens += payment.tokens;
/// Commit funds to the given account
fn apply_payment(payment: &Payment, account: &mut Account) {
trace!("apply payments {}", payment.tokens);
account.tokens += payment.tokens;
}
/// Return the last entry ID registered.
@ -171,23 +176,6 @@ impl Bank {
Ok(())
}
/// Forget the given `signature` because its transaction was rejected.
fn forget_signature(signatures: &mut HashSet<Signature>, signature: &Signature) {
signatures.remove(signature);
}
/// Forget the given `signature` with `last_id` because the transaction was rejected.
fn forget_signature_with_last_id(&self, signature: &Signature, last_id: &Hash) {
if let Some(entry) = self
.last_ids_sigs
.write()
.expect("'last_ids' read lock in forget_signature_with_last_id")
.get_mut(last_id)
{
Self::forget_signature(&mut entry.0, signature);
}
}
/// Forget all signatures. Useful for benchmarking.
pub fn clear_signatures(&self) {
for (_, sigs) in self.last_ids_sigs.write().unwrap().iter_mut() {
@ -247,141 +235,230 @@ impl Bank {
/// Deduct tokens from the 'from' address the account has sufficient
/// funds and isn't a duplicate.
fn apply_debits(
&self,
tx: &Transaction,
accounts: &mut HashMap<Pubkey, Account>,
vote_no_account_err: &mut usize,
no_account_err: &mut usize,
accounts: &mut [Account],
instruction: &Instruction,
) -> Result<()> {
let mut purge = false;
{
let option = accounts.get_mut(&tx.from);
if option.is_none() {
if let Instruction::NewVote(_) = &tx.instruction {
*vote_no_account_err += 1;
} else {
*no_account_err += 1;
}
return Err(BankError::AccountNotFound(tx.from));
}
let bal = option.unwrap();
self.reserve_signature_with_last_id(&tx.signature, &tx.last_id)?;
if let Instruction::NewContract(contract) = &tx.instruction {
let empty = accounts[0].userdata.is_empty();
let tokens = if !empty { 0 } else { accounts[0].tokens };
if let Instruction::NewContract(contract) = &instruction {
if contract.tokens < 0 {
return Err(BankError::NegativeTokens);
}
if bal.tokens < contract.tokens {
self.forget_signature_with_last_id(&tx.signature, &tx.last_id);
return Err(BankError::InsufficientFunds(tx.from));
} else if bal.tokens == contract.tokens {
purge = true;
if tokens < contract.tokens {
return Err(BankError::InsufficientFunds(tx.keys[0]));
} else {
let bal = &mut accounts[0];
bal.tokens -= contract.tokens;
}
};
}
if purge {
accounts.remove(&tx.from);
}
Ok(())
}
/// Apply only a transaction's credits.
/// Note: It is safe to apply credits from multiple transactions in parallel.
fn apply_credits(&self, tx: &Transaction, accounts: &mut HashMap<Pubkey, Account>) {
match &tx.instruction {
fn apply_credits(
tx: &Transaction,
accounts: &mut [Account],
instruction: &Instruction,
) -> Result<()> {
match instruction {
Instruction::NewContract(contract) => {
let plan = contract.plan.clone();
if let Some(payment) = plan.final_payment() {
self.apply_payment(&payment, accounts);
Self::apply_payment(&payment, &mut accounts[1]);
Ok(())
} else if !accounts[1].userdata.is_empty() {
Err(BankError::ContractAlreadyPending(tx.keys[1]))
} else {
let mut pending = self
.pending
.write()
.expect("'pending' write lock in apply_credits");
let mut pending = HashMap::new();
pending.insert(tx.signature, plan);
//TODO this is a temporary on demand allocaiton
//until system contract requires explicit allocation of memory
accounts[1].userdata = serialize(&pending).unwrap();
accounts[1].tokens += contract.tokens;
Ok(())
}
}
Instruction::ApplyTimestamp(dt) => {
let _ = self.apply_timestamp(tx.from, *dt);
Self::apply_timestamp(tx.keys[0], *dt, &mut accounts[1]);
Ok(())
}
Instruction::ApplySignature(signature) => {
let _ = self.apply_signature(tx.from, *signature);
Self::apply_signature(tx.keys[0], *signature, accounts);
Ok(())
}
Instruction::NewVote(_vote) => {
trace!("GOT VOTE! last_id={:?}", &tx.last_id.as_ref()[..8]);
// TODO: record the vote in the stake table...
trace!("GOT VOTE! last_id={}", tx.last_id);
Ok(())
}
}
}
fn save_data(&self, tx: &Transaction, accounts: &mut HashMap<Pubkey, Account>) {
//TODO This is a temporary implementation until the full rules on memory management for
//smart contracts are implemented. See github issue #953
if !tx.userdata.is_empty() {
if let Some(ref mut account) = accounts.get_mut(&tx.from) {
if account.userdata.len() != tx.userdata.len() {
account.userdata.resize(tx.userdata.len(), 0);
}
account.userdata.copy_from_slice(&tx.userdata);
/// Budget DSL contract interface
/// * tx - the transaction
/// * accounts[0] - The source of the tokens
/// * accounts[1] - The contract context. Once the contract has been completed, the tokens can
/// be spent from this account .
pub fn process_transaction_of_budget_instruction(
tx: &Transaction,
accounts: &mut [Account],
) -> Result<()> {
let instruction = tx.instruction();
Self::apply_debits(tx, accounts, &instruction)?;
Self::apply_credits(tx, accounts, &instruction)
}
//TODO the contract needs to provide a "get_balance" introspection call of the userdata
pub fn get_balance_of_budget_payment_plan(account: &Account) -> i64 {
if let Ok(pending) = deserialize(&account.userdata) {
let pending: HashMap<Signature, Plan> = pending;
if !pending.is_empty() {
0
} else {
account.tokens
}
} else {
account.tokens
}
}
/// Process a Transaction. If it contains a payment plan that requires a witness
/// to progress, the payment plan will be stored in the bank.
pub fn process_transaction(&self, tx: &Transaction) -> Result<()> {
let accounts = &mut self.accounts.write().unwrap();
let mut vote_no_account_err = 0;
let mut no_account_err = 0;
self.apply_debits(tx, accounts, &mut vote_no_account_err, &mut no_account_err)?;
self.apply_credits(tx, accounts);
self.save_data(tx, accounts);
self.transaction_count.fetch_add(1, Ordering::Relaxed);
Ok(())
match self.process_transactions(vec![tx.clone()])[0] {
Err(ref e) => {
info!("process_transaction error: {:?}", e);
Err((*e).clone())
}
Ok(_) => Ok(()),
}
}
fn load_account(
&self,
tx: &Transaction,
accounts: &HashMap<Pubkey, Account>,
error_counters: &mut ErrorCounters,
) -> Result<Vec<Account>> {
// Copy all the accounts
if accounts.get(&tx.keys[0]).is_none() {
if !self.is_leader {
error_counters.account_not_found_validator += 1;
} else {
error_counters.account_not_found_leader += 1;
}
if let Instruction::NewVote(_vote) = tx.instruction() {
error_counters.account_not_found_vote += 1;
}
Err(BankError::AccountNotFound(*tx.from()))
} else if accounts.get(&tx.keys[0]).unwrap().tokens < tx.fee {
Err(BankError::InsufficientFunds(*tx.from()))
} else {
let mut called_accounts: Vec<Account> = tx
.keys
.iter()
.map(|key| accounts.get(key).cloned().unwrap_or(Account::default()))
.collect();
// There is no way to predict what contract will execute without an error
// If a fee can pay for execution then the contract will be scheduled
self.reserve_signature_with_last_id(&tx.signature, &tx.last_id)?;
called_accounts[0].tokens -= tx.fee;
Ok(called_accounts)
}
}
fn load_accounts(
&self,
txs: &Vec<Transaction>,
accounts: &HashMap<Pubkey, Account>,
error_counters: &mut ErrorCounters,
) -> Vec<Result<Vec<Account>>> {
txs.iter()
.map(|tx| self.load_account(tx, accounts, error_counters))
.collect()
}
pub fn execute_transaction(tx: Transaction, accounts: &mut [Account]) -> Result<Transaction> {
let pre_total: i64 = accounts.iter().map(|a| a.tokens).sum();
// TODO next steps is to add hooks to call arbitrary contracts here
// Call the contract method
// It's up to the contract to implement its own rules on moving funds
let e = Self::process_transaction_of_budget_instruction(&tx, accounts);
// Verify the transaction
// TODO, At the moment there is only 1 contract, so 1-3 are not checked
// 1. For accounts assigned to the contract, the total sum of all the tokens in these accounts cannot increase.
// 2. For accounts unassigned to the contract, the individual balance of each accounts cannot decrease.
// 3. For accounts unassigned to the contract, the userdata cannot change.
// 4. The total sum of all the tokens in all the pages cannot change.
let post_total: i64 = accounts.iter().map(|a| a.tokens).sum();
if pre_total != post_total {
Err(BankError::UnbalancedTransaction(tx.signature))
} else if let Err(err) = e {
Err(err)
} else {
Ok(tx)
}
}
pub fn store_accounts(
res: &Vec<Result<Transaction>>,
loaded: &Vec<Result<Vec<Account>>>,
accounts: &mut HashMap<Pubkey, Account>,
) {
loaded.iter().zip(res.iter()).for_each(|(racc, rtx)| {
if let (Ok(acc), Ok(tx)) = (racc, rtx) {
tx.keys.iter().zip(acc.iter()).for_each(|(key, account)| {
//purge if 0
if account.tokens == 0 {
accounts.remove(&key);
} else {
*accounts.entry(*key).or_insert_with(Account::default) = account.clone();
assert_eq!(accounts.get(key).unwrap().tokens, account.tokens);
}
});
};
});
}
/// Process a batch of transactions.
#[must_use]
pub fn process_transactions(&self, txs: Vec<Transaction>) -> Vec<Result<Transaction>> {
let accounts = &mut self.accounts.write().unwrap();
debug!("processing Transactions {}", txs.len());
// TODO right now a single write lock is held for the duration of processing all the
// transactions
// To break this lock each account needs to be locked to prevent concurrent access
let mut accounts = self.accounts.write().unwrap();
let txs_len = txs.len();
let mut vote_no_account_err = 0;
let mut no_account_err = 0;
let mut error_counters = ErrorCounters::default();
let now = Instant::now();
let results: Vec<_> = txs
.into_iter()
.map(|tx| {
self.apply_debits(&tx, accounts, &mut vote_no_account_err, &mut no_account_err)
.map(|_| tx)
})
.collect(); // Calling collect() here forces all debits to complete before moving on.
let debits = now.elapsed();
let mut loaded_accounts = self.load_accounts(&txs, &mut accounts, &mut error_counters);
let load_elapsed = now.elapsed();
let now = Instant::now();
let res: Vec<_> = results
.into_iter()
.map(|result| {
result.map(|tx| {
self.apply_credits(&tx, accounts);
tx
})
let res: Vec<Result<Transaction>> = loaded_accounts
.iter_mut()
.zip(txs.into_iter())
.map(|(acc, tx)| match acc {
Err(e) => Err(e.clone()),
Ok(ref mut accounts) => Self::execute_transaction(tx, accounts),
})
.collect();
let execution_elapsed = now.elapsed();
let now = Instant::now();
Self::store_accounts(&res, &loaded_accounts, &mut accounts);
let write_elapsed = now.elapsed();
debug!(
"debits: {} us credits: {:?} us tx: {}",
duration_as_us(&debits),
duration_as_us(&now.elapsed()),
"load: {} us execution: {} us write: {} us tx: {}",
duration_as_us(&load_elapsed),
duration_as_us(&execution_elapsed),
duration_as_us(&write_elapsed),
txs_len
);
let mut tx_count = 0;
let mut err_count = 0;
for r in &res {
@ -389,7 +466,7 @@ impl Bank {
tx_count += 1;
} else {
if err_count == 0 {
info!("tx error: {:?}", r);
trace!("tx error: {:?}", r);
}
err_count += 1;
}
@ -400,14 +477,17 @@ impl Bank {
inc_new_counter_info!("bank-process_transactions_err-validator", err_count);
inc_new_counter_info!(
"bank-appy_debits-account_not_found-validator",
no_account_err
error_counters.account_not_found_validator
);
} else {
inc_new_counter_info!("bank-process_transactions_err-leader", err_count);
inc_new_counter_info!("bank-appy_debits-generic_account_not_found", no_account_err);
inc_new_counter_info!(
"bank-appy_debits-account_not_found-leader",
error_counters.account_not_found_leader
);
inc_new_counter_info!(
"bank-appy_debits-vote_account_not_found",
vote_no_account_err
error_counters.account_not_found_vote
);
}
}
@ -507,13 +587,18 @@ impl Bank {
.expect("invalid ledger: need at least 2 entries");
{
let tx = &entry1.transactions[0];
let deposit = if let Instruction::NewContract(contract) = &tx.instruction {
let instruction = tx.instruction();
let deposit = if let Instruction::NewContract(contract) = instruction {
contract.plan.final_payment()
} else {
None
}.expect("invalid ledger, needs to start with a contract");
self.apply_payment(&deposit, &mut self.accounts.write().unwrap());
{
let mut accounts = self.accounts.write().unwrap();
let entry = accounts.entry(tx.keys[0]).or_insert_with(Account::default);
Self::apply_payment(&deposit, entry);
trace!("applied genesis payment {:?} {:?}", deposit, entry);
}
}
self.register_entry_id(&entry0.id);
self.register_entry_id(&entry1.id);
@ -535,39 +620,40 @@ impl Bank {
/// Process a Witness Signature. Any payment plans waiting on this signature
/// will progress one step.
fn apply_signature(&self, from: Pubkey, signature: Signature) -> Result<()> {
if let Occupied(mut e) = self
.pending
.write()
.expect("write() in apply_signature")
.entry(signature)
{
fn apply_signature(from: Pubkey, signature: Signature, account: &mut [Account]) {
let mut pending: HashMap<Signature, Plan> =
deserialize(&account[1].userdata).unwrap_or(HashMap::new());
if let Occupied(mut e) = pending.entry(signature) {
e.get_mut().apply_witness(&Witness::Signature, &from);
if let Some(payment) = e.get().final_payment() {
self.apply_payment(&payment, &mut self.accounts.write().unwrap());
//move the tokens back to the from account
account[0].tokens += payment.tokens;
account[1].tokens -= payment.tokens;
e.remove_entry();
}
};
Ok(())
//TODO this allocation needs to be changed once the runtime only allows for explitly
//allocated memory
account[1].userdata = if pending.is_empty() {
vec![]
} else {
serialize(&pending).unwrap()
};
}
/// Process a Witness Timestamp. Any payment plans waiting on this timestamp
/// will progress one step.
fn apply_timestamp(&self, from: Pubkey, dt: DateTime<Utc>) -> Result<()> {
fn apply_timestamp(from: Pubkey, dt: DateTime<Utc>, account: &mut Account) {
let mut pending: HashMap<Signature, Plan> =
deserialize(&account.userdata).unwrap_or(HashMap::new());
// Check to see if any timelocked transactions can be completed.
let mut completed = vec![];
// Hold 'pending' write lock until the end of this function. Otherwise another thread can
// double-spend if it enters before the modified plan is removed from 'pending'.
let mut pending = self
.pending
.write()
.expect("'pending' write lock in apply_timestamp");
for (key, plan) in pending.iter_mut() {
plan.apply_witness(&Witness::Timestamp(dt), &from);
if let Some(payment) = plan.final_payment() {
self.apply_payment(&payment, &mut self.accounts.write().unwrap());
if let Some(_payment) = plan.final_payment() {
completed.push(key.clone());
}
}
@ -575,8 +661,13 @@ impl Bank {
for key in completed {
pending.remove(&key);
}
Ok(())
//TODO this allocation needs to be changed once the runtime only allows for explitly
//allocated memory
account.userdata = if pending.is_empty() {
vec![]
} else {
serialize(&pending).unwrap()
};
}
/// Create, sign, and process a Transaction from `keypair` to `to` of
@ -610,7 +701,9 @@ impl Bank {
}
pub fn get_balance(&self, pubkey: &Pubkey) -> i64 {
self.get_account(pubkey).map(|a| a.tokens).unwrap_or(0)
self.get_account(pubkey)
.map(|x| Self::get_balance_of_budget_payment_plan(&x))
.unwrap_or(0)
}
pub fn get_account(&self, pubkey: &Pubkey) -> Option<Account> {
@ -672,6 +765,13 @@ mod tests {
use std::io::{BufReader, Cursor, Seek, SeekFrom};
use std::mem::size_of;
#[test]
fn test_bank_new() {
let mint = Mint::new(10_000);
let bank = Bank::new(&mint);
assert_eq!(bank.get_balance(&mint.pubkey()), 10_000);
}
#[test]
fn test_two_payments_to_one_party() {
let mint = Mint::new(10_000);
@ -701,6 +801,23 @@ mod tests {
assert_eq!(bank.transaction_count(), 0);
}
// TODO: This test verifies potentially undesirable behavior
// See github issue 1157 (https://github.com/solana-labs/solana/issues/1157)
#[test]
fn test_detect_failed_duplicate_transactions_issue_1157() {
let mint = Mint::new(1);
let bank = Bank::new(&mint);
let tx = Transaction::new(&mint.keypair(), mint.keypair().pubkey(), -1, mint.last_id());
let signature = tx.signature;
assert!(!bank.has_signature(&signature));
assert_eq!(
bank.process_transaction(&tx),
Err(BankError::NegativeTokens)
);
assert!(bank.has_signature(&signature));
}
#[test]
fn test_account_not_found() {
let mint = Mint::new(1);
@ -721,6 +838,7 @@ mod tests {
bank.transfer(1_000, &mint.keypair(), pubkey, mint.last_id())
.unwrap();
assert_eq!(bank.transaction_count(), 1);
assert_eq!(bank.get_balance(&pubkey), 1_000);
assert_eq!(
bank.transfer(10_001, &mint.keypair(), pubkey, mint.last_id()),
Err(BankError::InsufficientFunds(mint.pubkey()))
@ -742,56 +860,48 @@ mod tests {
assert_eq!(bank.get_balance(&pubkey), 500);
}
#[test]
fn test_userdata() {
let mint = Mint::new(10_000);
let bank = Bank::new(&mint);
let pubkey = mint.keypair().pubkey();
let mut tx = Transaction::new(&mint.keypair(), pubkey, 0, bank.last_id());
tx.userdata = vec![1, 2, 3];
let rv = bank.process_transaction(&tx);
assert!(rv.is_ok());
let account = bank.get_account(&pubkey);
assert!(account.is_some());
assert_eq!(account.unwrap().userdata, vec![1, 2, 3]);
}
#[test]
fn test_transfer_on_date() {
let mint = Mint::new(1);
let mint = Mint::new(2);
let bank = Bank::new(&mint);
let pubkey = Keypair::new().pubkey();
let dt = Utc::now();
bank.transfer_on_date(1, &mint.keypair(), pubkey, dt, mint.last_id())
.unwrap();
// Mint's balance will be zero because all funds are locked up.
assert_eq!(bank.get_balance(&mint.pubkey()), 0);
// Mint's balance will be 1 because 1 of the tokens is locked up
assert_eq!(bank.get_balance(&mint.pubkey()), 1);
// tx count is 1, because debits were applied.
assert_eq!(bank.transaction_count(), 1);
// pubkey's balance will be None because the funds have not been
// pubkey's balance will be 0 because the funds have not been
// sent.
assert_eq!(bank.get_balance(&pubkey), 0);
// Now, acknowledge the time in the condition occurred and
// that pubkey's funds are now available.
bank.apply_timestamp(mint.pubkey(), dt).unwrap();
let tx = Transaction::new_timestamp(&mint.keypair(), pubkey, dt, bank.last_id());
let res = bank.process_transaction(&tx);
assert!(res.is_ok());
assert_eq!(bank.get_balance(&pubkey), 1);
// tx count is still 1, because we chose not to count timestamp transactions
// tx count.
assert_eq!(bank.transaction_count(), 1);
// tx count is 2
assert_eq!(bank.transaction_count(), 2);
bank.apply_timestamp(mint.pubkey(), dt).unwrap(); // <-- Attack! Attempt to process completed transaction.
assert_ne!(bank.get_balance(&pubkey), 2);
// try to replay the timestamp contract
bank.register_entry_id(&hash(bank.last_id().as_ref()));
let tx = Transaction::new_timestamp(&mint.keypair(), pubkey, dt, bank.last_id());
let res = bank.process_transaction(&tx);
assert!(res.is_ok());
assert_eq!(bank.get_balance(&pubkey), 1);
}
#[test]
fn test_cancel_transfer() {
let mint = Mint::new(1);
// mint needs to have a balance to modify the external contract
let mint = Mint::new(2);
let bank = Bank::new(&mint);
let pubkey = Keypair::new().pubkey();
let dt = Utc::now();
@ -802,23 +912,31 @@ mod tests {
// Assert the debit counts as a transaction.
assert_eq!(bank.transaction_count(), 1);
// Mint's balance will be zero because all funds are locked up.
assert_eq!(bank.get_balance(&mint.pubkey()), 0);
// Mint's balance will be 1 because 1 of the tokens is locked up.
assert_eq!(bank.get_balance(&mint.pubkey()), 1);
// pubkey's balance will be None because the funds have not been
// sent.
// pubkey's balance will be 0 because the funds are locked up
assert_eq!(bank.get_balance(&pubkey), 0);
// Now, cancel the trancaction. Mint gets her funds back, pubkey never sees them.
bank.apply_signature(mint.pubkey(), signature).unwrap();
assert_eq!(bank.get_balance(&mint.pubkey()), 1);
let tx = Transaction::new_signature(&mint.keypair(), pubkey, signature, bank.last_id());
let res = bank.process_transaction(&tx);
assert!(res.is_ok());
assert_eq!(bank.get_balance(&pubkey), 0);
assert_eq!(bank.get_balance(&mint.pubkey()), 2);
// Assert cancel doesn't cause count to go backward.
assert_eq!(bank.transaction_count(), 1);
// Assert cancel counts as a tx
assert_eq!(bank.transaction_count(), 2);
bank.apply_signature(mint.pubkey(), signature).unwrap(); // <-- Attack! Attempt to cancel completed transaction.
assert_ne!(bank.get_balance(&mint.pubkey()), 2);
// try to replay the signature contract
bank.register_entry_id(&hash(bank.last_id().as_ref()));
let tx = Transaction::new_signature(&mint.keypair(), pubkey, signature, bank.last_id());
let res = bank.process_transaction(&tx); //<-- attack! try to get budget dsl to pay out with another signature
assert!(res.is_ok());
// balance is is still 2 for the mint
assert_eq!(bank.get_balance(&mint.pubkey()), 2);
// balance is is still 0 for the contract
assert_eq!(bank.get_balance(&pubkey), 0);
}
#[test]
@ -837,13 +955,13 @@ mod tests {
}
#[test]
fn test_forget_signature() {
fn test_clear_signatures() {
let mint = Mint::new(1);
let bank = Bank::new(&mint);
let signature = Signature::default();
bank.reserve_signature_with_last_id(&signature, &mint.last_id())
.unwrap();
bank.forget_signature_with_last_id(&signature, &mint.last_id());
bank.clear_signatures();
assert!(
bank.reserve_signature_with_last_id(&signature, &mint.last_id())
.is_ok()
@ -1128,5 +1246,4 @@ mod tests {
def_bank.set_finality(90);
assert_eq!(def_bank.finality(), 90);
}
}

View File

@ -248,8 +248,8 @@ mod tests {
// First, verify entries
let keypair = Keypair::new();
let tx0 = Transaction::new_timestamp(&keypair, Utc::now(), zero);
let tx1 = Transaction::new_signature(&keypair, Default::default(), zero);
let tx0 = Transaction::new_timestamp(&keypair, keypair.pubkey(), Utc::now(), zero);
let tx1 = Transaction::new_signature(&keypair, keypair.pubkey(), Default::default(), zero);
let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()], false);
assert!(e0.verify(&zero));
@ -271,7 +271,7 @@ mod tests {
assert_eq!(tick.id, zero);
let keypair = Keypair::new();
let tx0 = Transaction::new_timestamp(&keypair, Utc::now(), zero);
let tx0 = Transaction::new_timestamp(&keypair, keypair.pubkey(), Utc::now(), zero);
let entry0 = next_entry(&zero, 1, vec![tx0.clone()]);
assert_eq!(entry0.num_hashes, 1);
assert_eq!(entry0.id, next_hash(&zero, 1, &vec![tx0]));

View File

@ -101,9 +101,11 @@ pub fn read_entries<R: BufRead>(reader: R) -> impl Iterator<Item = io::Result<En
#[cfg(test)]
mod tests {
use super::*;
use bincode::serialize;
use ledger;
use mint::Mint;
use packet::BLOB_DATA_SIZE;
use packet::PACKET_DATA_SIZE;
use signature::{Keypair, KeypairUtil};
use std::io::Cursor;
use transaction::Transaction;
@ -117,9 +119,11 @@ mod tests {
let mut entry_writer = EntryWriter::new(&bank, writer);
let keypair = Keypair::new();
let tx = Transaction::new(&mint.keypair(), keypair.pubkey(), 1, mint.last_id());
let tx_size = serialize(&tx).unwrap().len();
// NOTE: if Entry grows to larger than a transaction, the code below falls over
let threshold = (BLOB_DATA_SIZE / 256) - 1; // 256 is transaction size
assert!(tx_size <= PACKET_DATA_SIZE);
assert!(BLOB_DATA_SIZE >= PACKET_DATA_SIZE);
let threshold = (BLOB_DATA_SIZE / tx_size) - 1; // PACKET_DATA_SIZE is transaction size
// Verify large entries are split up and the first sets has_more.
let txs = vec![tx.clone(); threshold * 2];

View File

@ -590,7 +590,12 @@ mod tests {
Entry::new_mut(
&mut id,
&mut num_hashes,
vec![Transaction::new_timestamp(&keypair, Utc::now(), one)],
vec![Transaction::new_timestamp(
&keypair,
keypair.pubkey(),
Utc::now(),
one,
)],
false,
)
})
@ -610,7 +615,7 @@ mod tests {
one,
1,
);
let tx1 = Transaction::new_timestamp(&keypair, Utc::now(), one);
let tx1 = Transaction::new_timestamp(&keypair, keypair.pubkey(), Utc::now(), one);
//
// TODO: this magic number and the mix of transaction types
// is designed to fill up a Blob more or less exactly,

View File

@ -74,9 +74,9 @@ mod tests {
fn test_create_transactions() {
let mut transactions = Mint::new(100).create_transactions().into_iter();
let tx = transactions.next().unwrap();
if let Instruction::NewContract(contract) = tx.instruction {
if let Instruction::NewContract(contract) = tx.instruction() {
if let Plan::Budget(Budget::Pay(payment)) = contract.plan {
assert_eq!(tx.from, payment.to);
assert_eq!(*tx.from(), payment.to);
}
}
assert_eq!(transactions.next(), None);

View File

@ -503,7 +503,7 @@ impl Blob {
mod tests {
use packet::{
to_packets, Blob, BlobRecycler, Meta, Packet, PacketRecycler, Packets, Recycler, Reset,
BLOB_HEADER_SIZE, NUM_PACKETS,
BLOB_HEADER_SIZE, NUM_PACKETS, PACKET_DATA_SIZE,
};
use request::Request;
use std::io;
@ -577,12 +577,12 @@ mod tests {
p.write().unwrap().packets.resize(10, Packet::default());
for m in p.write().unwrap().packets.iter_mut() {
m.meta.set_addr(&addr);
m.meta.size = 256;
m.meta.size = PACKET_DATA_SIZE;
}
p.read().unwrap().send_to(&sender).unwrap();
p.write().unwrap().recv_from(&reader).unwrap();
for m in p.write().unwrap().packets.iter_mut() {
assert_eq!(m.meta.size, 256);
assert_eq!(m.meta.size, PACKET_DATA_SIZE);
assert_eq!(m.meta.addr(), saddr);
}

View File

@ -105,7 +105,7 @@ pub fn ed25519_verify_cpu(batches: &[SharedPackets]) -> Vec<Vec<u8>> {
pub fn ed25519_verify_disabled(batches: &[SharedPackets]) -> Vec<Vec<u8>> {
use rayon::prelude::*;
let count = batch_size(batches);
info!("CPU ECDSA for {}", batch_size(batches));
info!("disabled ECDSA for {}", batch_size(batches));
let rv = batches
.into_par_iter()
.map(|p| {

View File

@ -3,7 +3,7 @@
//! messages to the network directly. The binary encoding of its messages are
//! unstable and may change in future releases.
use bank::Account;
use bank::{Account, Bank};
use bincode::{deserialize, serialize};
use crdt::{Crdt, CrdtError, NodeInfo};
use hash::Hash;
@ -177,9 +177,12 @@ impl ThinClient {
}
self.process_response(&resp);
}
trace!("get_balance {:?}", self.balances.get(pubkey));
//TODO: call the contract specific get_balance for contract_id's thin client can introspect
//instead of hard coding to budget_dsl only
self.balances
.get(pubkey)
.map(|a| a.tokens)
.map(Bank::get_balance_of_budget_payment_plan)
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "nokey"))
}
@ -508,10 +511,12 @@ mod tests {
let last_id = client.get_last_id();
let mut tr2 = Transaction::new(&alice.keypair(), bob_pubkey, 501, last_id);
if let Instruction::NewContract(contract) = &mut tr2.instruction {
let mut instruction2 = tr2.instruction();
if let Instruction::NewContract(ref mut contract) = instruction2 {
contract.tokens = 502;
contract.plan = Plan::Budget(Budget::new_payment(502, bob_pubkey));
}
tr2.userdata = serialize(&instruction2).unwrap();
let signature = client.transfer_signed(&tr2).unwrap();
client.poll_for_signature(&signature).unwrap();

View File

@ -1,6 +1,6 @@
//! The `transaction` module provides functionality for creating log transactions.
use bincode::serialize;
use bincode::{deserialize, serialize};
use budget::{Budget, Condition};
use chrono::prelude::*;
use hash::Hash;
@ -8,9 +8,9 @@ use payment_plan::{Payment, PaymentPlan, Witness};
use signature::{Keypair, KeypairUtil, Pubkey, Signature};
use std::mem::size_of;
pub const SIGNED_DATA_OFFSET: usize = PUB_KEY_OFFSET + size_of::<Pubkey>();
pub const SIGNED_DATA_OFFSET: usize = size_of::<Signature>();
pub const SIG_OFFSET: usize = 0;
pub const PUB_KEY_OFFSET: usize = size_of::<Signature>();
pub const PUB_KEY_OFFSET: usize = size_of::<Signature>() + size_of::<u64>();
/// The type of payment plan. Each item must implement the PaymentPlan trait.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
@ -82,38 +82,45 @@ pub struct Transaction {
/// A digital signature of `instruction`, `last_id` and `fee`, signed by `Pubkey`.
pub signature: Signature,
/// The `Pubkey` of the entity that signed the transaction data.
pub from: Pubkey,
/// The action the server should take.
pub instruction: Instruction,
/// The `Pubkeys` that are executing this transaction userdata. The meaning of each key is
/// contract-specific.
/// * keys[0] - Typically this is the `from` public key. `signature` is verified with keys[0].
/// In the future which key pays the fee and which keys have signatures would be configurable.
/// * keys[1] - Typically this is the contract context or the recepient of the tokens
pub keys: Vec<Pubkey>,
/// The ID of a recent ledger entry.
pub last_id: Hash,
/// The number of tokens paid for processing and storage of this transaction.
pub fee: i64,
/// Optional user data to be stored in the account
/// TODO: This will be a required field for all contract operations including a simple spend.
/// `instruction` will be serialized into `userdata` once Budget is its own generic contract.
/// Userdata to be stored in the account
pub userdata: Vec<u8>,
}
impl Transaction {
/// Create a signed transaction with userdata and an instruction
fn new_with_userdata_and_instruction(
/// Create a signed transaction from the given `Instruction`.
/// * `from_keypair` - The key used to sign the transcation. This key is stored as keys[0]
/// * `transaction_keys` - The keys for the transaction. These are the contract state
/// instances or token recepient keys.
/// * `userdata` - The input data that the contract will execute with
/// * `last_id` - The PoH hash.
/// * `fee` - The transaction fee.
fn new_with_userdata(
from_keypair: &Keypair,
instruction: Instruction,
transaction_keys: &[Pubkey],
userdata: Vec<u8>,
last_id: Hash,
fee: i64,
userdata: Vec<u8>,
) -> Self {
let from = from_keypair.pubkey();
let mut keys = vec![from];
keys.extend_from_slice(transaction_keys);
let mut tx = Transaction {
signature: Signature::default(),
instruction,
keys,
last_id,
from,
fee,
userdata,
};
@ -123,29 +130,31 @@ impl Transaction {
/// Create a signed transaction from the given `Instruction`.
fn new_from_instruction(
from_keypair: &Keypair,
contract: Pubkey,
instruction: Instruction,
last_id: Hash,
fee: i64,
) -> Self {
Self::new_with_userdata_and_instruction(from_keypair, instruction, last_id, fee, vec![])
let userdata = serialize(&instruction).expect("serealize instruction");
Self::new_with_userdata(from_keypair, &[contract], userdata, last_id, fee)
}
/// Create and sign a new Transaction. Used for unit-testing.
pub fn new_taxed(
from_keypair: &Keypair,
to: Pubkey,
contract: Pubkey,
tokens: i64,
fee: i64,
last_id: Hash,
) -> Self {
let payment = Payment {
tokens: tokens - fee,
to,
to: contract,
};
let budget = Budget::Pay(payment);
let plan = Plan::Budget(budget);
let instruction = Instruction::NewContract(Contract { plan, tokens });
Self::new_from_instruction(from_keypair, instruction, last_id, fee)
Self::new_from_instruction(from_keypair, contract, instruction, last_id, fee)
}
/// Create and sign a new Transaction. Used for unit-testing.
@ -154,19 +163,31 @@ impl Transaction {
}
/// Create and sign a new Witness Timestamp. Used for unit-testing.
pub fn new_timestamp(from_keypair: &Keypair, dt: DateTime<Utc>, last_id: Hash) -> Self {
pub fn new_timestamp(
from_keypair: &Keypair,
contract: Pubkey,
dt: DateTime<Utc>,
last_id: Hash,
) -> Self {
let instruction = Instruction::ApplyTimestamp(dt);
Self::new_from_instruction(from_keypair, instruction, last_id, 0)
Self::new_from_instruction(from_keypair, contract, instruction, last_id, 0)
}
/// Create and sign a new Witness Signature. Used for unit-testing.
pub fn new_signature(from_keypair: &Keypair, signature: Signature, last_id: Hash) -> Self {
pub fn new_signature(
from_keypair: &Keypair,
contract: Pubkey,
signature: Signature,
last_id: Hash,
) -> Self {
let instruction = Instruction::ApplySignature(signature);
Self::new_from_instruction(from_keypair, instruction, last_id, 0)
Self::new_from_instruction(from_keypair, contract, instruction, last_id, 0)
}
pub fn new_vote(from_keypair: &Keypair, vote: Vote, last_id: Hash, fee: i64) -> Self {
Transaction::new_from_instruction(&from_keypair, Instruction::NewVote(vote), last_id, fee)
let instruction = Instruction::NewVote(vote);
let userdata = serialize(&instruction).expect("serealize instruction");
Self::new_with_userdata(from_keypair, &[], userdata, last_id, fee)
}
/// Create and sign a postdated Transaction. Used for unit-testing.
@ -184,12 +205,14 @@ impl Transaction {
);
let plan = Plan::Budget(budget);
let instruction = Instruction::NewContract(Contract { plan, tokens });
Self::new_from_instruction(from_keypair, instruction, last_id, 0)
let userdata = serialize(&instruction).expect("serealize instruction");
Self::new_with_userdata(from_keypair, &[to], userdata, last_id, 0)
}
/// Get the transaction data to sign.
fn get_sign_data(&self) -> Vec<u8> {
let mut data = serialize(&(&self.instruction)).expect("serialize Contract");
let mut data = serialize(&(&self.keys)).expect("serialize keys");
let last_id_data = serialize(&(&self.last_id)).expect("serialize last_id");
data.extend_from_slice(&last_id_data);
@ -198,7 +221,6 @@ impl Transaction {
let userdata = serialize(&(&self.userdata)).expect("serialize userdata");
data.extend_from_slice(&userdata);
data
}
@ -212,12 +234,13 @@ impl Transaction {
pub fn verify_signature(&self) -> bool {
warn!("transaction signature verification called");
self.signature
.verify(&self.from.as_ref(), &self.get_sign_data())
.verify(&self.from().as_ref(), &self.get_sign_data())
}
/// Verify only the payment plan.
pub fn verify_plan(&self) -> bool {
if let Instruction::NewContract(contract) = &self.instruction {
let instruction = deserialize(&self.userdata);
if let Ok(Instruction::NewContract(contract)) = instruction {
self.fee >= 0
&& self.fee <= contract.tokens
&& contract.plan.verify(contract.tokens - self.fee)
@ -225,14 +248,19 @@ impl Transaction {
true
}
}
pub fn vote(&self) -> Option<(Pubkey, Vote, Hash)> {
if let Instruction::NewVote(ref vote) = self.instruction {
Some((self.from, vote.clone(), self.last_id))
if let Instruction::NewVote(vote) = self.instruction() {
Some((*self.from(), vote, self.last_id))
} else {
None
}
}
pub fn from(&self) -> &Pubkey {
&self.keys[0]
}
pub fn instruction(&self) -> Instruction {
deserialize(&self.userdata).unwrap()
}
}
pub fn test_tx() -> Transaction {
@ -258,6 +286,7 @@ pub fn memfind<A: Eq>(a: &[A], b: &[A]) -> Option<usize> {
mod tests {
use super::*;
use bincode::{deserialize, serialize};
use packet::PACKET_DATA_SIZE;
#[test]
fn test_claim() {
@ -295,13 +324,13 @@ mod tests {
});
let plan = Plan::Budget(budget);
let instruction = Instruction::NewContract(Contract { plan, tokens: 0 });
let userdata = serialize(&instruction).unwrap();
let claim0 = Transaction {
instruction,
from: Default::default(),
keys: vec![],
last_id: Default::default(),
signature: Default::default(),
fee: 0,
userdata: vec![],
userdata,
};
let buf = serialize(&claim0).unwrap();
let claim1: Transaction = deserialize(&buf).unwrap();
@ -314,12 +343,14 @@ mod tests {
let keypair = Keypair::new();
let pubkey = keypair.pubkey();
let mut tx = Transaction::new(&keypair, pubkey, 42, zero);
if let Instruction::NewContract(contract) = &mut tx.instruction {
let mut instruction = tx.instruction();
if let Instruction::NewContract(ref mut contract) = instruction {
contract.tokens = 1_000_000; // <-- attack, part 1!
if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan {
payment.tokens = contract.tokens; // <-- attack, part 2!
}
}
tx.userdata = serialize(&instruction).unwrap();
assert!(tx.verify_plan());
assert!(!tx.verify_signature());
}
@ -332,11 +363,13 @@ mod tests {
let pubkey1 = keypair1.pubkey();
let zero = Hash::default();
let mut tx = Transaction::new(&keypair0, pubkey1, 42, zero);
if let Instruction::NewContract(contract) = &mut tx.instruction {
let mut instruction = tx.instruction();
if let Instruction::NewContract(ref mut contract) = instruction {
if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan {
payment.to = thief_keypair.pubkey(); // <-- attack!
}
}
tx.userdata = serialize(&instruction).unwrap();
assert!(tx.verify_plan());
assert!(!tx.verify_signature());
}
@ -345,9 +378,13 @@ mod tests {
let tx = test_tx();
let sign_data = tx.get_sign_data();
let tx_bytes = serialize(&tx).unwrap();
assert_matches!(memfind(&tx_bytes, &sign_data), Some(SIGNED_DATA_OFFSET));
assert_matches!(memfind(&tx_bytes, &tx.signature.as_ref()), Some(SIG_OFFSET));
assert_matches!(memfind(&tx_bytes, &tx.from.as_ref()), Some(PUB_KEY_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.from().as_ref()),
Some(PUB_KEY_OFFSET)
);
assert!(tx.verify_signature());
}
#[test]
fn test_userdata_layout() {
@ -355,13 +392,16 @@ mod tests {
tx0.userdata = vec![1, 2, 3];
let sign_data0a = tx0.get_sign_data();
let tx_bytes = serialize(&tx0).unwrap();
assert!(tx_bytes.len() < 256);
assert!(tx_bytes.len() < PACKET_DATA_SIZE);
assert_eq!(memfind(&tx_bytes, &sign_data0a), Some(SIGNED_DATA_OFFSET));
assert_eq!(
memfind(&tx_bytes, &tx0.signature.as_ref()),
Some(SIG_OFFSET)
);
assert_eq!(memfind(&tx_bytes, &tx0.from.as_ref()), Some(PUB_KEY_OFFSET));
assert_eq!(
memfind(&tx_bytes, &tx0.from().as_ref()),
Some(PUB_KEY_OFFSET)
);
let tx1 = deserialize(&tx_bytes).unwrap();
assert_eq!(tx0, tx1);
assert_eq!(tx1.userdata, vec![1, 2, 3]);
@ -377,19 +417,23 @@ mod tests {
let keypair1 = Keypair::new();
let zero = Hash::default();
let mut tx = Transaction::new(&keypair0, keypair1.pubkey(), 1, zero);
if let Instruction::NewContract(contract) = &mut tx.instruction {
let mut instruction = tx.instruction();
if let Instruction::NewContract(ref mut contract) = instruction {
if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan {
payment.tokens = 2; // <-- attack!
}
}
tx.userdata = serialize(&instruction).unwrap();
assert!(!tx.verify_plan());
// Also, ensure all branchs of the plan spend all tokens
if let Instruction::NewContract(contract) = &mut tx.instruction {
let mut instruction = tx.instruction();
if let Instruction::NewContract(ref mut contract) = instruction {
if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan {
payment.tokens = 0; // <-- whoops!
}
}
tx.userdata = serialize(&instruction).unwrap();
assert!(!tx.verify_plan());
}
}

View File

@ -522,7 +522,7 @@ fn test_multi_node_dynamic_network() {
Ok(val) => val
.parse()
.expect(&format!("env var {} is not parse-able as usize", key)),
Err(_) => 160,
Err(_) => 120,
};
let leader_keypair = Keypair::new();