Remove Signature from ApplySignature

This commit is contained in:
Michael Vines 2018-09-18 18:45:44 -07:00
parent 51e5de4d97
commit 6dee632d67
4 changed files with 54 additions and 122 deletions

View File

@ -4,9 +4,7 @@ use bincode::{self, deserialize, serialize_into, serialized_size};
use chrono::prelude::{DateTime, Utc}; use chrono::prelude::{DateTime, Utc};
use instruction::{Instruction, Plan}; use instruction::{Instruction, Plan};
use payment_plan::{PaymentPlan, Witness}; use payment_plan::{PaymentPlan, Witness};
use signature::{Pubkey, Signature}; use signature::Pubkey;
use std::collections::hash_map::Entry::Occupied;
use std::collections::HashMap;
use std::io; use std::io;
use transaction::Transaction; use transaction::Transaction;
@ -19,14 +17,13 @@ pub enum BudgetError {
UninitializedContract(Pubkey), UninitializedContract(Pubkey),
NegativeTokens, NegativeTokens,
DestinationMissing(Pubkey), DestinationMissing(Pubkey),
FailedWitness(Signature), FailedWitness,
SignatureUnoccupied(Signature),
} }
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
pub struct BudgetContract { pub struct BudgetContract {
pub initialized: bool, pub initialized: bool,
pub pending: HashMap<Signature, Plan>, pub pending_plan: Option<Plan>,
pub last_error: Option<BudgetError>, pub last_error: Option<BudgetError>,
} }
@ -35,7 +32,7 @@ pub const BUDGET_CONTRACT_ID: [u8; 32] = [
]; ];
impl BudgetContract { impl BudgetContract {
fn is_pending(&self) -> bool { fn is_pending(&self) -> bool {
!self.pending.is_empty() self.pending_plan != None
} }
pub fn id() -> Pubkey { pub fn id() -> Pubkey {
Pubkey::new(&BUDGET_CONTRACT_ID) Pubkey::new(&BUDGET_CONTRACT_ID)
@ -48,30 +45,23 @@ impl BudgetContract {
/// will progress one step. /// will progress one step.
fn apply_signature( fn apply_signature(
&mut self, &mut self,
tx: &Transaction, keys: &[Pubkey],
signature: Signature,
account: &mut [Account], account: &mut [Account],
) -> Result<(), BudgetError> { ) -> Result<(), BudgetError> {
if let Occupied(mut e) = self.pending.entry(signature) { let mut final_payment = None;
e.get_mut().apply_witness(&Witness::Signature, &tx.keys[0]); if let Some(ref mut plan) = self.pending_plan {
if let Some(payment) = e.get().final_payment() { plan.apply_witness(&Witness::Signature, &keys[0]);
if tx.keys.len() > 1 && payment.to == tx.keys[2] { final_payment = plan.final_payment();
trace!("apply_witness refund"); }
//move the tokens back to the from account
account[1].tokens -= payment.tokens; if let Some(payment) = final_payment {
account[2].tokens += payment.tokens; if keys.len() < 2 || payment.to != keys[2] {
e.remove_entry(); trace!("destination missing");
} else { return Err(BudgetError::DestinationMissing(payment.to));
trace!("destination is missing");
return Err(BudgetError::DestinationMissing(payment.to));
}
} else {
trace!("failed apply_witness");
return Err(BudgetError::FailedWitness(signature));
} }
} else { self.pending_plan = None;
trace!("apply_witness signature unoccupied"); account[1].tokens -= payment.tokens;
return Err(BudgetError::SignatureUnoccupied(signature)); account[2].tokens += payment.tokens;
} }
Ok(()) Ok(())
} }
@ -80,29 +70,26 @@ impl BudgetContract {
/// will progress one step. /// will progress one step.
fn apply_timestamp( fn apply_timestamp(
&mut self, &mut self,
tx: &Transaction, keys: &[Pubkey],
accounts: &mut [Account], accounts: &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 completed = vec![]; let mut final_payment = None;
// Hold 'pending' write lock until the end of this function. Otherwise another thread can if let Some(ref mut plan) = self.pending_plan {
// double-spend if it enters before the modified plan is removed from 'pending'. plan.apply_witness(&Witness::Timestamp(dt), &keys[0]);
for (key, plan) in &mut self.pending { final_payment = plan.final_payment();
plan.apply_witness(&Witness::Timestamp(dt), &tx.keys[0]);
if let Some(payment) = plan.final_payment() {
if tx.keys.len() < 2 || payment.to != tx.keys[2] {
trace!("destination missing");
return Err(BudgetError::DestinationMissing(payment.to));
}
completed.push(key.clone());
accounts[2].tokens += payment.tokens;
accounts[1].tokens -= payment.tokens;
}
} }
for key in completed {
self.pending.remove(&key); if let Some(payment) = final_payment {
if keys.len() < 2 || payment.to != keys[2] {
trace!("destination missing");
return Err(BudgetError::DestinationMissing(payment.to));
}
self.pending_plan = None;
accounts[1].tokens -= payment.tokens;
accounts[2].tokens += payment.tokens;
} }
Ok(()) Ok(())
} }
@ -157,7 +144,7 @@ impl BudgetContract {
Err(BudgetError::ContractAlreadyExists(tx.keys[1])) Err(BudgetError::ContractAlreadyExists(tx.keys[1]))
} else { } else {
let mut state = BudgetContract::default(); let mut state = BudgetContract::default();
state.pending.insert(tx.signature, plan); state.pending_plan = Some(plan);
accounts[1].tokens += contract.tokens; accounts[1].tokens += contract.tokens;
state.initialized = true; state.initialized = true;
state.serialize(&mut accounts[1].userdata); state.serialize(&mut accounts[1].userdata);
@ -168,29 +155,28 @@ impl BudgetContract {
Instruction::ApplyTimestamp(dt) => { Instruction::ApplyTimestamp(dt) => {
let mut state = Self::deserialize(&accounts[1].userdata).unwrap(); let mut state = Self::deserialize(&accounts[1].userdata).unwrap();
if !state.is_pending() { if !state.is_pending() {
return Err(BudgetError::ContractNotPending(tx.keys[1])); Err(BudgetError::ContractNotPending(tx.keys[1]))
} } else if !state.initialized {
if !state.initialized {
trace!("contract is uninitialized"); trace!("contract is uninitialized");
Err(BudgetError::UninitializedContract(tx.keys[1])) Err(BudgetError::UninitializedContract(tx.keys[1]))
} else { } else {
state.apply_timestamp(tx, accounts, *dt)?; trace!("apply timestamp");
state.apply_timestamp(&tx.keys, accounts, *dt)?;
trace!("apply timestamp committed"); trace!("apply timestamp committed");
state.serialize(&mut accounts[1].userdata); state.serialize(&mut accounts[1].userdata);
Ok(()) Ok(())
} }
} }
Instruction::ApplySignature(signature) => { Instruction::ApplySignature => {
let mut state = Self::deserialize(&accounts[1].userdata).unwrap(); let mut state = Self::deserialize(&accounts[1].userdata).unwrap();
if !state.is_pending() { if !state.is_pending() {
return Err(BudgetError::ContractNotPending(tx.keys[1])); Err(BudgetError::ContractNotPending(tx.keys[1]))
} } else if !state.initialized {
if !state.initialized {
trace!("contract is uninitialized"); trace!("contract is uninitialized");
Err(BudgetError::UninitializedContract(tx.keys[1])) Err(BudgetError::UninitializedContract(tx.keys[1]))
} else { } else {
trace!("apply signature"); trace!("apply signature");
state.apply_signature(tx, *signature, accounts)?; state.apply_signature(&tx.keys, accounts)?;
trace!("apply signature committed"); trace!("apply signature committed");
state.serialize(&mut accounts[1].userdata); state.serialize(&mut accounts[1].userdata);
Ok(()) Ok(())
@ -220,7 +206,7 @@ impl BudgetContract {
return Err(Box::new(bincode::ErrorKind::SizeLimit)); return Err(Box::new(bincode::ErrorKind::SizeLimit));
} }
let len: u64 = deserialize(&input[..8]).unwrap(); let len: u64 = deserialize(&input[..8]).unwrap();
if len < 8 { if len < 3 {
return Err(Box::new(bincode::ErrorKind::SizeLimit)); return Err(Box::new(bincode::ErrorKind::SizeLimit));
} }
if input.len() < 8 + len as usize { if input.len() < 8 + len as usize {
@ -256,9 +242,9 @@ impl BudgetContract {
//TODO the contract needs to provide a "get_balance" introspection call of the userdata //TODO the contract needs to provide a "get_balance" introspection call of the userdata
pub fn get_balance(account: &Account) -> i64 { pub fn get_balance(account: &Account) -> i64 {
if let Ok(pending) = deserialize(&account.userdata) { if let Ok(state) = deserialize(&account.userdata) {
let pending: BudgetContract = pending; let state: BudgetContract = state;
if pending.is_pending() { if state.is_pending() {
0 0
} else { } else {
account.tokens account.tokens
@ -275,7 +261,7 @@ mod test {
use budget_contract::{BudgetContract, BudgetError}; use budget_contract::{BudgetContract, BudgetError};
use chrono::prelude::{DateTime, NaiveDate, Utc}; use chrono::prelude::{DateTime, NaiveDate, Utc};
use hash::Hash; use hash::Hash;
use signature::{GenKeys, Keypair, KeypairUtil, Pubkey, Signature}; use signature::{GenKeys, Keypair, KeypairUtil, Pubkey};
use transaction::Transaction; use transaction::Transaction;
#[test] #[test]
fn test_serializer() { fn test_serializer() {
@ -388,7 +374,6 @@ mod test {
1, 1,
Hash::default(), Hash::default(),
); );
let sig = tx.signature;
BudgetContract::process_transaction(&tx, &mut accounts); BudgetContract::process_transaction(&tx, &mut accounts);
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);
@ -397,13 +382,8 @@ mod test {
assert!(state.is_pending()); assert!(state.is_pending());
// Attack! try to put the tokens into the wrong account with cancel // Attack! try to put the tokens into the wrong account with cancel
let tx = Transaction::budget_new_signature( let tx =
&to, Transaction::budget_new_signature(&to, contract.pubkey(), to.pubkey(), Hash::default());
contract.pubkey(),
to.pubkey(),
sig,
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
BudgetContract::process_transaction(&tx, &mut accounts); BudgetContract::process_transaction(&tx, &mut accounts);
@ -412,34 +392,12 @@ mod test {
assert_eq!(accounts[contract_account].tokens, 1); assert_eq!(accounts[contract_account].tokens, 1);
// this would be the `to.pubkey()` account // this would be the `to.pubkey()` account
assert_eq!(accounts[pay_account].tokens, 0); assert_eq!(accounts[pay_account].tokens, 0);
let state = BudgetContract::deserialize(&accounts[contract_account].userdata).unwrap();
assert_eq!(state.last_error, Some(BudgetError::FailedWitness(sig)));
// Attack! try canceling with a bad signature
let badsig = tx.signature;
let tx = Transaction::budget_new_signature(
&from,
contract.pubkey(),
from.pubkey(),
badsig,
Hash::default(),
);
BudgetContract::process_transaction(&tx, &mut accounts);
assert_eq!(accounts[from_account].tokens, 0);
assert_eq!(accounts[contract_account].tokens, 1);
assert_eq!(accounts[pay_account].tokens, 0);
let state = BudgetContract::deserialize(&accounts[contract_account].userdata).unwrap();
assert_eq!(
state.last_error,
Some(BudgetError::SignatureUnoccupied(badsig))
);
// Now, cancel the transaction. from gets her funds back // Now, cancel the transaction. from gets her funds back
let tx = Transaction::budget_new_signature( let tx = Transaction::budget_new_signature(
&from, &from,
contract.pubkey(), contract.pubkey(),
from.pubkey(), from.pubkey(),
sig,
Hash::default(), Hash::default(),
); );
BudgetContract::process_transaction(&tx, &mut accounts); BudgetContract::process_transaction(&tx, &mut accounts);
@ -452,7 +410,6 @@ mod test {
&from, &from,
contract.pubkey(), contract.pubkey(),
from.pubkey(), from.pubkey(),
sig,
Hash::default(), Hash::default(),
); );
BudgetContract::process_transaction(&tx, &mut accounts); BudgetContract::process_transaction(&tx, &mut accounts);
@ -480,11 +437,6 @@ mod test {
2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, 2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4,
2, 2, 2, 2, 2, 2,
]); ]);
let signature = Signature::new(&[
1, 2, 3, 4, 5, 6, 7, 8, 9, 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, 9, 8, 7,
6, 5, 4, 3, 2, 1,
]);
let date = let date =
DateTime::<Utc>::from_utc(NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11), Utc); DateTime::<Utc>::from_utc(NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11), Utc);
@ -535,20 +487,7 @@ mod test {
); );
// ApplySignature // ApplySignature
let tx = Transaction::budget_new_signature( let tx = Transaction::budget_new_signature(&keypair, keypair.pubkey(), to, Hash::default());
&keypair, assert_eq!(tx.userdata, vec![2, 0, 0, 0]);
keypair.pubkey(),
to,
signature,
Hash::default(),
);
assert_eq!(
tx.userdata,
vec![
2, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 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, 9, 8, 7, 6, 5, 4, 3, 2, 1
]
);
} }
} }

View File

@ -256,13 +256,8 @@ mod tests {
Utc::now(), Utc::now(),
zero, zero,
); );
let tx1 = Transaction::budget_new_signature( let tx1 =
&keypair, Transaction::budget_new_signature(&keypair, keypair.pubkey(), keypair.pubkey(), zero);
keypair.pubkey(),
keypair.pubkey(),
Default::default(),
zero,
);
let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()], false); let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()], false);
assert!(e0.verify(&zero)); assert!(e0.verify(&zero));

View File

@ -2,7 +2,6 @@ use budget::Budget;
use chrono::prelude::{DateTime, Utc}; use chrono::prelude::{DateTime, Utc};
use payment_plan::{Payment, PaymentPlan, Witness}; use payment_plan::{Payment, PaymentPlan, Witness};
use signature::Pubkey; use signature::Pubkey;
use signature::Signature;
/// The type of payment plan. Each item must implement the PaymentPlan trait. /// The type of payment plan. Each item must implement the PaymentPlan trait.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
@ -61,7 +60,7 @@ pub enum Instruction {
/// Tell the payment plan that the `NewContract` with `Signature` has been /// Tell the payment plan that the `NewContract` with `Signature` has been
/// signed by the containing transaction's `Pubkey`. /// signed by the containing transaction's `Pubkey`.
ApplySignature(Signature), ApplySignature,
/// Vote for a PoH that is equal to the lastid of this transaction /// Vote for a PoH that is equal to the lastid of this transaction
NewVote(Vote), NewVote(Vote),

View File

@ -127,10 +127,9 @@ impl Transaction {
from_keypair: &Keypair, from_keypair: &Keypair,
contract: Pubkey, contract: Pubkey,
to: Pubkey, to: Pubkey,
signature: Signature,
last_id: Hash, last_id: Hash,
) -> Self { ) -> Self {
let instruction = Instruction::ApplySignature(signature); let instruction = Instruction::ApplySignature;
let userdata = serialize(&instruction).unwrap(); let userdata = serialize(&instruction).unwrap();
Self::new_with_userdata( Self::new_with_userdata(
from_keypair, from_keypair,