Revert "Accounts with state (#954)"

This reverts commit c23fa289c3.
This commit is contained in:
anatoly yakovenko
2018-08-15 19:17:16 -07:00
committed by Michael Vines
parent c23fa289c3
commit 88d6fea999
11 changed files with 193 additions and 324 deletions

View File

@@ -1,4 +1,4 @@
//! The `bank` module tracks client accounts and the progress of smart
//! The `bank` module tracks client balances and the progress of smart
//! contracts. It offers a high-level API that signs transactions
//! on behalf of the caller, and a low-level API for when they have
//! already been signed and verified.
@@ -65,20 +65,11 @@ pub enum BankError {
}
pub type Result<T> = result::Result<T, BankError>;
/// An Account with userdata that is stored on chain
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Account {
/// tokens in the account
pub tokens: i64,
/// user data
/// A transaction can write to its userdata
pub userdata: Vec<u8>,
}
/// 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>>,
balances: RwLock<HashMap<Pubkey, i64>>,
/// 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.
@@ -109,7 +100,7 @@ pub struct Bank {
impl Default for Bank {
fn default() -> Self {
Bank {
accounts: RwLock::new(HashMap::new()),
balances: RwLock::new(HashMap::new()),
pending: RwLock::new(HashMap::new()),
last_ids: RwLock::new(VecDeque::new()),
last_ids_sigs: RwLock::new(HashMap::new()),
@@ -130,7 +121,7 @@ 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());
bank.apply_payment(deposit, &mut bank.balances.write().unwrap());
bank
}
@@ -146,11 +137,8 @@ impl 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;
fn apply_payment(&self, payment: &Payment, balances: &mut HashMap<Pubkey, i64>) {
*balances.entry(payment.to).or_insert(0) += payment.tokens;
}
/// Return the last entry ID registered.
@@ -247,14 +235,10 @@ 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>,
) -> Result<()> {
fn apply_debits(&self, tx: &Transaction, bals: &mut HashMap<Pubkey, i64>) -> Result<()> {
let mut purge = false;
{
let option = accounts.get_mut(&tx.from);
let option = bals.get_mut(&tx.from);
if option.is_none() {
// TODO: this is gnarly because the counters are static atomics
if !self.is_leader {
@@ -275,19 +259,19 @@ impl Bank {
return Err(BankError::NegativeTokens);
}
if bal.tokens < contract.tokens {
if *bal < 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 {
} else if *bal == contract.tokens {
purge = true;
} else {
bal.tokens -= contract.tokens;
*bal -= contract.tokens;
}
};
}
if purge {
accounts.remove(&tx.from);
bals.remove(&tx.from);
}
Ok(())
@@ -295,12 +279,12 @@ impl Bank {
/// 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>) {
fn apply_credits(&self, tx: &Transaction, balances: &mut HashMap<Pubkey, i64>) {
match &tx.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, balances);
} else {
let mut pending = self
.pending
@@ -321,26 +305,13 @@ impl Bank {
}
}
}
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);
}
}
}
/// 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();
self.apply_debits(tx, accounts)?;
self.apply_credits(tx, accounts);
self.save_data(tx, accounts);
let bals = &mut self.balances.write().unwrap();
self.apply_debits(tx, bals)?;
self.apply_credits(tx, bals);
self.transaction_count.fetch_add(1, Ordering::Relaxed);
Ok(())
}
@@ -348,13 +319,13 @@ impl Bank {
/// 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();
let bals = &mut self.balances.write().unwrap();
debug!("processing Transactions {}", txs.len());
let txs_len = txs.len();
let now = Instant::now();
let results: Vec<_> = txs
.into_iter()
.map(|tx| self.apply_debits(&tx, accounts).map(|_| tx))
.map(|tx| self.apply_debits(&tx, bals).map(|_| tx))
.collect(); // Calling collect() here forces all debits to complete before moving on.
let debits = now.elapsed();
@@ -364,7 +335,7 @@ impl Bank {
.into_iter()
.map(|result| {
result.map(|tx| {
self.apply_credits(&tx, accounts);
self.apply_credits(&tx, bals);
tx
})
})
@@ -496,7 +467,7 @@ impl Bank {
None
}.expect("invalid ledger, needs to start with a contract");
self.apply_payment(&deposit, &mut self.accounts.write().unwrap());
self.apply_payment(&deposit, &mut self.balances.write().unwrap());
}
self.register_entry_id(&entry0.id);
self.register_entry_id(&entry1.id);
@@ -527,7 +498,7 @@ impl Bank {
{
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());
self.apply_payment(&payment, &mut self.balances.write().unwrap());
e.remove_entry();
}
};
@@ -550,7 +521,7 @@ impl Bank {
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());
self.apply_payment(&payment, &mut self.balances.write().unwrap());
completed.push(key.clone());
}
}
@@ -593,15 +564,11 @@ impl Bank {
}
pub fn get_balance(&self, pubkey: &Pubkey) -> i64 {
self.get_account(pubkey).map(|a| a.tokens).unwrap_or(0)
}
pub fn get_account(&self, pubkey: &Pubkey) -> Option<Account> {
let accounts = self
.accounts
let bals = self
.balances
.read()
.expect("'accounts' read lock in get_balance");
accounts.get(pubkey).cloned()
.expect("'balances' read lock in get_balance");
bals.get(pubkey).cloned().unwrap_or(0)
}
pub fn transaction_count(&self) -> usize {
@@ -715,21 +682,6 @@ 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);

View File

@@ -91,7 +91,7 @@ fn main() -> () {
};
let mut client = mk_client(&repl_clone);
let previous_balance = client.poll_get_balance(&leader_pubkey).unwrap_or(0);
let previous_balance = client.poll_get_balance(&leader_pubkey).unwrap();
eprintln!("balance is {}", previous_balance);
if previous_balance == 0 {

View File

@@ -254,7 +254,7 @@ fn process_command(
"Requesting airdrop of {:?} tokens from {}",
tokens, config.drone_addr
);
let previous_balance = client.poll_get_balance(&config.id.pubkey()).unwrap_or(0);
let previous_balance = client.poll_get_balance(&config.id.pubkey())?;
request_airdrop(&config.drone_addr, &config.id.pubkey(), tokens as u64)?;
// TODO: return airdrop Result from Drone instead of polling the
@@ -262,10 +262,7 @@ fn process_command(
let mut current_balance = previous_balance;
for _ in 0..20 {
sleep(Duration::from_millis(500));
current_balance = client
.poll_get_balance(&config.id.pubkey())
.unwrap_or(previous_balance);
current_balance = client.poll_get_balance(&config.id.pubkey())?;
if previous_balance != current_balance {
break;
}

View File

@@ -691,7 +691,7 @@ mod tests {
transactions.extend(large_transactions);
let entries0 = next_entries(&id, 0, transactions.clone());
assert!(entries0.len() >= 2);
assert!(entries0.len() > 2);
assert!(entries0[0].has_more);
assert!(!entries0[entries0.len() - 1].has_more);
assert!(entries0.verify(&id));

View File

@@ -1,13 +1,12 @@
//! The `request` module defines the messages for the thin client.
use bank::Account;
use hash::Hash;
use signature::{Pubkey, Signature};
#[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))]
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum Request {
GetAccount { key: Pubkey },
GetBalance { key: Pubkey },
GetLastId,
GetTransactionCount,
GetSignature { signature: Signature },
@@ -23,20 +22,9 @@ impl Request {
#[derive(Serialize, Deserialize, Debug)]
pub enum Response {
Account {
key: Pubkey,
account: Option<Account>,
},
LastId {
id: Hash,
},
TransactionCount {
transaction_count: u64,
},
SignatureStatus {
signature_status: bool,
},
Finality {
time: usize,
},
Balance { key: Pubkey, val: i64 },
LastId { id: Hash },
TransactionCount { transaction_count: u64 },
SignatureStatus { signature_status: bool },
Finality { time: usize },
}

View File

@@ -22,10 +22,10 @@ impl RequestProcessor {
rsp_addr: SocketAddr,
) -> Option<(Response, SocketAddr)> {
match msg {
Request::GetAccount { key } => {
let account = self.bank.get_account(&key);
let rsp = (Response::Account { key, account }, rsp_addr);
info!("Response::Account {:?}", rsp);
Request::GetBalance { key } => {
let val = self.bank.get_balance(&key);
let rsp = (Response::Balance { key, val }, rsp_addr);
info!("Response::Balance {:?}", rsp);
Some(rsp)
}
Request::GetLastId => {

View File

@@ -3,7 +3,6 @@
//! messages to the network directly. The binary encoding of its messages are
//! unstable and may change in future releases.
use bank::Account;
use bincode::{deserialize, serialize};
use hash::Hash;
use request::{Request, Response};
@@ -28,7 +27,7 @@ pub struct ThinClient {
transactions_socket: UdpSocket,
last_id: Option<Hash>,
transaction_count: u64,
balances: HashMap<Pubkey, Account>,
balances: HashMap<Pubkey, i64>,
signature_status: bool,
finality: Option<usize>,
}
@@ -66,15 +65,9 @@ impl ThinClient {
pub fn process_response(&mut self, resp: &Response) {
match *resp {
Response::Account {
key,
account: Some(ref account),
} => {
trace!("Response account {:?} {:?}", key, account);
self.balances.insert(key, account.clone());
}
Response::Account { key, account: None } => {
debug!("Response account {}: None ", key);
Response::Balance { key, val } => {
trace!("Response balance {:?} {:?}", key, val);
self.balances.insert(key, val);
}
Response::LastId { id } => {
trace!("Response last_id {:?}", id);
@@ -136,8 +129,8 @@ impl ThinClient {
/// by the network, this method will hang indefinitely.
pub fn get_balance(&mut self, pubkey: &Pubkey) -> io::Result<i64> {
trace!("get_balance");
let req = Request::GetAccount { key: *pubkey };
let data = serialize(&req).expect("serialize GetAccount in pub fn get_balance");
let req = Request::GetBalance { key: *pubkey };
let data = serialize(&req).expect("serialize GetBalance in pub fn get_balance");
self.requests_socket
.send_to(&data, &self.requests_addr)
.expect("buffer error in pub fn get_balance");
@@ -145,14 +138,14 @@ impl ThinClient {
while !done {
let resp = self.recv_response()?;
trace!("recv_response {:?}", resp);
if let Response::Account { key, .. } = &resp {
if let Response::Balance { key, .. } = &resp {
done = key == pubkey;
}
self.process_response(&resp);
}
self.balances
.get(pubkey)
.map(|a| a.tokens)
.cloned()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "nokey"))
}

View File

@@ -92,20 +92,15 @@ pub struct Transaction {
/// 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.
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`.
fn new_from_instruction(
from_keypair: &Keypair,
instruction: Instruction,
last_id: Hash,
fee: i64,
userdata: Vec<u8>,
) -> Self {
let from = from_keypair.pubkey();
let mut tx = Transaction {
@@ -114,20 +109,10 @@ impl Transaction {
last_id,
from,
fee,
userdata,
};
tx.sign(from_keypair);
tx
}
/// Create a signed transaction from the given `Instruction`.
fn new_from_instruction(
from_keypair: &Keypair,
instruction: Instruction,
last_id: Hash,
fee: i64,
) -> Self {
Self::new_with_userdata_and_instruction(from_keypair, instruction, last_id, fee, vec![])
}
/// Create and sign a new Transaction. Used for unit-testing.
pub fn new_taxed(
@@ -195,9 +180,6 @@ impl Transaction {
let fee_data = serialize(&(&self.fee)).expect("serialize last_id");
data.extend_from_slice(&fee_data);
let userdata = serialize(&(&self.userdata)).expect("serialize userdata");
data.extend_from_slice(&userdata);
data
}
@@ -292,7 +274,6 @@ mod tests {
last_id: Default::default(),
signature: Default::default(),
fee: 0,
userdata: vec![],
};
let buf = serialize(&claim0).unwrap();
let claim1: Transaction = deserialize(&buf).unwrap();
@@ -340,27 +321,6 @@ mod tests {
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));
}
#[test]
fn test_userdata_layout() {
let mut tx0 = test_tx();
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_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));
let tx1 = deserialize(&tx_bytes).unwrap();
assert_eq!(tx0, tx1);
assert_eq!(tx1.userdata, vec![1, 2, 3]);
tx0.userdata = vec![1, 2, 4];
let sign_data0b = tx0.get_sign_data();
assert_ne!(sign_data0a, sign_data0b);
}
#[test]
fn test_overspend_attack() {