Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Vet timestamp source from contract, not leader #563

Merged
merged 3 commits into from
Jul 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 3 additions & 62 deletions src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,6 @@ pub struct Bank {
/// reject transactions with signatures its seen before
last_ids_sigs: RwLock<HashMap<Hash, HashSet<Signature>>>,

/// The set of trusted timekeepers. A Timestamp transaction from a `PublicKey`
/// outside this set will be discarded. Note that if validators do not have the
/// same set as leaders, they may interpret the ledger differently.
time_sources: RwLock<HashSet<PublicKey>>,

/// The most recent timestamp from a trusted timekeeper. This timestamp is applied
/// to every smart contract when it enters the system. If it is waiting on a
/// timestamp witness before that timestamp, the bank will execute it immediately.
last_time: RwLock<DateTime<Utc>>,

/// The number of transactions the bank has processed without error since the
/// start of the ledger.
transaction_count: AtomicUsize,
Expand All @@ -103,8 +93,6 @@ impl Default for Bank {
pending: RwLock::new(HashMap::new()),
last_ids: RwLock::new(VecDeque::new()),
last_ids_sigs: RwLock::new(HashMap::new()),
time_sources: RwLock::new(HashSet::new()),
last_time: RwLock::new(Utc.timestamp(0, 0)),
transaction_count: AtomicUsize::new(0),
}
}
Expand Down Expand Up @@ -244,11 +232,7 @@ impl Bank {
fn apply_credits(&self, tx: &Transaction, balances: &mut HashMap<PublicKey, i64>) {
match &tx.instruction {
Instruction::NewContract(contract) => {
let mut plan = contract.plan.clone();
plan.apply_witness(&Witness::Timestamp(*self.last_time
.read()
.expect("timestamp creation in apply_credits")));

let plan = contract.plan.clone();
if let Some(payment) = plan.final_payment() {
self.apply_payment(&payment, balances);
} else {
Expand Down Expand Up @@ -404,7 +388,7 @@ impl Bank {
.expect("write() in apply_signature")
.entry(tx_sig)
{
e.get_mut().apply_witness(&Witness::Signature(from));
e.get_mut().apply_witness(&Witness::Signature, &from);
if let Some(payment) = e.get().final_payment() {
self.apply_payment(&payment, &mut self.balances.write().unwrap());
e.remove_entry();
Expand All @@ -417,31 +401,6 @@ impl Bank {
/// Process a Witness Timestamp. Any payment plans waiting on this timestamp
/// will progress one step.
fn apply_timestamp(&self, from: PublicKey, dt: DateTime<Utc>) -> Result<()> {
// If this is the first timestamp we've seen, it probably came from the genesis block,
// so we'll trust it.
if *self.last_time
.read()
.expect("'last_time' read lock on first timestamp check")
== Utc.timestamp(0, 0)
{
self.time_sources
.write()
.expect("'time_sources' write lock on first timestamp")
.insert(from);
}

if self.time_sources
.read()
.expect("'time_sources' read lock")
.contains(&from)
{
if dt > *self.last_time.read().expect("'last_time' read lock") {
*self.last_time.write().expect("'last_time' write lock") = dt;
}
} else {
return Ok(());
}

// Check to see if any timelocked transactions can be completed.
let mut completed = vec![];

Expand All @@ -451,9 +410,7 @@ impl Bank {
.write()
.expect("'pending' write lock in apply_timestamp");
for (key, plan) in pending.iter_mut() {
plan.apply_witness(&Witness::Timestamp(*self.last_time
.read()
.expect("'last_time' read lock when creating timestamp")));
plan.apply_witness(&Witness::Timestamp(dt), &from);
if let Some(payment) = plan.final_payment() {
self.apply_payment(&payment, &mut self.balances.write().unwrap());
completed.push(key.clone());
Expand Down Expand Up @@ -634,22 +591,6 @@ mod tests {
assert_ne!(bank.get_balance(&pubkey), 2);
}

#[test]
fn test_transfer_after_date() {
let mint = Mint::new(1);
let bank = Bank::new(&mint);
let pubkey = KeyPair::new().pubkey();
let dt = Utc::now();
bank.apply_timestamp(mint.pubkey(), dt).unwrap();

// It's now past now, so this transfer should be processed immediately.
bank.transfer_on_date(1, &mint.keypair(), pubkey, dt, mint.last_id())
.unwrap();

assert_eq!(bank.get_balance(&mint.pubkey()), 0);
assert_eq!(bank.get_balance(&pubkey), 1);
}

#[test]
fn test_cancel_transfer() {
let mint = Mint::new(1);
Expand Down
70 changes: 47 additions & 23 deletions src/budget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ use std::mem;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Condition {
/// Wait for a `Timestamp` `Witness` at or after the given `DateTime`.
Timestamp(DateTime<Utc>),
Timestamp(DateTime<Utc>, PublicKey),

/// Wait for a `Signature` `Witness` from `PublicKey`.
Signature(PublicKey),
}

impl Condition {
/// Return true if the given Witness satisfies this Condition.
pub fn is_satisfied(&self, witness: &Witness) -> bool {
pub fn is_satisfied(&self, witness: &Witness, from: &PublicKey) -> bool {
match (self, witness) {
(Condition::Signature(pubkey), Witness::Signature(from)) => pubkey == from,
(Condition::Timestamp(dt), Witness::Timestamp(last_time)) => dt <= last_time,
(Condition::Signature(pubkey), Witness::Signature) => pubkey == from,
(Condition::Timestamp(dt, pubkey), Witness::Timestamp(last_time)) => {
pubkey == from && dt <= last_time
}
_ => false,
}
}
Expand Down Expand Up @@ -56,8 +58,13 @@ impl Budget {
}

/// Create a budget that pays `tokens` to `to` after the given DateTime.
pub fn new_future_payment(dt: DateTime<Utc>, tokens: i64, to: PublicKey) -> Self {
Budget::After(Condition::Timestamp(dt), Payment { tokens, to })
pub fn new_future_payment(
dt: DateTime<Utc>,
from: PublicKey,
tokens: i64,
to: PublicKey,
) -> Self {
Budget::After(Condition::Timestamp(dt, from), Payment { tokens, to })
}

/// Create a budget that pays `tokens` to `to` after the given DateTime
Expand All @@ -69,7 +76,7 @@ impl Budget {
to: PublicKey,
) -> Self {
Budget::Or(
(Condition::Timestamp(dt), Payment { tokens, to }),
(Condition::Timestamp(dt, from), Payment { tokens, to }),
(Condition::Signature(from), Payment { tokens, to: from }),
)
}
Expand All @@ -94,11 +101,11 @@ impl PaymentPlan for Budget {

/// Apply a witness to the budget to see if the budget can be reduced.
/// If so, modify the budget in-place.
fn apply_witness(&mut self, witness: &Witness) {
fn apply_witness(&mut self, witness: &Witness, from: &PublicKey) {
let new_payment = match self {
Budget::After(cond, payment) if cond.is_satisfied(witness) => Some(payment),
Budget::Or((cond, payment), _) if cond.is_satisfied(witness) => Some(payment),
Budget::Or(_, (cond, payment)) if cond.is_satisfied(witness) => Some(payment),
Budget::After(cond, payment) if cond.is_satisfied(witness, from) => Some(payment),
Budget::Or((cond, payment), _) if cond.is_satisfied(witness, from) => Some(payment),
Budget::Or(_, (cond, payment)) if cond.is_satisfied(witness, from) => Some(payment),
_ => None,
}.cloned();

Expand All @@ -111,20 +118,22 @@ impl PaymentPlan for Budget {
#[cfg(test)]
mod tests {
use super::*;
use signature::{KeyPair, KeyPairUtil};

#[test]
fn test_signature_satisfied() {
let sig = PublicKey::default();
assert!(Condition::Signature(sig).is_satisfied(&Witness::Signature(sig)));
let from = PublicKey::default();
assert!(Condition::Signature(from).is_satisfied(&Witness::Signature, &from));
}

#[test]
fn test_timestamp_satisfied() {
let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8);
assert!(Condition::Timestamp(dt1).is_satisfied(&Witness::Timestamp(dt1)));
assert!(Condition::Timestamp(dt1).is_satisfied(&Witness::Timestamp(dt2)));
assert!(!Condition::Timestamp(dt2).is_satisfied(&Witness::Timestamp(dt1)));
let from = PublicKey::default();
assert!(Condition::Timestamp(dt1, from).is_satisfied(&Witness::Timestamp(dt1), &from));
assert!(Condition::Timestamp(dt1, from).is_satisfied(&Witness::Timestamp(dt2), &from));
assert!(!Condition::Timestamp(dt2, from).is_satisfied(&Witness::Timestamp(dt1), &from));
}

#[test]
Expand All @@ -134,7 +143,7 @@ mod tests {
let to = PublicKey::default();
assert!(Budget::new_payment(42, to).verify(42));
assert!(Budget::new_authorized_payment(from, 42, to).verify(42));
assert!(Budget::new_future_payment(dt, 42, to).verify(42));
assert!(Budget::new_future_payment(dt, from, 42, to).verify(42));
assert!(Budget::new_cancelable_future_payment(dt, from, 42, to).verify(42));
}

Expand All @@ -144,32 +153,47 @@ mod tests {
let to = PublicKey::default();

let mut budget = Budget::new_authorized_payment(from, 42, to);
budget.apply_witness(&Witness::Signature(from));
budget.apply_witness(&Witness::Signature, &from);
assert_eq!(budget, Budget::new_payment(42, to));
}

#[test]
fn test_future_payment() {
let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let to = PublicKey::default();
let from = KeyPair::new().pubkey();
let to = KeyPair::new().pubkey();

let mut budget = Budget::new_future_payment(dt, 42, to);
budget.apply_witness(&Witness::Timestamp(dt));
let mut budget = Budget::new_future_payment(dt, from, 42, to);
budget.apply_witness(&Witness::Timestamp(dt), &from);
assert_eq!(budget, Budget::new_payment(42, to));
}

#[test]
fn test_unauthorized_future_payment() {
// Ensure timestamp will only be acknowledged if it came from the
// whitelisted public key.
let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let from = KeyPair::new().pubkey();
let to = KeyPair::new().pubkey();

let mut budget = Budget::new_future_payment(dt, from, 42, to);
let orig_budget = budget.clone();
budget.apply_witness(&Witness::Timestamp(dt), &to); // <-- Attack!
assert_eq!(budget, orig_budget);
}

#[test]
fn test_cancelable_future_payment() {
let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let from = PublicKey::default();
let to = PublicKey::default();

let mut budget = Budget::new_cancelable_future_payment(dt, from, 42, to);
budget.apply_witness(&Witness::Timestamp(dt));
budget.apply_witness(&Witness::Timestamp(dt), &from);
assert_eq!(budget, Budget::new_payment(42, to));

let mut budget = Budget::new_cancelable_future_payment(dt, from, 42, to);
budget.apply_witness(&Witness::Signature(from));
budget.apply_witness(&Witness::Signature, &from);
assert_eq!(budget, Budget::new_payment(42, from));
}
}
4 changes: 2 additions & 2 deletions src/payment_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub enum Witness {
Timestamp(DateTime<Utc>),

/// A siganture from PublicKey.
Signature(PublicKey),
Signature,
}

/// Some amount of tokens that should be sent to the `to` `PublicKey`.
Expand All @@ -36,5 +36,5 @@ pub trait PaymentPlan {

/// Apply a witness to the payment plan to see if the plan can be reduced.
/// If so, modify the plan in-place.
fn apply_witness(&mut self, witness: &Witness);
fn apply_witness(&mut self, witness: &Witness, from: &PublicKey);
}
6 changes: 3 additions & 3 deletions src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ impl PaymentPlan for Plan {
}
}

fn apply_witness(&mut self, witness: &Witness) {
fn apply_witness(&mut self, witness: &Witness, from: &PublicKey) {
match self {
Plan::Budget(budget) => budget.apply_witness(witness),
Plan::Budget(budget) => budget.apply_witness(witness, from),
}
}
}
Expand Down Expand Up @@ -145,7 +145,7 @@ impl Transaction {
) -> Self {
let from = from_keypair.pubkey();
let budget = Budget::Or(
(Condition::Timestamp(dt), Payment { tokens, to }),
(Condition::Timestamp(dt, from), Payment { tokens, to }),
(Condition::Signature(from), Payment { tokens, to: from }),
);
let plan = Plan::Budget(budget);
Expand Down