Move BudgetTransaction into its own module

This commit is contained in:
Greg Fitzgerald 2018-09-26 10:33:52 -06:00
parent c83dcea87d
commit df3b78c18c
10 changed files with 371 additions and 333 deletions

View File

@ -6,6 +6,7 @@
use bincode::deserialize;
use bincode::serialize;
use budget_program::BudgetState;
use budget_transaction::BudgetTransaction;
use counter::Counter;
use dynamic_program::{DynamicProgram, KeyedAccount};
use entry::Entry;

View File

@ -4,6 +4,7 @@
use bank::Bank;
use bincode::deserialize;
use budget_transaction::BudgetTransaction;
use counter::Counter;
use entry::Entry;
use hash::Hasher;

View File

@ -268,10 +268,12 @@ mod test {
use bank::Account;
use bincode::serialize;
use budget_program::{BudgetError, BudgetState};
use budget_transaction::BudgetTransaction;
use chrono::prelude::{DateTime, NaiveDate, Utc};
use hash::Hash;
use signature::{GenKeys, Keypair, KeypairUtil, Pubkey};
use transaction::{BudgetTransaction, Transaction};
use transaction::Transaction;
#[test]
fn test_serializer() {
let mut a = Account::new(0, 512, BudgetState::id());

346
src/budget_transaction.rs Normal file
View File

@ -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());
}
}

View File

@ -3,6 +3,7 @@
//! transactions within it. Entries cannot be reordered, and its field `num_hashes`
//! represents an approximate amount of time since the last Entry was created.
use bincode::{serialize_into, serialized_size};
use budget_transaction::BudgetTransaction;
use hash::Hash;
use packet::{BlobRecycler, SharedBlob, BLOB_DATA_SIZE};
use poh::Poh;
@ -226,12 +227,13 @@ pub fn next_entry(start_hash: &Hash, num_hashes: u64, transactions: Vec<Transact
#[cfg(test)]
mod tests {
use super::*;
use budget_transaction::BudgetTransaction;
use chrono::prelude::*;
use entry::Entry;
use hash::hash;
use signature::{Keypair, KeypairUtil};
use system_transaction::SystemTransaction;
use transaction::{BudgetTransaction, Transaction};
use transaction::Transaction;
#[test]
fn test_entry_verify() {

View File

@ -3,6 +3,7 @@
//! access read to a persistent file-based ledger.
use bincode::{self, deserialize, deserialize_from, serialize_into, serialized_size};
use budget_transaction::BudgetTransaction;
use entry::Entry;
use hash::Hash;
use instruction::Vote;
@ -456,8 +457,12 @@ impl Block for [Entry] {
fn votes(&self) -> Vec<(Pubkey, Vote, Hash)> {
self.iter()
.flat_map(|entry| entry.transactions.iter().filter_map(Transaction::vote))
.collect()
.flat_map(|entry| {
entry
.transactions
.iter()
.filter_map(BudgetTransaction::vote)
}).collect()
}
}
@ -576,6 +581,7 @@ pub fn genesis(name: &str, num: i64) -> (Mint, String) {
mod tests {
use super::*;
use bincode::serialized_size;
use budget_transaction::BudgetTransaction;
use chrono::prelude::*;
use entry::{next_entry, Entry};
use hash::hash;
@ -584,7 +590,7 @@ mod tests {
use signature::{Keypair, KeypairUtil};
use std;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use transaction::{BudgetTransaction, Transaction};
use transaction::Transaction;
#[test]
fn test_verify_slice() {

View File

@ -14,6 +14,7 @@ pub mod banking_stage;
pub mod blob_fetch_stage;
pub mod broadcast_stage;
pub mod budget;
pub mod budget_transaction;
pub mod choose_gossip_peer_strategy;
pub mod client;
pub mod instruction;

View File

@ -1,12 +1,7 @@
//! The `transaction` module provides functionality for creating log transactions.
use bincode::{deserialize, serialize};
use budget::{Budget, Condition};
use budget_program::BudgetState;
use chrono::prelude::*;
use bincode::serialize;
use hash::{Hash, Hasher};
use instruction::{Contract, Instruction, Vote};
use payment_plan::Payment;
use signature::{Keypair, KeypairUtil, Pubkey, Signature};
use std::mem::size_of;
@ -102,29 +97,10 @@ impl Transaction {
.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 {
&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
pub fn hash(transactions: &[Transaction]) -> Hash {
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)]
mod tests {
use super::*;
use bincode::{deserialize, serialize};
use bincode::serialize;
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
/// affect on SDKs and DApps
#[test]

View File

@ -2,6 +2,7 @@
use bank::Bank;
use bincode::serialize;
use budget_transaction::BudgetTransaction;
use counter::Counter;
use crdt::Crdt;
use hash::Hash;
@ -16,7 +17,7 @@ use std::sync::atomic::AtomicUsize;
use std::sync::{Arc, RwLock};
use streamer::BlobSender;
use timing;
use transaction::{BudgetTransaction, Transaction};
use transaction::Transaction;
pub const VOTE_TIMEOUT_MS: u64 = 1000;

View File

@ -1,6 +1,7 @@
use bincode::{deserialize, serialize};
use bs58;
use budget_program::BudgetState;
use budget_transaction::BudgetTransaction;
use chrono::prelude::*;
use clap::ArgMatches;
use crdt::NodeInfo;
@ -23,7 +24,7 @@ use std::thread::sleep;
use std::time::Duration;
use std::{error, fmt, mem};
use system_transaction::SystemTransaction;
use transaction::{BudgetTransaction, Transaction};
use transaction::Transaction;
#[derive(Debug, PartialEq)]
pub enum WalletCommand {