#![doc(
html_logo_url = "https://raw.githubusercontent.com/maidsafe/QA/master/Images/maidsafe_logo.png",
html_favicon_url = "https://maidsafe.net/img/favicon.ico",
test(attr(forbid(warnings)))
)]
#![forbid(unsafe_code)]
#![warn(
missing_docs,
trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
unused_results
)]
mod actor;
mod error;
mod test_utils;
mod wallet;
mod wallet_replica;
pub use self::{
actor::Actor as TransferActor, error::Error, wallet::Wallet, wallet_replica::WalletReplica,
};
use serde::{Deserialize, Serialize};
use sn_data_types::{
CreditId, DebitId, PublicKey, SignedCredit, SignedDebit, Token, TransferAgreementProof,
TransferValidated,
};
use std::collections::HashSet;
type Result<T> = std::result::Result<T, Error>;
type Outcome<T> = Result<Option<T>>;
trait TernaryResult<T> {
fn success(item: T) -> Self;
fn no_change() -> Self;
fn rejected(error: Error) -> Self;
}
impl<T> TernaryResult<T> for Outcome<T> {
fn success(item: T) -> Self {
Ok(Some(item))
}
fn no_change() -> Self {
Ok(None)
}
fn rejected(error: Error) -> Self {
Err(error)
}
}
pub trait ReplicaValidator {
fn is_valid(&self, section: PublicKey) -> bool;
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub enum ActorEvent {
TransferInitiated(TransferInitiated),
TransferValidationReceived(TransferValidationReceived),
TransferRegistrationSent(TransferRegistrationSent),
TransfersSynched(TransfersSynched),
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub struct TransfersSynched {
id: PublicKey,
balance: Token,
debit_version: u64,
credit_ids: HashSet<CreditId>,
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub struct TransferInitiated {
pub signed_debit: SignedDebit,
pub signed_credit: SignedCredit,
}
impl TransferInitiated {
pub fn id(&self) -> DebitId {
self.signed_debit.id()
}
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub struct TransferValidationReceived {
validation: TransferValidated,
pub proof: Option<TransferAgreementProof>,
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub struct TransferRegistrationSent {
transfer_proof: TransferAgreementProof,
}
#[allow(unused)]
mod test {
use crate::{
actor::Actor, test_utils, test_utils::*, wallet, wallet_replica::WalletReplica, ActorEvent,
Error, ReplicaValidator, Result, TransferInitiated, Wallet,
};
use crdts::{
quickcheck::{quickcheck, TestResult},
Dot,
};
use sn_data_types::{
ActorHistory, Credit, CreditAgreementProof, CreditId, Debit, Keypair, OwnerType, PublicKey,
ReplicaEvent, SignatureShare, SignedCredit, SignedDebit, SignedTransfer, Token, Transfer,
TransferAgreementProof,
};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use threshold_crypto::{PublicKeySet, PublicKeyShare, SecretKey, SecretKeySet, SecretKeyShare};
macro_rules! hashmap {
($( $key: expr => $val: expr ),*) => {{
let mut map = ::std::collections::HashMap::new();
$( let _ = map.insert($key, $val); )*
map
}}
}
#[test]
fn basic_transfer() {
let _ = transfer_between_actors(100, 10, 3);
}
#[test]
fn synching() -> Result<()> {
let section_count = 1;
let replicas_per_section = 1;
let section_configs = vec![vec![]];
let Network {
genesis_credit,
mut sections,
mut actors,
} = setup_new_network(section_count, replicas_per_section, section_configs)?;
let genesis_key = genesis_credit.recipient();
let genesis_elder = &mut sections.remove(0).elders.remove(0);
let past_key = Ok(PublicKey::Bls(
genesis_elder.signing.replicas_pk_set().public_key(),
));
let wallet_replica = match genesis_elder.replicas.get_mut(&genesis_key) {
Some(w) => w,
None => panic!("Failed the test; no such wallet."),
};
let _ = wallet_replica
.genesis(&genesis_credit, || past_key)?
.ok_or(Error::UnexpectedOutcome)?;
let event = ReplicaEvent::TransferPropagated(sn_data_types::TransferPropagated {
credit_proof: genesis_credit.clone(),
crediting_replica_keys: PublicKey::Bls(
genesis_elder.signing.replicas_pk_set().public_key(),
),
crediting_replica_sig: genesis_elder.signing.sign_credit_proof(&genesis_credit)?,
});
wallet_replica.apply(event)?;
println!("Finding genesis actor by id: {}", genesis_key);
let mut actor_balance = None;
for actor in actors.iter_mut() {
println!("Actor id: {}", actor.actor.id());
if actor.actor.id() == genesis_key {
println!("Found actor!");
if let Some(synched_event) = actor.actor.from_history(ActorHistory {
credits: vec![genesis_credit.clone()],
debits: vec![],
})? {
actor
.actor
.apply(ActorEvent::TransfersSynched(synched_event))?;
actor_balance = Some(actor.actor.balance());
}
break;
}
}
let balance = wallet_replica.balance();
assert_eq!(genesis_credit.amount(), balance);
assert_eq!(Some(balance), actor_balance);
Ok(())
}
#[test]
fn can_start_with_genesis() -> Result<()> {
let section_count = 1;
let replicas_per_section = 1;
let section_configs = vec![vec![u32::MAX as u64]];
let Network {
genesis_credit,
mut sections,
..
} = setup_new_network(section_count, replicas_per_section, section_configs)?;
let genesis_key = genesis_credit.recipient();
let genesis_elder = &mut sections.remove(0).elders.remove(0);
let past_key = Ok(PublicKey::Bls(
genesis_elder.signing.replicas_pk_set().public_key(),
));
let wallet_replica = match genesis_elder.replicas.get_mut(&genesis_key) {
Some(w) => w,
None => panic!("Failed the test; no such wallet."),
};
let _ = wallet_replica
.genesis(&genesis_credit, || past_key)?
.ok_or(Error::UnexpectedOutcome)?;
wallet_replica.apply(ReplicaEvent::TransferPropagated(
sn_data_types::TransferPropagated {
credit_proof: genesis_credit.clone(),
crediting_replica_keys: PublicKey::Bls(
genesis_elder.signing.replicas_pk_set().public_key(),
),
crediting_replica_sig: genesis_elder.signing.sign_credit_proof(&genesis_credit)?,
},
))?;
let balance = wallet_replica.balance();
assert_eq!(genesis_credit.amount(), balance);
Ok(())
}
#[test]
fn genesis_can_only_be_the_first() -> Result<()> {
let section_count = 1;
let replica_count = 1;
let section_configs = vec![vec![0]];
let Network {
genesis_credit,
mut sections,
..
} = setup_new_network(section_count, replica_count, section_configs)?;
let genesis_elder = &mut sections.remove(0).elders.remove(0);
let past_key = PublicKey::Bls(genesis_credit.replica_keys().public_key());
let wallet_replica = match genesis_elder.replicas.get_mut(&genesis_credit.recipient()) {
Some(w) => w,
None => panic!("Failed the test; no such wallet."),
};
let _ = wallet_replica
.genesis(&genesis_credit, || Ok(past_key))?
.ok_or(Error::UnexpectedOutcome)?;
wallet_replica.apply(ReplicaEvent::TransferPropagated(
sn_data_types::TransferPropagated {
credit_proof: genesis_credit.clone(),
crediting_replica_keys: PublicKey::Bls(
genesis_elder.signing.replicas_pk_set().public_key(),
),
crediting_replica_sig: genesis_elder.signing.sign_credit_proof(&genesis_credit)?,
},
))?;
let result = wallet_replica.genesis(&genesis_credit, || Ok(past_key));
match result {
Ok(_) => panic!("Should not be able to genesis again."),
Err(e) => assert_eq!(e, Error::InvalidOperation),
}
Ok(())
}
fn transfer_between_actors(
sender_balance: u64,
recipient_balance: u64,
replica_count: u8,
) -> TestResult {
match basic_transfer_between_actors(sender_balance, recipient_balance, replica_count) {
Ok(Some(_)) => TestResult::passed(),
Ok(None) => TestResult::discard(),
Err(_) => TestResult::failed(),
}
}
fn basic_transfer_between_actors(
sender_balance: u64,
recipient_balance: u64,
replica_count: u8,
) -> Result<Option<()>> {
if 0 == sender_balance || 2 >= replica_count {
return Ok(None);
}
let section_count = 2;
let sender_index = 0;
let recipient_index = 1;
let recipient_final = sender_balance + recipient_balance;
let section_configs = vec![vec![sender_balance], vec![recipient_balance]];
let Network {
mut actors,
mut sections,
..
} = setup_new_network(section_count, replica_count, section_configs)?;
let mut sender_section = sections.remove(0);
let mut recipient_section = sections.remove(0);
let mut sender = actors.remove(0);
let mut recipient = actors.remove(0);
let transfer = init_transfer(&mut sender, recipient.actor.id())?;
let debit_proof =
validate_at_sender_replicas(transfer, &mut sender)?.ok_or(Error::UnexpectedOutcome)?;
register_at_debiting_replicas(&debit_proof, &mut sender_section)?;
let events =
propagate_to_crediting_replicas(debit_proof.credit_proof(), &mut recipient_section);
synch(&mut recipient)?;
assert_balance(sender, Token::zero());
assert_balance(recipient, Token::from_nano(recipient_final));
Ok(Some(()))
}
fn assert_balance(actor: TestActor, amount: Token) {
assert!(actor.actor.balance() == amount);
actor.section.elders.iter().for_each(|elder| {
let wallet = match elder.replicas.get(&actor.actor.id()) {
Some(w) => w,
None => panic!("Failed the test; no such wallet."),
};
assert_eq!(wallet.balance(), amount)
});
}
fn init_transfer(sender: &mut TestActor, to: PublicKey) -> Result<TransferInitiated> {
let transfer = sender
.actor
.transfer(sender.actor.balance(), to, "asdf".to_string())?
.ok_or(Error::UnexpectedOutcome)?;
sender
.actor
.apply(ActorEvent::TransferInitiated(transfer.clone()))?;
Ok(transfer)
}
fn validate_at_sender_replicas(
transfer: TransferInitiated,
sender: &mut TestActor,
) -> Result<Option<TransferAgreementProof>> {
for elder in &mut sender.section.elders {
let wallet_replica = match elder.replicas.get_mut(&sender.actor.id()) {
Some(w) => w,
None => panic!("Failed the test; no such wallet."),
};
let _ = wallet_replica
.validate(&transfer.signed_debit, &transfer.signed_credit)?
.ok_or(Error::UnexpectedOutcome)?;
let signed_transfer = SignedTransfer {
debit: transfer.signed_debit.clone(),
credit: transfer.signed_credit.clone(),
};
let (replica_debit_sig, replica_credit_sig) =
elder.signing.sign_transfer(&signed_transfer)?;
let validation = sn_data_types::TransferValidated {
signed_credit: signed_transfer.credit,
signed_debit: signed_transfer.debit,
replica_debit_sig,
replica_credit_sig,
replicas: sender.section.id.clone(),
};
wallet_replica.apply(ReplicaEvent::TransferValidated(validation.clone()))?;
let validation_received = sender
.actor
.receive(validation)?
.ok_or(Error::UnexpectedOutcome)?;
sender.actor.apply(ActorEvent::TransferValidationReceived(
validation_received.clone(),
))?;
if let Some(proof) = validation_received.proof {
let registered = sender
.actor
.register(proof.clone())?
.ok_or(Error::UnexpectedOutcome)?;
sender
.actor
.apply(ActorEvent::TransferRegistrationSent(registered))?;
return Ok(Some(proof));
}
}
Ok(None)
}
fn register_at_debiting_replicas(
debit_proof: &TransferAgreementProof,
section: &mut Section,
) -> Result<()> {
for elder in &mut section.elders {
let wallet_replica = match elder.replicas.get_mut(&debit_proof.sender()) {
Some(w) => w,
None => panic!("Failed the test; no such wallet."),
};
let past_key = Ok(PublicKey::Bls(debit_proof.replica_keys().public_key()));
let registered = wallet_replica
.register(debit_proof, || past_key)?
.ok_or(Error::UnexpectedOutcome)?;
wallet_replica.apply(ReplicaEvent::TransferRegistered(registered))?;
}
Ok(())
}
fn propagate_to_crediting_replicas(
credit_proof: CreditAgreementProof,
section: &mut Section,
) -> Vec<ReplicaEvent> {
section
.elders
.iter_mut()
.map(|replica| {
let wallet_replica = match replica.replicas.get_mut(&credit_proof.recipient()) {
Some(w) => w,
None => panic!("Failed the test; no such wallet."),
};
let past_key = Ok(PublicKey::Bls(credit_proof.replica_keys().public_key()));
let _ = wallet_replica
.receive_propagated(&credit_proof, || past_key)?
.ok_or(Error::UnexpectedOutcome)?;
let crediting_replica_sig = replica.signing.sign_credit_proof(&credit_proof)?;
let propagated = sn_data_types::TransferPropagated {
credit_proof: credit_proof.clone(),
crediting_replica_keys: PublicKey::Bls(
replica.signing.replicas_pk_set().public_key(),
),
crediting_replica_sig,
};
wallet_replica.apply(ReplicaEvent::TransferPropagated(propagated.clone()))?;
Ok(ReplicaEvent::TransferPropagated(propagated))
})
.filter_map(|c: Result<ReplicaEvent>| match c {
Ok(c) => Some(c),
_ => None,
})
.collect()
}
fn synch(recipient: &mut TestActor) -> Result<()> {
let section = &recipient.section;
let wallet = section.elders[0]
.replicas
.get(&recipient.actor.id())
.ok_or(Error::UnexpectedOutcome)?;
let snapshot = wallet.wallet().ok_or(Error::UnexpectedOutcome)?;
let transfers = recipient
.actor
.synch(
snapshot.balance,
snapshot.debit_version,
snapshot.credit_ids,
)?
.ok_or(Error::UnexpectedOutcome)?;
recipient
.actor
.apply(ActorEvent::TransfersSynched(transfers))
}
fn get_random_pk() -> PublicKey {
PublicKey::from(SecretKey::random().public_key())
}
fn find_group(index: u8, sections: &[Section]) -> Option<Section> {
for section in sections {
if section.index == index {
return Some(section.clone());
}
}
None
}
fn setup_random_wallet(balance: u64, section: u8) -> Result<TestWallet> {
let mut rng = rand::thread_rng();
let keypair = Keypair::new_ed25519(&mut rng);
let recipient = keypair.public_key();
let owner = OwnerType::Single(recipient);
let mut wallet = Wallet::new(owner);
setup_wallet(balance, section, keypair, wallet)
}
fn setup_wallet(
balance: u64,
section: u8,
keypair: Keypair,
wallet: Wallet,
) -> Result<TestWallet> {
let mut wallet = wallet;
if balance > 0 {
let amount = Token::from_nano(balance);
let sender = Dot::new(get_random_pk(), 0);
let debit = Debit { id: sender, amount };
let credit = Credit {
id: debit.credit_id()?,
recipient: wallet.id().public_key(),
amount,
msg: "".to_string(),
};
let _ = wallet.apply_credit(credit)?;
}
Ok(TestWallet {
wallet,
keypair,
section,
})
}
fn setup_actor(wallet: TestWallet, sections: &[Section]) -> Result<TestActor> {
let section = find_group(wallet.section, sections).ok_or(Error::UnexpectedOutcome)?;
let actor = Actor::from_snapshot(
wallet.wallet,
wallet.keypair,
section.id.clone(),
Validator {},
);
Ok(TestActor { actor, section })
}
fn setup_section_keys(group_count: u8, replica_count: u8) -> HashMap<u8, SectionKeys> {
let mut rng = rand::thread_rng();
let mut groups = HashMap::new();
for i in 0..group_count {
let threshold = std::cmp::max(1, 2 * replica_count / 3) - 1;
let bls_secret_key = SecretKeySet::random(threshold as usize, &mut rng);
let peers = bls_secret_key.public_keys();
let mut shares = vec![];
for j in 0..replica_count {
let share = bls_secret_key.secret_key_share(j as usize);
shares.push((share, j as usize));
}
let _ = groups.insert(
i,
SectionKeys {
index: i,
id: peers,
keys: shares,
},
);
}
groups
}
fn setup_new_network(
section_count: u8,
replicas_per_section: u8,
section_configs: Vec<Vec<u64>>,
) -> Result<Network> {
let mut wallets_in_genesis_section = vec![];
let genesis_index = 0;
let mut section_configs = section_configs;
let mut genesis_section_configs = section_configs.remove(genesis_index);
for balance in genesis_section_configs {
let wallet = setup_random_wallet(balance, genesis_index as u8)?;
let _ = wallets_in_genesis_section.push(wallet);
}
let ((genesis_credit, genesis_wallet), genesis_section) =
setup_genesis_section(replicas_per_section, wallets_in_genesis_section.clone())?;
let _ = wallets_in_genesis_section.insert(0, genesis_wallet);
let mut all_sections = vec![genesis_section];
let mut other_section_wallets = vec![];
if section_count > 1 {
for (section_index, wallet_configs) in section_configs.iter().enumerate() {
let mut next_section_wallets = vec![];
for balance in wallet_configs {
let wallet = setup_random_wallet(*balance, (section_index + 1) as u8)?;
let _ = next_section_wallets.push(wallet.clone());
let _ = other_section_wallets.push(wallet);
}
let section_keys = generate_section_keys(section_index as u8, replicas_per_section);
let section =
setup_section(section_index as u8, section_keys, next_section_wallets);
all_sections.push(section);
}
}
let wallets = wallets_in_genesis_section
.into_iter()
.chain(other_section_wallets.into_iter())
.collect();
let actors = get_test_actors(wallets, all_sections.clone())?;
Ok(Network {
genesis_credit,
sections: all_sections,
actors,
})
}
fn get_test_actors(wallets: Vec<TestWallet>, sections: Vec<Section>) -> Result<Vec<TestActor>> {
let mut actors = vec![];
for wallet in wallets {
actors.push(setup_actor(wallet.clone(), §ions)?);
}
Ok(actors)
}
fn generate_section_keys(section_index: u8, replica_count: u8) -> SectionKeys {
let mut rng = rand::thread_rng();
let threshold = std::cmp::max(1, 2 * replica_count / 3) - 1;
let bls_secret_key = SecretKeySet::random(threshold as usize, &mut rng);
let peers = bls_secret_key.public_keys();
let mut shares = vec![];
for j in 0..replica_count {
let share = bls_secret_key.secret_key_share(j as usize);
shares.push((share, j as usize));
}
SectionKeys {
index: section_index,
id: peers,
keys: shares,
}
}
fn setup_section(
section_index: u8,
section_keys: SectionKeys,
wallets: Vec<TestWallet>,
) -> Section {
let mut elders = vec![];
let peer_replicas = section_keys.id.clone();
for (secret_key, key_index) in §ion_keys.keys {
let wallets = wallets.clone();
let mut wallet_replicas = hashmap![];
for wallet in wallets.into_iter() {
let wallet_id = wallet.wallet.id();
let pending_proposals = Default::default();
let wallet_replica = WalletReplica::from_snapshot(
wallet_id.clone(),
secret_key.public_key_share(),
*key_index,
peer_replicas.clone(),
wallet.wallet.clone(),
pending_proposals,
None,
);
let _ = wallet_replicas.insert(wallet_id.public_key(), wallet_replica);
}
elders.push(Elder {
id: secret_key.public_key_share(),
replicas: wallet_replicas,
signing: ReplicaSigning::new(secret_key.clone(), *key_index, peer_replicas.clone()),
});
}
Section {
index: section_index,
id: peer_replicas,
elders,
}
}
fn setup_genesis_section(
replica_count: u8,
wallets: Vec<TestWallet>,
) -> Result<((CreditAgreementProof, TestWallet), Section)> {
let threshold = (replica_count - 1) as usize;
let balance = u32::MAX as u64 * 1_000_000_000;
let mut rng = rand::thread_rng();
let bls_secret_key = SecretKeySet::random(threshold, &mut rng);
let peer_replicas = bls_secret_key.public_keys();
let id = PublicKey::Bls(peer_replicas.public_key());
let keypair = sn_data_types::Keypair::new_bls_share(
0,
bls_secret_key.secret_key_share(0),
peer_replicas.clone(),
);
let owner = OwnerType::Multi(peer_replicas.clone());
let empty_genesis_wallet = setup_wallet(0, 0, keypair, Wallet::new(owner))?;
let genesis_credit = get_multi_genesis(balance, id, bls_secret_key.clone())?;
let mut wallets = wallets;
wallets.insert(0, empty_genesis_wallet.clone());
let mut elders = vec![];
for key_index in 0..replica_count as usize {
for wallet in &wallets {
let secret_key = bls_secret_key.secret_key_share(key_index);
let peer_replicas = bls_secret_key.public_keys();
let mut wallet_replicas = hashmap![];
let wallet_id = wallet.wallet.id();
let pending_proposals = Default::default();
let wallet_replica = WalletReplica::from_snapshot(
wallet_id.clone(),
secret_key.public_key_share(),
key_index,
peer_replicas.clone(),
wallet.wallet.clone(),
pending_proposals,
None,
);
let _ = wallet_replicas.insert(wallet_id.public_key(), wallet_replica);
elders.push(Elder {
id: secret_key.public_key_share(),
replicas: wallet_replicas,
signing: ReplicaSigning::new(
secret_key.clone(),
key_index,
peer_replicas.clone(),
),
});
}
}
Ok((
(genesis_credit, empty_genesis_wallet),
Section {
index: 0,
id: peer_replicas,
elders,
},
))
}
}