Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.
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
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "silk"
description = "A silky smooth implementation of the Loom architecture"
version = "0.3.0"
version = "0.3.1"
documentation = "https://docs.rs/silk"
homepage = "http://loomprotocol.com/"
repository = "https://github.com/loomprotocol/silk"
Expand Down Expand Up @@ -53,3 +53,4 @@ serde_json = "1.0.10"
ring = "0.12.1"
untrusted = "0.5.1"
bincode = "1.0.0"
chrono = { version = "0.4.0", features = ["serde"] }
182 changes: 176 additions & 6 deletions src/accountant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
use hash::Hash;
use entry::Entry;
use event::Event;
use transaction::Transaction;
use transaction::{Condition, Transaction};
use signature::{KeyPair, PublicKey, Signature};
use mint::Mint;
use historian::{reserve_signature, Historian};
use std::sync::mpsc::SendError;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::result;
use chrono::prelude::*;

#[derive(Debug, PartialEq, Eq)]
pub enum AccountingError {
Expand All @@ -28,6 +29,9 @@ pub struct Accountant {
pub balances: HashMap<PublicKey, i64>,
pub first_id: Hash,
pub last_id: Hash,
pending: HashMap<Signature, Transaction<i64>>,
time_sources: HashSet<PublicKey>,
last_time: DateTime<Utc>,
}

impl Accountant {
Expand All @@ -48,6 +52,9 @@ impl Accountant {
balances: HashMap::new(),
first_id: start_hash,
last_id: start_hash,
pending: HashMap::new(),
time_sources: HashSet::new(),
last_time: Utc.timestamp(0, 0),
};

// The second item in the log is a special transaction where the to and from
Expand Down Expand Up @@ -94,6 +101,24 @@ impl Accountant {
Ok(())
}

/// Commit funds to the 'to' party.
fn complete_transaction(self: &mut Self, tr: &Transaction<i64>) {
if self.balances.contains_key(&tr.to) {
if let Some(x) = self.balances.get_mut(&tr.to) {
*x += tr.asset;
}
} else {
self.balances.insert(tr.to, tr.asset);
}
}

/// Return funds to the 'from' party.
fn cancel_transaction(self: &mut Self, tr: &Transaction<i64>) {
if let Some(x) = self.balances.get_mut(&tr.from) {
*x += tr.asset;
}
}

fn process_verified_transaction(
self: &mut Self,
tr: &Transaction<i64>,
Expand All @@ -103,18 +128,97 @@ impl Accountant {
return Err(AccountingError::InvalidTransferSignature);
}

if !tr.unless_any.is_empty() {
// TODO: Check to see if the transaction is expired.
}

if !Self::is_deposit(allow_deposits, &tr.from, &tr.to) {
if let Some(x) = self.balances.get_mut(&tr.from) {
*x -= tr.asset;
}
}

if self.balances.contains_key(&tr.to) {
if let Some(x) = self.balances.get_mut(&tr.to) {
*x += tr.asset;
if !tr.if_all.is_empty() {
self.pending.insert(tr.sig, tr.clone());
return Ok(());
}

self.complete_transaction(tr);
Ok(())
}

fn process_verified_sig(&mut self, from: PublicKey, tx_sig: Signature) -> Result<()> {
let mut cancel = false;
if let Some(tr) = self.pending.get(&tx_sig) {
// Cancel:
// if Signature(from) is in unless_any, return funds to tx.from, and remove the tx from this map.

// TODO: Use find().
for cond in &tr.unless_any {
if let Condition::Signature(pubkey) = *cond {
if from == pubkey {
cancel = true;
break;
}
}
}
}

if cancel {
if let Some(tr) = self.pending.remove(&tx_sig) {
self.cancel_transaction(&tr);
}
}

// Process Multisig:
// otherwise, if "Signature(from) is in if_all, remove it. If that causes that list
// to be empty, add the asset to to, and remove the tx from this map.
Ok(())
}

fn process_verified_timestamp(&mut 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 == Utc.timestamp(0, 0) {
self.time_sources.insert(from);
}

if self.time_sources.contains(&from) {
if dt > self.last_time {
self.last_time = dt;
}
} else {
self.balances.insert(tr.to, tr.asset);
return Ok(());
}
// TODO: Lookup pending Transaction waiting on time, signed by a whitelisted PublicKey.

// Expire:
// if a Timestamp after this DateTime is in unless_any, return funds to tx.from,
// and remove the tx from this map.

// Check to see if any timelocked transactions can be completed.
let mut completed = vec![];
for (key, tr) in &self.pending {
for cond in &tr.if_all {
if let Condition::Timestamp(dt) = *cond {
if self.last_time >= dt {
if tr.if_all.len() == 1 {
completed.push(*key);
}
}
}
}
// TODO: Add this in once we start removing constraints
//if tr.if_all.is_empty() {
// // TODO: Remove tr from pending
// self.complete_transaction(tr);
//}
}

for key in completed {
if let Some(tr) = self.pending.remove(&key) {
self.complete_transaction(&tr);
}
}

Ok(())
Expand All @@ -124,6 +228,8 @@ impl Accountant {
match *event {
Event::Tick => Ok(()),
Event::Transaction(ref tr) => self.process_verified_transaction(tr, allow_deposits),
Event::Signature { from, tx_sig, .. } => self.process_verified_sig(from, tx_sig),
Event::Timestamp { from, dt, .. } => self.process_verified_timestamp(from, dt),
}
}

Expand All @@ -138,6 +244,18 @@ impl Accountant {
self.process_transaction(tr).map(|_| sig)
}

pub fn transfer_on_date(
self: &mut Self,
n: i64,
keypair: &KeyPair,
to: PublicKey,
dt: DateTime<Utc>,
) -> Result<Signature> {
let tr = Transaction::new_on_date(keypair, to, dt, n, self.last_id);
let sig = tr.sig;
self.process_transaction(tr).map(|_| sig)
}

pub fn get_balance(self: &Self, pubkey: &PublicKey) -> Option<i64> {
self.balances.get(pubkey).map(|x| *x)
}
Expand Down Expand Up @@ -204,4 +322,56 @@ mod tests {
ExitReason::RecvDisconnected
);
}

#[test]
fn test_transfer_on_date() {
let alice = Mint::new(1);
let mut acc = Accountant::new(&alice, Some(2));
let alice_keypair = alice.keypair();
let bob_pubkey = KeyPair::new().pubkey();
let dt = Utc::now();
acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt)
.unwrap();

// Alice's balance will be zero because all funds are locked up.
assert_eq!(acc.get_balance(&alice.pubkey()), Some(0));

// Bob's balance will be None because the funds have not been
// sent.
assert_eq!(acc.get_balance(&bob_pubkey), None);

// Now, acknowledge the time in the condition occurred and
// that bob's funds are now available.
acc.process_verified_timestamp(alice.pubkey(), dt).unwrap();
assert_eq!(acc.get_balance(&bob_pubkey), Some(1));

acc.process_verified_timestamp(alice.pubkey(), dt).unwrap(); // <-- Attack! Attempt to process completed transaction.
assert_ne!(acc.get_balance(&bob_pubkey), Some(2));
}

#[test]
fn test_cancel_transfer() {
let alice = Mint::new(1);
let mut acc = Accountant::new(&alice, Some(2));
let alice_keypair = alice.keypair();
let bob_pubkey = KeyPair::new().pubkey();
let dt = Utc::now();
let sig = acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt)
.unwrap();

// Alice's balance will be zero because all funds are locked up.
assert_eq!(acc.get_balance(&alice.pubkey()), Some(0));

// Bob's balance will be None because the funds have not been
// sent.
assert_eq!(acc.get_balance(&bob_pubkey), None);

// Now, cancel the trancaction. Alice gets her funds back, Bob never sees them.
acc.process_verified_sig(alice.pubkey(), sig).unwrap();
assert_eq!(acc.get_balance(&alice.pubkey()), Some(1));
assert_eq!(acc.get_balance(&bob_pubkey), None);

acc.process_verified_sig(alice.pubkey(), sig).unwrap(); // <-- Attack! Attempt to cancel completed transaction.
assert_ne!(acc.get_balance(&alice.pubkey()), Some(2));
}
}
29 changes: 28 additions & 1 deletion src/event.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! The `event` crate provides the data structures for log events.

use signature::Signature;
use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil};
use transaction::Transaction;
use chrono::prelude::*;
use bincode::serialize;

