Merge pull request #56 from garious/add-conditions
Add conditions to transactions
This commit is contained in:
		@@ -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"
 | 
			
		||||
@@ -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"] }
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
@@ -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 {
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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>,
 | 
			
		||||
@@ -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(())
 | 
			
		||||
@@ -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),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -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)
 | 
			
		||||
    }
 | 
			
		||||
@@ -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
									
								
								src/event.rs
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								src/event.rs
									
									
									
									
									
								
							@@ -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
 | 
			
		||||
@@ -12,13 +14,36 @@ 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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -26,6 +51,8 @@ impl Event {
 | 
			
		||||
        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()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								src/mint.rs
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								src/mint.rs
									
									
									
									
									
								
							@@ -12,6 +12,7 @@ use untrusted::Input;
 | 
			
		||||
#[derive(Serialize, Deserialize, Debug)]
 | 
			
		||||
pub struct Mint {
 | 
			
		||||
    pub pkcs8: Vec<u8>,
 | 
			
		||||
    pubkey: PublicKey,
 | 
			
		||||
    pub tokens: i64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -19,7 +20,13 @@ 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 {
 | 
			
		||||
@@ -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)]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,20 @@ use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil};
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
use bincode::serialize;
 | 
			
		||||
use hash::Hash;
 | 
			
		||||
use chrono::prelude::*;
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
 | 
			
		||||
pub enum Condition {
 | 
			
		||||
    Timestamp(DateTime<Utc>),
 | 
			
		||||
    Signature(PublicKey),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
 | 
			
		||||
pub struct Transaction<T> {
 | 
			
		||||
    pub from: PublicKey,
 | 
			
		||||
    pub to: PublicKey,
 | 
			
		||||
    pub if_all: Vec<Condition>,
 | 
			
		||||
    pub unless_any: Vec<Condition>,
 | 
			
		||||
    pub asset: T,
 | 
			
		||||
    pub last_id: Hash,
 | 
			
		||||
    pub sig: Signature,
 | 
			
		||||
@@ -19,6 +28,29 @@ impl<T: Serialize> Transaction<T> {
 | 
			
		||||
        let mut tr = Transaction {
 | 
			
		||||
            from: from_keypair.pubkey(),
 | 
			
		||||
            to,
 | 
			
		||||
            if_all: vec![],
 | 
			
		||||
            unless_any: vec![],
 | 
			
		||||
            asset,
 | 
			
		||||
            last_id,
 | 
			
		||||
            sig: Signature::default(),
 | 
			
		||||
        };
 | 
			
		||||
        tr.sign(from_keypair);
 | 
			
		||||
        tr
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn new_on_date(
 | 
			
		||||
        from_keypair: &KeyPair,
 | 
			
		||||
        to: PublicKey,
 | 
			
		||||
        dt: DateTime<Utc>,
 | 
			
		||||
        asset: T,
 | 
			
		||||
        last_id: Hash,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let from = from_keypair.pubkey();
 | 
			
		||||
        let mut tr = Transaction {
 | 
			
		||||
            from,
 | 
			
		||||
            to,
 | 
			
		||||
            if_all: vec![Condition::Timestamp(dt)],
 | 
			
		||||
            unless_any: vec![Condition::Signature(from)],
 | 
			
		||||
            asset,
 | 
			
		||||
            last_id,
 | 
			
		||||
            sig: Signature::default(),
 | 
			
		||||
@@ -28,7 +60,14 @@ impl<T: Serialize> Transaction<T> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_sign_data(&self) -> Vec<u8> {
 | 
			
		||||
        serialize(&(&self.from, &self.to, &self.asset, &self.last_id)).unwrap()
 | 
			
		||||
        serialize(&(
 | 
			
		||||
            &self.from,
 | 
			
		||||
            &self.to,
 | 
			
		||||
            &self.if_all,
 | 
			
		||||
            &self.unless_any,
 | 
			
		||||
            &self.asset,
 | 
			
		||||
            &self.last_id,
 | 
			
		||||
        )).unwrap()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn sign(&mut self, keypair: &KeyPair) {
 | 
			
		||||
@@ -72,6 +111,8 @@ mod tests {
 | 
			
		||||
        let claim0 = Transaction {
 | 
			
		||||
            from: Default::default(),
 | 
			
		||||
            to: Default::default(),
 | 
			
		||||
            if_all: Default::default(),
 | 
			
		||||
            unless_any: Default::default(),
 | 
			
		||||
            asset: 0u8,
 | 
			
		||||
            last_id: Default::default(),
 | 
			
		||||
            sig: Default::default(),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user