Remove Signature from ApplySignature
This commit is contained in:
parent
51e5de4d97
commit
6dee632d67
|
@ -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
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue