use super::{
wallet::Wallet, ActorEvent, Outcome, ReceivedCredit, ReplicaValidator, TernaryResult,
TransferInitiated, TransferRegistrationSent, TransferValidated, TransferValidationReceived,
TransfersSynched,
};
use crdts::Dot;
use itertools::Itertools;
use log::{debug, warn};
use sn_data_types::{
DebitAgreementProof, Error, Keypair, Money, PublicKey, ReplicaEvent, Result, Signature,
SignatureShare, SignedTransfer, Transfer, TransferId,
};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::sync::Arc;
use threshold_crypto::PublicKeySet;
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct SecretKeyShare {
pub index: usize,
pub secret_key: threshold_crypto::SecretKeyShare,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Actor<V: ReplicaValidator> {
id: PublicKey,
keypair: Arc<Keypair>,
wallet: Wallet,
next_expected_debit: u64,
accumulating_validations: HashMap<TransferId, HashSet<TransferValidated>>,
replicas: PublicKeySet,
replica_validator: V,
}
impl<V: ReplicaValidator> Actor<V> {
pub fn new(keypair: Arc<Keypair>, replicas: PublicKeySet, replica_validator: V) -> Actor<V> {
let id = keypair.public_key();
Actor {
id,
keypair,
replicas,
replica_validator,
wallet: Wallet::new(id),
next_expected_debit: 0,
accumulating_validations: Default::default(),
}
}
pub fn from_snapshot(
wallet: Wallet,
keypair: Arc<Keypair>,
replicas: PublicKeySet,
replica_validator: V,
) -> Actor<V> {
let id = keypair.public_key();
Actor {
id,
keypair,
replicas,
replica_validator,
wallet,
next_expected_debit: 0,
accumulating_validations: Default::default(),
}
}
pub fn id(&self) -> PublicKey {
self.id
}
pub fn credits_since(&self, index: usize) -> Vec<Transfer> {
self.wallet.credits_since(index)
}
pub fn debits_since(&self, index: usize) -> Vec<Transfer> {
self.wallet.debits_since(index)
}
pub fn balance(&self) -> Money {
self.wallet.balance()
}
pub fn transfer(&self, amount: Money, to: PublicKey) -> Outcome<TransferInitiated> {
if to == self.id {
return Outcome::rejected(Error::from("Sender and recipient are the same"));
}
let id = Dot::new(self.id, self.wallet.next_debit());
if self.next_expected_debit != self.wallet.next_debit() {
return Outcome::rejected(Error::from("Current pending debit has not been completed"));
}
if self.next_expected_debit != id.counter {
return Outcome::rejected(Error::from("Debit already proposed or out of order"));
}
if amount > self.balance() {
return Outcome::rejected(Error::InsufficientBalance);
}
if amount == Money::from_nano(0) {
return Outcome::rejected(Error::Unexpected(
"Cannot send zero-value transfers".to_string(),
));
}
let transfer = Transfer { id, to, amount };
match self.sign(&transfer) {
Ok(actor_signature) => {
let signed_transfer = SignedTransfer {
transfer,
actor_signature,
};
Outcome::success(TransferInitiated { signed_transfer })
}
Err(e) => Outcome::rejected(e),
}
}
pub fn receive(&self, validation: TransferValidated) -> Outcome<TransferValidationReceived> {
if self.verify(&validation).is_err() {
return Err(Error::InvalidSignature);
}
let signed_transfer = &validation.signed_transfer;
if self.id != signed_transfer.from() {
return Err(Error::from("Validation not intended for this actor")); }
if self.next_expected_debit != signed_transfer.id().counter + 1 {
return Err(Error::from("Out of order validation"));
}
if let Some(set) = self.accumulating_validations.get(&validation.id()) {
if set.contains(&validation) {
return Err(Error::from("Already received validation"));
}
} else {
return Err(Error::Unexpected(format!(
"No set found for TransferID: {:?}",
validation.id()
)));
}
let set = self.accumulating_validations.get(&validation.id()).unwrap();
let mut proof = None;
let quorum =
set.len() + 1 > self.replicas.threshold() && self.replicas == validation.replicas;
if quorum {
if let Ok(data) = bincode::serialize(&signed_transfer) {
let sig_shares: BTreeMap<_, _> = set
.iter()
.chain(vec![&validation])
.map(|v| v.replica_signature.clone())
.map(|s| (s.index, s.share))
.collect();
let sig = self.replicas.combine_signatures(&sig_shares).map_err(|_| {
Error::Unexpected(
"Could not aggregate with the given SignatureShares".to_string(),
)
})?;
if self.replicas.public_key().verify(&sig, data) {
proof = Some(DebitAgreementProof {
signed_transfer: signed_transfer.clone(),
debiting_replicas_sig: sn_data_types::Signature::Bls(sig),
replica_key: self.replicas.clone(),
});
} }
}
Outcome::success(TransferValidationReceived { validation, proof })
}
pub fn register(&self, debit_proof: DebitAgreementProof) -> Outcome<TransferRegistrationSent> {
if self.verify_debit_proof(&debit_proof).is_err() {
return Err(Error::InvalidSignature);
}
match self
.wallet
.is_sequential(&debit_proof.signed_transfer.transfer)
{
Ok(is_sequential) => {
if is_sequential {
Outcome::success(TransferRegistrationSent { debit_proof })
} else {
Err(Error::from("Non-sequential operation"))
}
}
Err(_) => {
warn!("Invalid operation in transfer actor.");
Err(Error::InvalidOperation)
} }
}
pub fn synch(&self, events: Vec<ReplicaEvent>) -> Outcome<TransfersSynched> {
let credits = self.validate_credits(&events);
let debits = self.validate_debits(events);
if !credits.is_empty() || !debits.is_empty() {
Outcome::success(TransfersSynched { credits, debits })
} else {
Err(Error::from("No credits or debits found to sync to actor"))
}
}
fn validate_credits(&self, events: &[ReplicaEvent]) -> Vec<ReceivedCredit> {
let valid_credits: Vec<_> = events
.iter()
.filter_map(|e| match e {
ReplicaEvent::TransferPropagated(e) => Some(e),
_ => None,
})
.unique_by(|e| e.id())
.map(|e| ReceivedCredit {
debit_proof: e.debit_proof.clone(),
debiting_replicas: e.debiting_replicas,
})
.filter(|_credit| {
#[cfg(feature = "simulated-payouts")]
return true;
#[cfg(not(feature = "simulated-payouts"))]
self.verify_credit_proof(_credit).is_ok()
})
.filter(|credit| self.id == credit.to())
.filter(|credit| !self.wallet.contains(&credit.id()))
.collect();
valid_credits
}
#[allow(clippy::explicit_counter_loop)]
fn validate_debits(&self, events: Vec<ReplicaEvent>) -> Vec<DebitAgreementProof> {
let mut debits: Vec<_> = events
.iter()
.filter_map(|e| match e {
ReplicaEvent::TransferRegistered(e) => Some(e),
_ => None,
})
.unique_by(|e| e.id())
.map(|e| &e.debit_proof)
.filter(|debit| self.id == debit.from())
.filter(|debit| debit.id().counter >= self.wallet.next_debit())
.filter(|debit| self.verify_debit_proof(debit).is_ok())
.collect();
debits.sort_by_key(|t| t.id().counter);
let mut iter = 0;
let mut valid_debits = vec![];
for out in debits {
let version = out.id().counter;
let expected_version = iter + self.wallet.next_debit();
if version != expected_version {
break; }
valid_debits.push(out.clone());
iter += 1;
}
valid_debits
}
pub fn apply(&mut self, event: ActorEvent) -> Result<()> {
debug!("Applying event {:?}", event);
match event {
ActorEvent::TransferInitiated(e) => {
self.next_expected_debit = e.id().counter + 1;
let _ = self.accumulating_validations.insert(e.id(), HashSet::new());
Ok(())
}
ActorEvent::TransferValidationReceived(e) => {
if e.proof.is_some() {
self.replicas = e.validation.replicas.clone();
}
match self.accumulating_validations.get_mut(&e.validation.id()) {
Some(set) => {
let _ = set.insert(e.validation);
}
None => return Err(Error::Unexpected(
"Could not find the expected transfer id among accumulating validations!"
.to_string(),
)),
}
Ok(())
}
ActorEvent::TransferRegistrationSent(e) => {
self.wallet.append(e.debit_proof.signed_transfer.transfer)?;
self.accumulating_validations.clear();
Ok(())
}
ActorEvent::TransfersSynched(e) => {
for credit in e.credits {
self.wallet
.append(credit.debit_proof.signed_transfer.transfer)?;
}
let any_debits = !e.debits.is_empty();
for proof in e.debits {
self.wallet.append(proof.signed_transfer.transfer)?;
}
if any_debits {
self.next_expected_debit = self.wallet.next_debit();
}
Ok(())
}
}
}
fn sign(&self, transfer: &Transfer) -> Result<Signature> {
match bincode::serialize(transfer) {
Err(_) => Err(Error::NetworkOther("Could not serialise transfer".into())),
Ok(data) => Ok(self.keypair.sign(&data)),
}
}
fn verify(&self, event: &TransferValidated) -> Result<()> {
let cmd = &event.signed_transfer;
if let error @ Err(_) = self.verify_is_our_transfer(cmd) {
return error;
}
self.verify_share(cmd, &event.replica_signature, &event.replicas)
}
fn verify_share<T: serde::Serialize>(
&self,
item: T,
replica_signature: &SignatureShare,
replicas: &PublicKeySet,
) -> Result<()> {
let sig_share = &replica_signature.share;
let share_index = replica_signature.index;
match bincode::serialize(&item) {
Err(_) => Err(Error::NetworkOther("Could not serialise item".into())),
Ok(data) => {
let verified = replicas
.public_key_share(share_index)
.verify(sig_share, data);
if verified {
Ok(())
} else {
Err(Error::InvalidSignature)
}
}
}
}
fn verify_debit_proof(&self, proof: &DebitAgreementProof) -> Result<()> {
let cmd = &proof.signed_transfer;
if let error @ Err(_) = self.verify_is_our_transfer(cmd) {
return error;
}
match bincode::serialize(&proof.signed_transfer) {
Err(_) => Err(Error::NetworkOther("Could not serialise transfer".into())),
Ok(data) => {
let public_key = sn_data_types::PublicKey::Bls(self.replicas.public_key());
public_key.verify(&proof.debiting_replicas_sig, &data)
}
}
}
#[cfg(not(feature = "simulated-payouts"))]
fn verify_credit_proof(&self, credit: &ReceivedCredit) -> Result<()> {
if !self.replica_validator.is_valid(credit.debiting_replicas) {
return Err(Error::InvalidSignature);
}
let proof = &credit.debit_proof;
match bincode::serialize(&proof.signed_transfer) {
Err(_) => Err(Error::NetworkOther("Could not serialise transfer".into())),
Ok(data) => credit
.debiting_replicas
.verify(&proof.debiting_replicas_sig, &data),
}
}
fn verify_is_our_transfer(&self, signed_transfer: &SignedTransfer) -> Result<()> {
match bincode::serialize(&signed_transfer.transfer) {
Err(_) => Err(Error::NetworkOther("Could not serialise transfer".into())),
Ok(data) => {
let actor_sig = self
.keypair
.public_key()
.verify(&signed_transfer.actor_signature, data);
if actor_sig.is_ok() {
Ok(())
} else {
Err(Error::InvalidSignature)
}
}
}
}
}
#[cfg(test)]
mod test {
use super::{
Actor, ActorEvent, ReplicaValidator, TransferInitiated, TransferRegistrationSent, Wallet,
};
use crdts::Dot;
use serde::Serialize;
use sn_data_types::{
DebitAgreementProof, Error, Keypair, Money, PublicKey, Result, Signature, SignatureShare,
Transfer, TransferValidated,
};
use std::collections::BTreeMap;
use std::sync::Arc;
use threshold_crypto::{SecretKey, SecretKeySet};
struct Validator {}
impl ReplicaValidator for Validator {
fn is_valid(&self, _replica_group: PublicKey) -> bool {
true
}
}
#[test]
fn creates_actor() -> Result<()> {
let (_actor, _sk_set) = get_actor_and_replicas_sk_set(10)?;
Ok(())
}
#[test]
fn initial_state_is_applied() -> Result<()> {
let initial_amount = 10;
let (actor, _sk_set) = get_actor_and_replicas_sk_set(initial_amount)?;
let credits = actor.credits_since(0);
let debits = actor.debits_since(0);
assert!(debits.is_empty());
assert_eq!(credits.len(), 1);
assert_eq!(credits[0].amount, Money::from_nano(initial_amount));
assert_eq!(actor.balance(), Money::from_nano(initial_amount));
Ok(())
}
#[test]
fn initiates_transfers() -> Result<()> {
let (actor, _sk_set) = get_actor_and_replicas_sk_set(10)?;
let debit = get_debit(&actor)?;
let mut actor = actor;
actor.apply(ActorEvent::TransferInitiated(debit))?;
Ok(())
}
#[test]
fn cannot_initiate_0_value_transfers() -> Result<()> {
let (actor, _sk_set) = get_actor_and_replicas_sk_set(10)?;
match actor.transfer(Money::from_nano(0), get_random_pk()) {
Ok(_) => Err(Error::from("Should not be able to send 0 value transfers")),
Err(error) => {
assert!(error
.to_string()
.contains("Cannot send zero-value transfers"));
Ok(())
}
}
}
#[test]
fn can_apply_completed_transfer() -> Result<()> {
let (actor, sk_set) = get_actor_and_replicas_sk_set(15)?;
let debit = get_debit(&actor)?;
let mut actor = actor;
actor.apply(ActorEvent::TransferInitiated(debit.clone()))?;
let transfer_event = get_transfer_registration_sent(debit, &sk_set)?;
actor.apply(ActorEvent::TransferRegistrationSent(transfer_event))?;
assert_eq!(Money::from_nano(5), actor.balance());
Ok(())
}
#[test]
fn can_apply_completed_transfers_in_succession() -> Result<()> {
let (actor, sk_set) = get_actor_and_replicas_sk_set(22)?;
let debit = get_debit(&actor)?;
let mut actor = actor;
actor.apply(ActorEvent::TransferInitiated(debit.clone()))?;
let transfer_event = get_transfer_registration_sent(debit, &sk_set)?;
actor.apply(ActorEvent::TransferRegistrationSent(transfer_event))?;
assert_eq!(Money::from_nano(12), actor.balance());
let debit2 = get_debit(&actor)?;
actor.apply(ActorEvent::TransferInitiated(debit2.clone()))?;
let transfer_event = get_transfer_registration_sent(debit2, &sk_set)?;
actor.apply(ActorEvent::TransferRegistrationSent(transfer_event))?;
assert_eq!(Money::from_nano(2), actor.balance()); Ok(())
}
#[allow(clippy::needless_range_loop)]
#[test]
fn can_return_proof_for_validated_transfers() -> Result<()> {
let (actor, sk_set) = get_actor_and_replicas_sk_set(22)?;
let debit = get_debit(&actor)?;
let mut actor = actor;
actor.apply(ActorEvent::TransferInitiated(debit.clone()))?;
let validations = get_transfer_validation_vec(debit, &sk_set)?;
for i in 0..7 {
let transfer_validation = actor.receive(validations[i].clone())?.unwrap();
if i < 1
{
assert_eq!(transfer_validation.clone().proof, None);
} else {
assert_ne!(transfer_validation.proof, None);
}
actor.apply(ActorEvent::TransferValidationReceived(
transfer_validation.clone(),
))?;
}
Ok(())
}
fn get_debit(actor: &Actor<Validator>) -> Result<TransferInitiated> {
let event = actor
.transfer(Money::from_nano(10), get_random_pk())?
.unwrap();
Ok(event)
}
fn try_serialize<T: Serialize>(value: T) -> Result<Vec<u8>> {
match bincode::serialize(&value) {
Ok(res) => Ok(res),
_ => Err(Error::from("serialization failed")),
}
}
fn get_transfer_validation_vec(
transfer: TransferInitiated,
sk_set: &SecretKeySet,
) -> Result<Vec<TransferValidated>> {
let signed_transfer = transfer.signed_transfer;
let serialized_signed_transfer = try_serialize(&signed_transfer)?;
let sk_shares: Vec<_> = (0..7).map(|i| sk_set.secret_key_share(i)).collect();
let pk_set = sk_set.public_keys();
let sig_shares: BTreeMap<_, _> = (0..7)
.map(|i| (i, sk_shares[i].sign(serialized_signed_transfer.clone())))
.collect();
let mut validated_transfers = vec![];
for (i, sig_share) in &sig_shares {
assert!(pk_set
.public_key_share(*i)
.verify(sig_share, serialized_signed_transfer.clone()));
validated_transfers.push(TransferValidated {
signed_transfer: signed_transfer.clone(),
replica_signature: SignatureShare {
index: *i,
share: sig_share.clone(),
},
replicas: pk_set.clone(),
})
}
Ok(validated_transfers)
}
fn get_transfer_registration_sent(
transfer: TransferInitiated,
sk_set: &SecretKeySet,
) -> Result<TransferRegistrationSent> {
let signed_transfer = transfer.signed_transfer.clone();
let serialized_signed_transfer = try_serialize(signed_transfer)?;
let sk_shares: Vec<_> = (0..6).map(|i| sk_set.secret_key_share(i)).collect();
let pk_set = sk_set.public_keys();
let sig_shares: BTreeMap<_, _> = (0..4)
.map(|i| (i, sk_shares[i].sign(serialized_signed_transfer.clone())))
.collect();
for (i, sig_share) in &sig_shares {
assert!(pk_set
.public_key_share(*i)
.verify(sig_share, serialized_signed_transfer.clone()));
}
let sig = match pk_set.combine_signatures(&sig_shares) {
Ok(s) => s,
_ => return Err(Error::from("invalid signature")),
};
assert!(pk_set.public_key().verify(&sig, serialized_signed_transfer));
let debiting_replicas_sig = Signature::Bls(sig);
let debit_agreement_proof = DebitAgreementProof {
signed_transfer: transfer.signed_transfer,
debiting_replicas_sig,
replica_key: pk_set,
};
Ok(TransferRegistrationSent {
debit_proof: debit_agreement_proof,
})
}
fn get_actor_and_replicas_sk_set(amount: u64) -> Result<(Actor<Validator>, SecretKeySet)> {
let mut rng = rand::thread_rng();
let keypair = Keypair::new_ed25519(&mut rng);
let client_pubkey = keypair.public_key();
let bls_secret_key = SecretKeySet::random(1, &mut rng);
let replicas_id = bls_secret_key.public_keys();
let balance = Money::from_nano(amount);
let sender = Dot::new(get_random_pk(), 0);
let transfer = get_transfer(sender, client_pubkey, balance);
let replica_validator = Validator {};
let mut wallet = Wallet::new(transfer.to);
wallet.append(transfer)?;
let actor = Actor::from_snapshot(wallet, Arc::new(keypair), replicas_id, replica_validator);
Ok((actor, bls_secret_key))
}
fn get_transfer(from: Dot<PublicKey>, to: PublicKey, amount: Money) -> Transfer {
Transfer {
id: from,
to,
amount,
}
}
#[allow(unused)]
fn get_random_dot() -> Dot<PublicKey> {
Dot::new(get_random_pk(), 0)
}
fn get_random_pk() -> PublicKey {
PublicKey::from(SecretKey::random().public_key())
}
}