/// When 'event' is Tick, the event represents a simple clock tick, and exists for the
/// sole purpose of improving the performance of event log verification. A tick can
Expand All @@ -12,20 +14,45 @@ use transaction::Transaction;
pub enum Event {
Tick,
Transaction(Transaction<i64>),
Signature {
from: PublicKey,
tx_sig: Signature,
sig: Signature,
},
Timestamp {
from: PublicKey,
dt: DateTime<Utc>,
sig: Signature,
},
}

impl Event {
pub fn new_timestamp(from: &KeyPair, dt: DateTime<Utc>) -> Self {
let sign_data = serialize(&dt).unwrap();
let sig = Signature::clone_from_slice(from.sign(&sign_data).as_ref());
Event::Timestamp {
from: from.pubkey(),
dt,
sig,
}
}

// TODO: Rename this to transaction_signature().
pub fn get_signature(&self) -> Option<Signature> {
match *self {
Event::Tick => None,
Event::Transaction(ref tr) => Some(tr.sig),
Event::Signature { .. } => None,
Event::Timestamp { .. } => None,
}
}

pub fn verify(&self) -> bool {
match *self {
Event::Tick => true,
Event::Transaction(ref tr) => tr.verify(),
Event::Signature { from, tx_sig, sig } => sig.verify(&from, &tx_sig),
Event::Timestamp { from, dt, sig } => sig.verify(&from, &serialize(&dt).unwrap()),
}
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod accountant;
pub mod accountant_skel;
pub mod accountant_stub;
extern crate bincode;
extern crate chrono;
extern crate generic_array;
extern crate rayon;
extern crate ring;
Expand Down
14 changes: 11 additions & 3 deletions src/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,21 @@ use untrusted::Input;
#[derive(Serialize, Deserialize, Debug)]
pub struct Mint {
pub pkcs8: Vec<u8>,
pubkey: PublicKey,
pub tokens: i64,
}

impl Mint {
pub fn new(tokens: i64) -> Self {
let rnd = SystemRandom::new();
let pkcs8 = KeyPair::generate_pkcs8(&rnd).unwrap().to_vec();
Mint { pkcs8, tokens }
let keypair = KeyPair::from_pkcs8(Input::from(&pkcs8)).unwrap();
let pubkey = keypair.pubkey();
Mint {
pkcs8,
pubkey,
tokens,
}
}

pub fn seed(&self) -> Hash {
Expand All @@ -31,11 +38,12 @@ impl Mint {
}

pub fn pubkey(&self) -> PublicKey {
self.keypair().pubkey()
self.pubkey
}

pub fn create_events(&self) -> Vec<Event> {
let tr = Transaction::new(&self.keypair(), self.pubkey(), self.tokens, self.seed());
let keypair = self.keypair();
let tr = Transaction::new(&keypair, self.pubkey(), self.tokens, self.seed());
vec![Event::Tick, Event::Transaction(tr)]
}

Expand Down
Loading