Move BudgetTransaction into its own module
This commit is contained in:
parent
c83dcea87d
commit
df3b78c18c
|
@ -6,6 +6,7 @@
|
||||||
use bincode::deserialize;
|
use bincode::deserialize;
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
use budget_program::BudgetState;
|
use budget_program::BudgetState;
|
||||||
|
use budget_transaction::BudgetTransaction;
|
||||||
use counter::Counter;
|
use counter::Counter;
|
||||||
use dynamic_program::{DynamicProgram, KeyedAccount};
|
use dynamic_program::{DynamicProgram, KeyedAccount};
|
||||||
use entry::Entry;
|
use entry::Entry;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use bank::Bank;
|
use bank::Bank;
|
||||||
use bincode::deserialize;
|
use bincode::deserialize;
|
||||||
|
use budget_transaction::BudgetTransaction;
|
||||||
use counter::Counter;
|
use counter::Counter;
|
||||||
use entry::Entry;
|
use entry::Entry;
|
||||||
use hash::Hasher;
|
use hash::Hasher;
|
||||||
|
|
|
@ -268,10 +268,12 @@ mod test {
|
||||||
use bank::Account;
|
use bank::Account;
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
use budget_program::{BudgetError, BudgetState};
|
use budget_program::{BudgetError, BudgetState};
|
||||||
|
use budget_transaction::BudgetTransaction;
|
||||||
use chrono::prelude::{DateTime, NaiveDate, Utc};
|
use chrono::prelude::{DateTime, NaiveDate, Utc};
|
||||||
use hash::Hash;
|
use hash::Hash;
|
||||||
use signature::{GenKeys, Keypair, KeypairUtil, Pubkey};
|
use signature::{GenKeys, Keypair, KeypairUtil, Pubkey};
|
||||||
use transaction::{BudgetTransaction, Transaction};
|
use transaction::Transaction;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serializer() {
|
fn test_serializer() {
|
||||||
let mut a = Account::new(0, 512, BudgetState::id());
|
let mut a = Account::new(0, 512, BudgetState::id());
|
||||||
|
|
|
@ -0,0 +1,346 @@
|
||||||
|
//! The `budget_transaction` module provides functionality for creating Budget transactions.
|
||||||
|
|
||||||
|
use bincode::{deserialize, serialize};
|
||||||
|
use budget::{Budget, Condition};
|
||||||
|
use budget_program::BudgetState;
|
||||||
|
use chrono::prelude::*;
|
||||||
|
use hash::Hash;
|
||||||
|
use instruction::{Contract, Instruction, Vote};
|
||||||
|
use payment_plan::Payment;
|
||||||
|
use signature::{Keypair, Pubkey};
|
||||||
|
use transaction::Transaction;
|
||||||
|
|
||||||
|
pub trait BudgetTransaction {
|
||||||
|
fn budget_new_taxed(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
to: Pubkey,
|
||||||
|
tokens: i64,
|
||||||
|
fee: i64,
|
||||||
|
last_id: Hash,
|
||||||
|
) -> Self;
|
||||||
|
|
||||||
|
fn budget_new(from_keypair: &Keypair, to: Pubkey, tokens: i64, last_id: Hash) -> Self;
|
||||||
|
|
||||||
|
fn budget_new_timestamp(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
contract: Pubkey,
|
||||||
|
to: Pubkey,
|
||||||
|
dt: DateTime<Utc>,
|
||||||
|
last_id: Hash,
|
||||||
|
) -> Self;
|
||||||
|
|
||||||
|
fn budget_new_signature(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
contract: Pubkey,
|
||||||
|
to: Pubkey,
|
||||||
|
last_id: Hash,
|
||||||
|
) -> Self;
|
||||||
|
|
||||||
|
fn budget_new_vote(from_keypair: &Keypair, vote: Vote, last_id: Hash, fee: i64) -> Self;
|
||||||
|
|
||||||
|
fn budget_new_on_date(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
to: Pubkey,
|
||||||
|
contract: Pubkey,
|
||||||
|
dt: DateTime<Utc>,
|
||||||
|
dt_pubkey: Pubkey,
|
||||||
|
cancelable: Option<Pubkey>,
|
||||||
|
tokens: i64,
|
||||||
|
last_id: Hash,
|
||||||
|
) -> Self;
|
||||||
|
|
||||||
|
fn budget_new_when_signed(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
to: Pubkey,
|
||||||
|
contract: Pubkey,
|
||||||
|
witness: Pubkey,
|
||||||
|
cancelable: Option<Pubkey>,
|
||||||
|
tokens: i64,
|
||||||
|
last_id: Hash,
|
||||||
|
) -> Self;
|
||||||
|
|
||||||
|
fn vote(&self) -> Option<(Pubkey, Vote, Hash)>;
|
||||||
|
|
||||||
|
fn instruction(&self) -> Option<Instruction>;
|
||||||
|
|
||||||
|
fn verify_plan(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BudgetTransaction for Transaction {
|
||||||
|
/// Create and sign a new Transaction. Used for unit-testing.
|
||||||
|
fn budget_new_taxed(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
to: Pubkey,
|
||||||
|
tokens: i64,
|
||||||
|
fee: i64,
|
||||||
|
last_id: Hash,
|
||||||
|
) -> Self {
|
||||||
|
let payment = Payment {
|
||||||
|
tokens: tokens - fee,
|
||||||
|
to,
|
||||||
|
};
|
||||||
|
let budget = Budget::Pay(payment);
|
||||||
|
let instruction = Instruction::NewContract(Contract { budget, tokens });
|
||||||
|
let userdata = serialize(&instruction).unwrap();
|
||||||
|
Self::new(
|
||||||
|
from_keypair,
|
||||||
|
&[to],
|
||||||
|
BudgetState::id(),
|
||||||
|
userdata,
|
||||||
|
last_id,
|
||||||
|
fee,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create and sign a new Transaction. Used for unit-testing.
|
||||||
|
fn budget_new(from_keypair: &Keypair, to: Pubkey, tokens: i64, last_id: Hash) -> Self {
|
||||||
|
Self::budget_new_taxed(from_keypair, to, tokens, 0, last_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create and sign a new Witness Timestamp. Used for unit-testing.
|
||||||
|
fn budget_new_timestamp(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
contract: Pubkey,
|
||||||
|
to: Pubkey,
|
||||||
|
dt: DateTime<Utc>,
|
||||||
|
last_id: Hash,
|
||||||
|
) -> Self {
|
||||||
|
let instruction = Instruction::ApplyTimestamp(dt);
|
||||||
|
let userdata = serialize(&instruction).unwrap();
|
||||||
|
Self::new(
|
||||||
|
from_keypair,
|
||||||
|
&[contract, to],
|
||||||
|
BudgetState::id(),
|
||||||
|
userdata,
|
||||||
|
last_id,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create and sign a new Witness Signature. Used for unit-testing.
|
||||||
|
fn budget_new_signature(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
contract: Pubkey,
|
||||||
|
to: Pubkey,
|
||||||
|
last_id: Hash,
|
||||||
|
) -> Self {
|
||||||
|
let instruction = Instruction::ApplySignature;
|
||||||
|
let userdata = serialize(&instruction).unwrap();
|
||||||
|
Self::new(
|
||||||
|
from_keypair,
|
||||||
|
&[contract, to],
|
||||||
|
BudgetState::id(),
|
||||||
|
userdata,
|
||||||
|
last_id,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn budget_new_vote(from_keypair: &Keypair, vote: Vote, last_id: Hash, fee: i64) -> Self {
|
||||||
|
let instruction = Instruction::NewVote(vote);
|
||||||
|
let userdata = serialize(&instruction).expect("serialize instruction");
|
||||||
|
Self::new(from_keypair, &[], BudgetState::id(), userdata, last_id, fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create and sign a postdated Transaction. Used for unit-testing.
|
||||||
|
fn budget_new_on_date(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
to: Pubkey,
|
||||||
|
contract: Pubkey,
|
||||||
|
dt: DateTime<Utc>,
|
||||||
|
dt_pubkey: Pubkey,
|
||||||
|
cancelable: Option<Pubkey>,
|
||||||
|
tokens: i64,
|
||||||
|
last_id: Hash,
|
||||||
|
) -> Self {
|
||||||
|
let budget = if let Some(from) = cancelable {
|
||||||
|
Budget::Or(
|
||||||
|
(Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to }),
|
||||||
|
(Condition::Signature(from), Payment { tokens, to: from }),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Budget::After(Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to })
|
||||||
|
};
|
||||||
|
let instruction = Instruction::NewContract(Contract { budget, tokens });
|
||||||
|
let userdata = serialize(&instruction).expect("serialize instruction");
|
||||||
|
Self::new(
|
||||||
|
from_keypair,
|
||||||
|
&[contract],
|
||||||
|
BudgetState::id(),
|
||||||
|
userdata,
|
||||||
|
last_id,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/// Create and sign a multisig Transaction.
|
||||||
|
fn budget_new_when_signed(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
to: Pubkey,
|
||||||
|
contract: Pubkey,
|
||||||
|
witness: Pubkey,
|
||||||
|
cancelable: Option<Pubkey>,
|
||||||
|
tokens: i64,
|
||||||
|
last_id: Hash,
|
||||||
|
) -> Self {
|
||||||
|
let budget = if let Some(from) = cancelable {
|
||||||
|
Budget::Or(
|
||||||
|
(Condition::Signature(witness), Payment { tokens, to }),
|
||||||
|
(Condition::Signature(from), Payment { tokens, to: from }),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Budget::After(Condition::Signature(witness), Payment { tokens, to })
|
||||||
|
};
|
||||||
|
let instruction = Instruction::NewContract(Contract { budget, tokens });
|
||||||
|
let userdata = serialize(&instruction).expect("serialize instruction");
|
||||||
|
Self::new(
|
||||||
|
from_keypair,
|
||||||
|
&[contract],
|
||||||
|
BudgetState::id(),
|
||||||
|
userdata,
|
||||||
|
last_id,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vote(&self) -> Option<(Pubkey, Vote, Hash)> {
|
||||||
|
if let Some(Instruction::NewVote(vote)) = self.instruction() {
|
||||||
|
Some((*self.from(), vote, self.last_id))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instruction(&self) -> Option<Instruction> {
|
||||||
|
deserialize(&self.userdata).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify only the payment plan.
|
||||||
|
fn verify_plan(&self) -> bool {
|
||||||
|
if let Some(Instruction::NewContract(contract)) = self.instruction() {
|
||||||
|
self.fee >= 0
|
||||||
|
&& self.fee <= contract.tokens
|
||||||
|
&& contract.budget.verify(contract.tokens - self.fee)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use bincode::{deserialize, serialize};
|
||||||
|
use signature::KeypairUtil;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_claim() {
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
let zero = Hash::default();
|
||||||
|
let tx0 = Transaction::budget_new(&keypair, keypair.pubkey(), 42, zero);
|
||||||
|
assert!(tx0.verify_plan());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transfer() {
|
||||||
|
let zero = Hash::default();
|
||||||
|
let keypair0 = Keypair::new();
|
||||||
|
let keypair1 = Keypair::new();
|
||||||
|
let pubkey1 = keypair1.pubkey();
|
||||||
|
let tx0 = Transaction::budget_new(&keypair0, pubkey1, 42, zero);
|
||||||
|
assert!(tx0.verify_plan());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transfer_with_fee() {
|
||||||
|
let zero = Hash::default();
|
||||||
|
let keypair0 = Keypair::new();
|
||||||
|
let pubkey1 = Keypair::new().pubkey();
|
||||||
|
assert!(Transaction::budget_new_taxed(&keypair0, pubkey1, 1, 1, zero).verify_plan());
|
||||||
|
assert!(!Transaction::budget_new_taxed(&keypair0, pubkey1, 1, 2, zero).verify_plan());
|
||||||
|
assert!(!Transaction::budget_new_taxed(&keypair0, pubkey1, 1, -1, zero).verify_plan());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialize_claim() {
|
||||||
|
let budget = Budget::Pay(Payment {
|
||||||
|
tokens: 0,
|
||||||
|
to: Default::default(),
|
||||||
|
});
|
||||||
|
let instruction = Instruction::NewContract(Contract { budget, tokens: 0 });
|
||||||
|
let userdata = serialize(&instruction).unwrap();
|
||||||
|
let claim0 = Transaction {
|
||||||
|
keys: vec![],
|
||||||
|
last_id: Default::default(),
|
||||||
|
signature: Default::default(),
|
||||||
|
program_id: Default::default(),
|
||||||
|
fee: 0,
|
||||||
|
userdata,
|
||||||
|
};
|
||||||
|
let buf = serialize(&claim0).unwrap();
|
||||||
|
let claim1: Transaction = deserialize(&buf).unwrap();
|
||||||
|
assert_eq!(claim1, claim0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_token_attack() {
|
||||||
|
let zero = Hash::default();
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
let pubkey = keypair.pubkey();
|
||||||
|
let mut tx = Transaction::budget_new(&keypair, pubkey, 42, zero);
|
||||||
|
let mut instruction = tx.instruction().unwrap();
|
||||||
|
if let Instruction::NewContract(ref mut contract) = instruction {
|
||||||
|
contract.tokens = 1_000_000; // <-- attack, part 1!
|
||||||
|
if let Budget::Pay(ref mut payment) = contract.budget {
|
||||||
|
payment.tokens = contract.tokens; // <-- attack, part 2!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.userdata = serialize(&instruction).unwrap();
|
||||||
|
assert!(tx.verify_plan());
|
||||||
|
assert!(!tx.verify_signature());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hijack_attack() {
|
||||||
|
let keypair0 = Keypair::new();
|
||||||
|
let keypair1 = Keypair::new();
|
||||||
|
let thief_keypair = Keypair::new();
|
||||||
|
let pubkey1 = keypair1.pubkey();
|
||||||
|
let zero = Hash::default();
|
||||||
|
let mut tx = Transaction::budget_new(&keypair0, pubkey1, 42, zero);
|
||||||
|
let mut instruction = tx.instruction();
|
||||||
|
if let Some(Instruction::NewContract(ref mut contract)) = instruction {
|
||||||
|
if let Budget::Pay(ref mut payment) = contract.budget {
|
||||||
|
payment.to = thief_keypair.pubkey(); // <-- attack!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.userdata = serialize(&instruction).unwrap();
|
||||||
|
assert!(tx.verify_plan());
|
||||||
|
assert!(!tx.verify_signature());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_overspend_attack() {
|
||||||
|
let keypair0 = Keypair::new();
|
||||||
|
let keypair1 = Keypair::new();
|
||||||
|
let zero = Hash::default();
|
||||||
|
let mut tx = Transaction::budget_new(&keypair0, keypair1.pubkey(), 1, zero);
|
||||||
|
let mut instruction = tx.instruction().unwrap();
|
||||||
|
if let Instruction::NewContract(ref mut contract) = instruction {
|
||||||
|
if let Budget::Pay(ref mut payment) = contract.budget {
|
||||||
|
payment.tokens = 2; // <-- attack!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.userdata = serialize(&instruction).unwrap();
|
||||||
|
assert!(!tx.verify_plan());
|
||||||
|
|
||||||
|
// Also, ensure all branchs of the plan spend all tokens
|
||||||
|
let mut instruction = tx.instruction().unwrap();
|
||||||
|
if let Instruction::NewContract(ref mut contract) = instruction {
|
||||||
|
if let Budget::Pay(ref mut payment) = contract.budget {
|
||||||
|
payment.tokens = 0; // <-- whoops!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.userdata = serialize(&instruction).unwrap();
|
||||||
|
assert!(!tx.verify_plan());
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
//! transactions within it. Entries cannot be reordered, and its field `num_hashes`
|
//! transactions within it. Entries cannot be reordered, and its field `num_hashes`
|
||||||
//! represents an approximate amount of time since the last Entry was created.
|
//! represents an approximate amount of time since the last Entry was created.
|
||||||
use bincode::{serialize_into, serialized_size};
|
use bincode::{serialize_into, serialized_size};
|
||||||
|
use budget_transaction::BudgetTransaction;
|
||||||
use hash::Hash;
|
use hash::Hash;
|
||||||
use packet::{BlobRecycler, SharedBlob, BLOB_DATA_SIZE};
|
use packet::{BlobRecycler, SharedBlob, BLOB_DATA_SIZE};
|
||||||
use poh::Poh;
|
use poh::Poh;
|
||||||
|
@ -226,12 +227,13 @@ pub fn next_entry(start_hash: &Hash, num_hashes: u64, transactions: Vec<Transact
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use budget_transaction::BudgetTransaction;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use entry::Entry;
|
use entry::Entry;
|
||||||
use hash::hash;
|
use hash::hash;
|
||||||
use signature::{Keypair, KeypairUtil};
|
use signature::{Keypair, KeypairUtil};
|
||||||
use system_transaction::SystemTransaction;
|
use system_transaction::SystemTransaction;
|
||||||
use transaction::{BudgetTransaction, Transaction};
|
use transaction::Transaction;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_entry_verify() {
|
fn test_entry_verify() {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
//! access read to a persistent file-based ledger.
|
//! access read to a persistent file-based ledger.
|
||||||
|
|
||||||
use bincode::{self, deserialize, deserialize_from, serialize_into, serialized_size};
|
use bincode::{self, deserialize, deserialize_from, serialize_into, serialized_size};
|
||||||
|
use budget_transaction::BudgetTransaction;
|
||||||
use entry::Entry;
|
use entry::Entry;
|
||||||
use hash::Hash;
|
use hash::Hash;
|
||||||
use instruction::Vote;
|
use instruction::Vote;
|
||||||
|
@ -456,8 +457,12 @@ impl Block for [Entry] {
|
||||||
|
|
||||||
fn votes(&self) -> Vec<(Pubkey, Vote, Hash)> {
|
fn votes(&self) -> Vec<(Pubkey, Vote, Hash)> {
|
||||||
self.iter()
|
self.iter()
|
||||||
.flat_map(|entry| entry.transactions.iter().filter_map(Transaction::vote))
|
.flat_map(|entry| {
|
||||||
.collect()
|
entry
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.filter_map(BudgetTransaction::vote)
|
||||||
|
}).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,6 +581,7 @@ pub fn genesis(name: &str, num: i64) -> (Mint, String) {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use bincode::serialized_size;
|
use bincode::serialized_size;
|
||||||
|
use budget_transaction::BudgetTransaction;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use entry::{next_entry, Entry};
|
use entry::{next_entry, Entry};
|
||||||
use hash::hash;
|
use hash::hash;
|
||||||
|
@ -584,7 +590,7 @@ mod tests {
|
||||||
use signature::{Keypair, KeypairUtil};
|
use signature::{Keypair, KeypairUtil};
|
||||||
use std;
|
use std;
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
use transaction::{BudgetTransaction, Transaction};
|
use transaction::Transaction;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_verify_slice() {
|
fn test_verify_slice() {
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub mod banking_stage;
|
||||||
pub mod blob_fetch_stage;
|
pub mod blob_fetch_stage;
|
||||||
pub mod broadcast_stage;
|
pub mod broadcast_stage;
|
||||||
pub mod budget;
|
pub mod budget;
|
||||||
|
pub mod budget_transaction;
|
||||||
pub mod choose_gossip_peer_strategy;
|
pub mod choose_gossip_peer_strategy;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod instruction;
|
pub mod instruction;
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
//! The `transaction` module provides functionality for creating log transactions.
|
//! The `transaction` module provides functionality for creating log transactions.
|
||||||
|
|
||||||
use bincode::{deserialize, serialize};
|
use bincode::serialize;
|
||||||
use budget::{Budget, Condition};
|
|
||||||
use budget_program::BudgetState;
|
|
||||||
use chrono::prelude::*;
|
|
||||||
use hash::{Hash, Hasher};
|
use hash::{Hash, Hasher};
|
||||||
use instruction::{Contract, Instruction, Vote};
|
|
||||||
use payment_plan::Payment;
|
|
||||||
use signature::{Keypair, KeypairUtil, Pubkey, Signature};
|
use signature::{Keypair, KeypairUtil, Pubkey, Signature};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
@ -102,29 +97,10 @@ impl Transaction {
|
||||||
.verify(&self.from().as_ref(), &self.get_sign_data())
|
.verify(&self.from().as_ref(), &self.get_sign_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vote(&self) -> Option<(Pubkey, Vote, Hash)> {
|
|
||||||
if let Some(Instruction::NewVote(vote)) = self.instruction() {
|
|
||||||
Some((*self.from(), vote, self.last_id))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from(&self) -> &Pubkey {
|
pub fn from(&self) -> &Pubkey {
|
||||||
&self.keys[0]
|
&self.keys[0]
|
||||||
}
|
}
|
||||||
pub fn instruction(&self) -> Option<Instruction> {
|
|
||||||
deserialize(&self.userdata).ok()
|
|
||||||
}
|
|
||||||
/// Verify only the payment plan.
|
|
||||||
pub fn verify_plan(&self) -> bool {
|
|
||||||
if let Some(Instruction::NewContract(contract)) = self.instruction() {
|
|
||||||
self.fee >= 0
|
|
||||||
&& self.fee <= contract.tokens
|
|
||||||
&& contract.budget.verify(contract.tokens - self.fee)
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// a hash of a slice of transactions only needs to hash the signatures
|
// a hash of a slice of transactions only needs to hash the signatures
|
||||||
pub fn hash(transactions: &[Transaction]) -> Hash {
|
pub fn hash(transactions: &[Transaction]) -> Hash {
|
||||||
let mut hasher = Hasher::default();
|
let mut hasher = Hasher::default();
|
||||||
|
@ -135,311 +111,12 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BudgetTransaction {
|
|
||||||
fn budget_new_taxed(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
to: Pubkey,
|
|
||||||
tokens: i64,
|
|
||||||
fee: i64,
|
|
||||||
last_id: Hash,
|
|
||||||
) -> Self;
|
|
||||||
|
|
||||||
fn budget_new(from_keypair: &Keypair, to: Pubkey, tokens: i64, last_id: Hash) -> Self;
|
|
||||||
|
|
||||||
fn budget_new_timestamp(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
contract: Pubkey,
|
|
||||||
to: Pubkey,
|
|
||||||
dt: DateTime<Utc>,
|
|
||||||
last_id: Hash,
|
|
||||||
) -> Self;
|
|
||||||
|
|
||||||
fn budget_new_signature(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
contract: Pubkey,
|
|
||||||
to: Pubkey,
|
|
||||||
last_id: Hash,
|
|
||||||
) -> Self;
|
|
||||||
|
|
||||||
fn budget_new_vote(from_keypair: &Keypair, vote: Vote, last_id: Hash, fee: i64) -> Self;
|
|
||||||
|
|
||||||
fn budget_new_on_date(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
to: Pubkey,
|
|
||||||
contract: Pubkey,
|
|
||||||
dt: DateTime<Utc>,
|
|
||||||
dt_pubkey: Pubkey,
|
|
||||||
cancelable: Option<Pubkey>,
|
|
||||||
tokens: i64,
|
|
||||||
last_id: Hash,
|
|
||||||
) -> Self;
|
|
||||||
|
|
||||||
fn budget_new_when_signed(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
to: Pubkey,
|
|
||||||
contract: Pubkey,
|
|
||||||
witness: Pubkey,
|
|
||||||
cancelable: Option<Pubkey>,
|
|
||||||
tokens: i64,
|
|
||||||
last_id: Hash,
|
|
||||||
) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BudgetTransaction for Transaction {
|
|
||||||
/// Create and sign a new Transaction. Used for unit-testing.
|
|
||||||
fn budget_new_taxed(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
to: Pubkey,
|
|
||||||
tokens: i64,
|
|
||||||
fee: i64,
|
|
||||||
last_id: Hash,
|
|
||||||
) -> Self {
|
|
||||||
let payment = Payment {
|
|
||||||
tokens: tokens - fee,
|
|
||||||
to,
|
|
||||||
};
|
|
||||||
let budget = Budget::Pay(payment);
|
|
||||||
let instruction = Instruction::NewContract(Contract { budget, tokens });
|
|
||||||
let userdata = serialize(&instruction).unwrap();
|
|
||||||
Self::new(
|
|
||||||
from_keypair,
|
|
||||||
&[to],
|
|
||||||
BudgetState::id(),
|
|
||||||
userdata,
|
|
||||||
last_id,
|
|
||||||
fee,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create and sign a new Transaction. Used for unit-testing.
|
|
||||||
fn budget_new(from_keypair: &Keypair, to: Pubkey, tokens: i64, last_id: Hash) -> Self {
|
|
||||||
Self::budget_new_taxed(from_keypair, to, tokens, 0, last_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create and sign a new Witness Timestamp. Used for unit-testing.
|
|
||||||
fn budget_new_timestamp(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
contract: Pubkey,
|
|
||||||
to: Pubkey,
|
|
||||||
dt: DateTime<Utc>,
|
|
||||||
last_id: Hash,
|
|
||||||
) -> Self {
|
|
||||||
let instruction = Instruction::ApplyTimestamp(dt);
|
|
||||||
let userdata = serialize(&instruction).unwrap();
|
|
||||||
Self::new(
|
|
||||||
from_keypair,
|
|
||||||
&[contract, to],
|
|
||||||
BudgetState::id(),
|
|
||||||
userdata,
|
|
||||||
last_id,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create and sign a new Witness Signature. Used for unit-testing.
|
|
||||||
fn budget_new_signature(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
contract: Pubkey,
|
|
||||||
to: Pubkey,
|
|
||||||
last_id: Hash,
|
|
||||||
) -> Self {
|
|
||||||
let instruction = Instruction::ApplySignature;
|
|
||||||
let userdata = serialize(&instruction).unwrap();
|
|
||||||
Self::new(
|
|
||||||
from_keypair,
|
|
||||||
&[contract, to],
|
|
||||||
BudgetState::id(),
|
|
||||||
userdata,
|
|
||||||
last_id,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn budget_new_vote(from_keypair: &Keypair, vote: Vote, last_id: Hash, fee: i64) -> Self {
|
|
||||||
let instruction = Instruction::NewVote(vote);
|
|
||||||
let userdata = serialize(&instruction).expect("serialize instruction");
|
|
||||||
Self::new(from_keypair, &[], BudgetState::id(), userdata, last_id, fee)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create and sign a postdated Transaction. Used for unit-testing.
|
|
||||||
fn budget_new_on_date(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
to: Pubkey,
|
|
||||||
contract: Pubkey,
|
|
||||||
dt: DateTime<Utc>,
|
|
||||||
dt_pubkey: Pubkey,
|
|
||||||
cancelable: Option<Pubkey>,
|
|
||||||
tokens: i64,
|
|
||||||
last_id: Hash,
|
|
||||||
) -> Self {
|
|
||||||
let budget = if let Some(from) = cancelable {
|
|
||||||
Budget::Or(
|
|
||||||
(Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to }),
|
|
||||||
(Condition::Signature(from), Payment { tokens, to: from }),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Budget::After(Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to })
|
|
||||||
};
|
|
||||||
let instruction = Instruction::NewContract(Contract { budget, tokens });
|
|
||||||
let userdata = serialize(&instruction).expect("serialize instruction");
|
|
||||||
Self::new(
|
|
||||||
from_keypair,
|
|
||||||
&[contract],
|
|
||||||
BudgetState::id(),
|
|
||||||
userdata,
|
|
||||||
last_id,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// Create and sign a multisig Transaction.
|
|
||||||
fn budget_new_when_signed(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
to: Pubkey,
|
|
||||||
contract: Pubkey,
|
|
||||||
witness: Pubkey,
|
|
||||||
cancelable: Option<Pubkey>,
|
|
||||||
tokens: i64,
|
|
||||||
last_id: Hash,
|
|
||||||
) -> Self {
|
|
||||||
let budget = if let Some(from) = cancelable {
|
|
||||||
Budget::Or(
|
|
||||||
(Condition::Signature(witness), Payment { tokens, to }),
|
|
||||||
(Condition::Signature(from), Payment { tokens, to: from }),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Budget::After(Condition::Signature(witness), Payment { tokens, to })
|
|
||||||
};
|
|
||||||
let instruction = Instruction::NewContract(Contract { budget, tokens });
|
|
||||||
let userdata = serialize(&instruction).expect("serialize instruction");
|
|
||||||
Self::new(
|
|
||||||
from_keypair,
|
|
||||||
&[contract],
|
|
||||||
BudgetState::id(),
|
|
||||||
userdata,
|
|
||||||
last_id,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use bincode::{deserialize, serialize};
|
use bincode::serialize;
|
||||||
use signature::GenKeys;
|
use signature::GenKeys;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_claim() {
|
|
||||||
let keypair = Keypair::new();
|
|
||||||
let zero = Hash::default();
|
|
||||||
let tx0 = Transaction::budget_new(&keypair, keypair.pubkey(), 42, zero);
|
|
||||||
assert!(tx0.verify_plan());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_transfer() {
|
|
||||||
let zero = Hash::default();
|
|
||||||
let keypair0 = Keypair::new();
|
|
||||||
let keypair1 = Keypair::new();
|
|
||||||
let pubkey1 = keypair1.pubkey();
|
|
||||||
let tx0 = Transaction::budget_new(&keypair0, pubkey1, 42, zero);
|
|
||||||
assert!(tx0.verify_plan());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_transfer_with_fee() {
|
|
||||||
let zero = Hash::default();
|
|
||||||
let keypair0 = Keypair::new();
|
|
||||||
let pubkey1 = Keypair::new().pubkey();
|
|
||||||
assert!(Transaction::budget_new_taxed(&keypair0, pubkey1, 1, 1, zero).verify_plan());
|
|
||||||
assert!(!Transaction::budget_new_taxed(&keypair0, pubkey1, 1, 2, zero).verify_plan());
|
|
||||||
assert!(!Transaction::budget_new_taxed(&keypair0, pubkey1, 1, -1, zero).verify_plan());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_serialize_claim() {
|
|
||||||
let budget = Budget::Pay(Payment {
|
|
||||||
tokens: 0,
|
|
||||||
to: Default::default(),
|
|
||||||
});
|
|
||||||
let instruction = Instruction::NewContract(Contract { budget, tokens: 0 });
|
|
||||||
let userdata = serialize(&instruction).unwrap();
|
|
||||||
let claim0 = Transaction {
|
|
||||||
keys: vec![],
|
|
||||||
last_id: Default::default(),
|
|
||||||
signature: Default::default(),
|
|
||||||
program_id: Default::default(),
|
|
||||||
fee: 0,
|
|
||||||
userdata,
|
|
||||||
};
|
|
||||||
let buf = serialize(&claim0).unwrap();
|
|
||||||
let claim1: Transaction = deserialize(&buf).unwrap();
|
|
||||||
assert_eq!(claim1, claim0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_token_attack() {
|
|
||||||
let zero = Hash::default();
|
|
||||||
let keypair = Keypair::new();
|
|
||||||
let pubkey = keypair.pubkey();
|
|
||||||
let mut tx = Transaction::budget_new(&keypair, pubkey, 42, zero);
|
|
||||||
let mut instruction = tx.instruction().unwrap();
|
|
||||||
if let Instruction::NewContract(ref mut contract) = instruction {
|
|
||||||
contract.tokens = 1_000_000; // <-- attack, part 1!
|
|
||||||
if let Budget::Pay(ref mut payment) = contract.budget {
|
|
||||||
payment.tokens = contract.tokens; // <-- attack, part 2!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tx.userdata = serialize(&instruction).unwrap();
|
|
||||||
assert!(tx.verify_plan());
|
|
||||||
assert!(!tx.verify_signature());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_hijack_attack() {
|
|
||||||
let keypair0 = Keypair::new();
|
|
||||||
let keypair1 = Keypair::new();
|
|
||||||
let thief_keypair = Keypair::new();
|
|
||||||
let pubkey1 = keypair1.pubkey();
|
|
||||||
let zero = Hash::default();
|
|
||||||
let mut tx = Transaction::budget_new(&keypair0, pubkey1, 42, zero);
|
|
||||||
let mut instruction = tx.instruction();
|
|
||||||
if let Some(Instruction::NewContract(ref mut contract)) = instruction {
|
|
||||||
if let Budget::Pay(ref mut payment) = contract.budget {
|
|
||||||
payment.to = thief_keypair.pubkey(); // <-- attack!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tx.userdata = serialize(&instruction).unwrap();
|
|
||||||
assert!(tx.verify_plan());
|
|
||||||
assert!(!tx.verify_signature());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_overspend_attack() {
|
|
||||||
let keypair0 = Keypair::new();
|
|
||||||
let keypair1 = Keypair::new();
|
|
||||||
let zero = Hash::default();
|
|
||||||
let mut tx = Transaction::budget_new(&keypair0, keypair1.pubkey(), 1, zero);
|
|
||||||
let mut instruction = tx.instruction().unwrap();
|
|
||||||
if let Instruction::NewContract(ref mut contract) = instruction {
|
|
||||||
if let Budget::Pay(ref mut payment) = contract.budget {
|
|
||||||
payment.tokens = 2; // <-- attack!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tx.userdata = serialize(&instruction).unwrap();
|
|
||||||
assert!(!tx.verify_plan());
|
|
||||||
|
|
||||||
// Also, ensure all branchs of the plan spend all tokens
|
|
||||||
let mut instruction = tx.instruction().unwrap();
|
|
||||||
if let Instruction::NewContract(ref mut contract) = instruction {
|
|
||||||
if let Budget::Pay(ref mut payment) = contract.budget {
|
|
||||||
payment.tokens = 0; // <-- whoops!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tx.userdata = serialize(&instruction).unwrap();
|
|
||||||
assert!(!tx.verify_plan());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Detect binary changes in the serialized contract userdata, which could have a downstream
|
/// Detect binary changes in the serialized contract userdata, which could have a downstream
|
||||||
/// affect on SDKs and DApps
|
/// affect on SDKs and DApps
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use bank::Bank;
|
use bank::Bank;
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
|
use budget_transaction::BudgetTransaction;
|
||||||
use counter::Counter;
|
use counter::Counter;
|
||||||
use crdt::Crdt;
|
use crdt::Crdt;
|
||||||
use hash::Hash;
|
use hash::Hash;
|
||||||
|
@ -16,7 +17,7 @@ use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use streamer::BlobSender;
|
use streamer::BlobSender;
|
||||||
use timing;
|
use timing;
|
||||||
use transaction::{BudgetTransaction, Transaction};
|
use transaction::Transaction;
|
||||||
|
|
||||||
pub const VOTE_TIMEOUT_MS: u64 = 1000;
|
pub const VOTE_TIMEOUT_MS: u64 = 1000;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use bincode::{deserialize, serialize};
|
use bincode::{deserialize, serialize};
|
||||||
use bs58;
|
use bs58;
|
||||||
use budget_program::BudgetState;
|
use budget_program::BudgetState;
|
||||||
|
use budget_transaction::BudgetTransaction;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use crdt::NodeInfo;
|
use crdt::NodeInfo;
|
||||||
|
@ -23,7 +24,7 @@ use std::thread::sleep;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{error, fmt, mem};
|
use std::{error, fmt, mem};
|
||||||
use system_transaction::SystemTransaction;
|
use system_transaction::SystemTransaction;
|
||||||
use transaction::{BudgetTransaction, Transaction};
|
use transaction::Transaction;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum WalletCommand {
|
pub enum WalletCommand {
|
||||||
|
|
Loading…
Reference in New Issue