Add Instruction type

This commit is contained in:
Greg Fitzgerald
2018-05-22 21:42:04 -06:00
parent 5a45eef1dc
commit cc907ba69d
6 changed files with 97 additions and 47 deletions

View File

@ -18,7 +18,7 @@ use std::collections::{HashMap, HashSet, VecDeque};
use std::result; use std::result;
use std::sync::RwLock; use std::sync::RwLock;
use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering};
use transaction::Transaction; use transaction::{Instruction, Transaction};
pub const MAX_ENTRY_IDS: usize = 1024 * 4; pub const MAX_ENTRY_IDS: usize = 1024 * 4;
@ -160,7 +160,9 @@ impl Bank {
/// Deduct tokens from the 'from' address the account has sufficient /// Deduct tokens from the 'from' address the account has sufficient
/// funds and isn't a duplicate. /// funds and isn't a duplicate.
pub fn process_verified_transaction_debits(&self, tr: &Transaction) -> Result<()> { pub fn process_verified_transaction_debits(&self, tr: &Transaction) -> Result<()> {
info!("Transaction {}", tr.contract.tokens); if let Instruction::NewContract(contract) = &tr.instruction {
info!("Transaction {}", contract.tokens);
}
let bals = self.balances let bals = self.balances
.read() .read()
.expect("'balances' read lock in process_verified_transaction_debits"); .expect("'balances' read lock in process_verified_transaction_debits");
@ -175,20 +177,24 @@ impl Bank {
} }
loop { loop {
let bal = option.expect("assignment of option to bal"); let result = if let Instruction::NewContract(contract) = &tr.instruction {
let current = bal.load(Ordering::Relaxed) as i64; let bal = option.expect("assignment of option to bal");
let current = bal.load(Ordering::Relaxed) as i64;
if current < tr.contract.tokens { if current < contract.tokens {
self.forget_signature_with_last_id(&tr.sig, &tr.last_id); self.forget_signature_with_last_id(&tr.sig, &tr.last_id);
return Err(BankError::InsufficientFunds(tr.from)); return Err(BankError::InsufficientFunds(tr.from));
} }
let result = bal.compare_exchange( bal.compare_exchange(
current as isize, current as isize,
(current - tr.contract.tokens) as isize, (current - contract.tokens) as isize,
Ordering::Relaxed, Ordering::Relaxed,
Ordering::Relaxed, Ordering::Relaxed,
); )
} else {
Ok(0)
};
match result { match result {
Ok(_) => { Ok(_) => {
@ -201,18 +207,28 @@ impl Bank {
} }
pub fn process_verified_transaction_credits(&self, tr: &Transaction) { pub fn process_verified_transaction_credits(&self, tr: &Transaction) {
let mut plan = tr.contract.plan.clone(); match &tr.instruction {
plan.apply_witness(&Witness::Timestamp(*self.last_time Instruction::NewContract(contract) => {
.read() let mut plan = contract.plan.clone();
.expect("timestamp creation in process_verified_transaction_credits"))); plan.apply_witness(&Witness::Timestamp(*self.last_time
.read()
.expect("timestamp creation in process_verified_transaction_credits")));
if let Some(ref payment) = plan.final_payment() { if let Some(ref payment) = plan.final_payment() {
apply_payment(&self.balances, payment); apply_payment(&self.balances, payment);
} else { } else {
let mut pending = self.pending let mut pending = self.pending
.write() .write()
.expect("'pending' write lock in process_verified_transaction_credits"); .expect("'pending' write lock in process_verified_transaction_credits");
pending.insert(tr.sig, plan); pending.insert(tr.sig, plan);
}
}
Instruction::ApplyTimestamp(dt) => {
let _ = self.process_verified_timestamp(tr.from, *dt);
}
Instruction::ApplySignature(tx_sig) => {
let _ = self.process_verified_sig(tr.from, *tx_sig);
}
} }
} }

View File

@ -14,6 +14,7 @@ use solana::entry::Entry;
use solana::event::Event; use solana::event::Event;
use solana::server::Server; use solana::server::Server;
use solana::signature::{KeyPair, KeyPairUtil}; use solana::signature::{KeyPair, KeyPairUtil};
use solana::transaction::Instruction;
use std::env; use std::env;
use std::fs::File; use std::fs::File;
use std::io::{stdin, stdout, Read}; use std::io::{stdin, stdout, Read};
@ -97,7 +98,11 @@ fn main() {
// transfer to oneself. // transfer to oneself.
let entry1: Entry = entries.next().unwrap(); let entry1: Entry = entries.next().unwrap();
let deposit = if let Event::Transaction(ref tr) = entry1.events[0] { let deposit = if let Event::Transaction(ref tr) = entry1.events[0] {
tr.contract.plan.final_payment() if let Instruction::NewContract(contract) = &tr.instruction {
contract.plan.final_payment()
} else {
None
}
} else { } else {
None None
}; };

View File

@ -71,13 +71,16 @@ mod tests {
use super::*; use super::*;
use ledger::Block; use ledger::Block;
use plan::Plan; use plan::Plan;
use transaction::Instruction;
#[test] #[test]
fn test_create_events() { fn test_create_events() {
let mut events = Mint::new(100).create_events().into_iter(); let mut events = Mint::new(100).create_events().into_iter();
if let Event::Transaction(tr) = events.next().unwrap() { if let Event::Transaction(tr) = events.next().unwrap() {
if let Plan::Pay(payment) = tr.contract.plan { if let Instruction::NewContract(contract) = tr.instruction {
assert_eq!(tr.from, payment.to); if let Plan::Pay(payment) = contract.plan {
assert_eq!(tr.from, payment.to);
}
} }
} }
assert_eq!(events.next(), None); assert_eq!(events.next(), None);

View File

@ -7,6 +7,7 @@ use chrono::prelude::*;
use signature::PublicKey; use signature::PublicKey;
use std::mem; use std::mem;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Witness { pub enum Witness {
Timestamp(DateTime<Utc>), Timestamp(DateTime<Utc>),
Signature(PublicKey), Signature(PublicKey),

View File

@ -192,6 +192,7 @@ mod tests {
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use streamer::default_window; use streamer::default_window;
use transaction::Instruction;
use tvu::tests::TestNode; use tvu::tests::TestNode;
#[test] #[test]
@ -284,8 +285,10 @@ mod tests {
let last_id = client.get_last_id().wait().unwrap(); let last_id = client.get_last_id().wait().unwrap();
let mut tr2 = Transaction::new(&alice.keypair(), bob_pubkey, 501, last_id); let mut tr2 = Transaction::new(&alice.keypair(), bob_pubkey, 501, last_id);
tr2.contract.tokens = 502; if let Instruction::NewContract(contract) = &mut tr2.instruction {
tr2.contract.plan = Plan::new_payment(502, bob_pubkey); contract.tokens = 502;
contract.plan = Plan::new_payment(502, bob_pubkey);
}
let _sig = client.transfer_signed(tr2).unwrap(); let _sig = client.transfer_signed(tr2).unwrap();
let balance = poll_get_balance(&mut client, &bob_pubkey); let balance = poll_get_balance(&mut client, &bob_pubkey);

View File

@ -17,11 +17,18 @@ pub struct Contract {
pub plan: Plan, pub plan: Plan,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Instruction {
NewContract(Contract),
ApplyTimestamp(DateTime<Utc>),
ApplySignature(Signature),
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Transaction { pub struct Transaction {
pub sig: Signature, pub sig: Signature,
pub from: PublicKey, pub from: PublicKey,
pub contract: Contract, pub instruction: Instruction,
pub last_id: Hash, pub last_id: Hash,
} }
@ -30,9 +37,10 @@ impl Transaction {
pub fn new(from_keypair: &KeyPair, to: PublicKey, tokens: i64, last_id: Hash) -> Self { pub fn new(from_keypair: &KeyPair, to: PublicKey, tokens: i64, last_id: Hash) -> Self {
let from = from_keypair.pubkey(); let from = from_keypair.pubkey();
let plan = Plan::Pay(Payment { tokens, to }); let plan = Plan::Pay(Payment { tokens, to });
let instruction = Instruction::NewContract(Contract { plan, tokens });
let mut tr = Transaction { let mut tr = Transaction {
sig: Signature::default(), sig: Signature::default(),
contract: Contract { plan, tokens }, instruction,
last_id, last_id,
from, from,
}; };
@ -53,8 +61,9 @@ impl Transaction {
(Condition::Timestamp(dt), Payment { tokens, to }), (Condition::Timestamp(dt), Payment { tokens, to }),
(Condition::Signature(from), Payment { tokens, to: from }), (Condition::Signature(from), Payment { tokens, to: from }),
); );
let instruction = Instruction::NewContract(Contract { plan, tokens });
let mut tr = Transaction { let mut tr = Transaction {
contract: Contract { plan, tokens }, instruction,
from, from,
last_id, last_id,
sig: Signature::default(), sig: Signature::default(),
@ -64,7 +73,7 @@ impl Transaction {
} }
fn get_sign_data(&self) -> Vec<u8> { fn get_sign_data(&self) -> Vec<u8> {
let mut data = serialize(&(&self.contract)).expect("serialize Contract"); let mut data = serialize(&(&self.instruction)).expect("serialize Contract");
let last_id_data = serialize(&(&self.last_id)).expect("serialize last_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);
data data
@ -81,7 +90,11 @@ impl Transaction {
} }
pub fn verify_plan(&self) -> bool { pub fn verify_plan(&self) -> bool {
self.contract.plan.verify(self.contract.tokens) if let Instruction::NewContract(contract) = &self.instruction {
contract.plan.verify(contract.tokens)
} else {
true
}
} }
} }
@ -149,8 +162,9 @@ mod tests {
tokens: 0, tokens: 0,
to: Default::default(), to: Default::default(),
}); });
let instruction = Instruction::NewContract(Contract { plan, tokens: 0 });
let claim0 = Transaction { let claim0 = Transaction {
contract: Contract { plan, tokens: 0 }, instruction,
from: Default::default(), from: Default::default(),
last_id: Default::default(), last_id: Default::default(),
sig: Default::default(), sig: Default::default(),
@ -166,10 +180,12 @@ mod tests {
let keypair = KeyPair::new(); let keypair = KeyPair::new();
let pubkey = keypair.pubkey(); let pubkey = keypair.pubkey();
let mut tr = Transaction::new(&keypair, pubkey, 42, zero); let mut tr = Transaction::new(&keypair, pubkey, 42, zero);
tr.contract.tokens = 1_000_000; // <-- attack, part 1! if let Instruction::NewContract(contract) = &mut tr.instruction {
if let Plan::Pay(ref mut payment) = tr.contract.plan { contract.tokens = 1_000_000; // <-- attack, part 1!
payment.tokens = tr.contract.tokens; // <-- attack, part 2! if let Plan::Pay(ref mut payment) = contract.plan {
}; payment.tokens = contract.tokens; // <-- attack, part 2!
}
}
assert!(tr.verify_plan()); assert!(tr.verify_plan());
assert!(!tr.verify_sig()); assert!(!tr.verify_sig());
} }
@ -182,9 +198,11 @@ mod tests {
let pubkey1 = keypair1.pubkey(); let pubkey1 = keypair1.pubkey();
let zero = Hash::default(); let zero = Hash::default();
let mut tr = Transaction::new(&keypair0, pubkey1, 42, zero); let mut tr = Transaction::new(&keypair0, pubkey1, 42, zero);
if let Plan::Pay(ref mut payment) = tr.contract.plan { if let Instruction::NewContract(contract) = &mut tr.instruction {
payment.to = thief_keypair.pubkey(); // <-- attack! if let Plan::Pay(ref mut payment) = contract.plan {
}; payment.to = thief_keypair.pubkey(); // <-- attack!
}
}
assert!(tr.verify_plan()); assert!(tr.verify_plan());
assert!(!tr.verify_sig()); assert!(!tr.verify_sig());
} }
@ -204,14 +222,18 @@ mod tests {
let keypair1 = KeyPair::new(); let keypair1 = KeyPair::new();
let zero = Hash::default(); let zero = Hash::default();
let mut tr = Transaction::new(&keypair0, keypair1.pubkey(), 1, zero); let mut tr = Transaction::new(&keypair0, keypair1.pubkey(), 1, zero);
if let Plan::Pay(ref mut payment) = tr.contract.plan { if let Instruction::NewContract(contract) = &mut tr.instruction {
payment.tokens = 2; // <-- attack! if let Plan::Pay(ref mut payment) = contract.plan {
payment.tokens = 2; // <-- attack!
}
} }
assert!(!tr.verify_plan()); assert!(!tr.verify_plan());
// Also, ensure all branchs of the plan spend all tokens // Also, ensure all branchs of the plan spend all tokens
if let Plan::Pay(ref mut payment) = tr.contract.plan { if let Instruction::NewContract(contract) = &mut tr.instruction {
payment.tokens = 0; // <-- whoops! if let Plan::Pay(ref mut payment) = contract.plan {
payment.tokens = 0; // <-- whoops!
}
} }
assert!(!tr.verify_plan()); assert!(!tr.verify_plan());
} }