#[cfg(test)]
mod tests {
use crate::{
mock, random_derivation_index,
tests::{TinyInt, TinyVec},
Dbc, DerivedKey, Error, Hash, MainKey, Result, SignedSpend, Spend, Token,
TransactionBuilder,
};
use blsttc::SecretKey;
use quickcheck_macros::quickcheck;
use std::collections::{BTreeMap, BTreeSet};
use std::iter::FromIterator;
#[test]
fn issue_genesis() -> Result<(), Error> {
let (_spentbook_node, genesis_dbc, genesis, _token) =
mock::GenesisBuilder::init_genesis_single()?;
let verified = genesis_dbc.verify(&genesis.main_key);
assert!(verified.is_ok());
Ok(())
}
#[quickcheck]
fn prop_splitting_the_genesis_dbc(output_amounts: TinyVec<TinyInt>) -> Result<(), Error> {
let mut rng = crate::rng::from_seed([0u8; 32]);
let mut output_amounts =
Vec::from_iter(output_amounts.into_iter().map(TinyInt::coerce::<u64>));
output_amounts
.push(mock::GenesisMaterial::GENESIS_AMOUNT - output_amounts.iter().sum::<u64>());
let n_outputs = output_amounts.len();
let output_amount: u64 = output_amounts.iter().sum();
let (mut spentbook_node, genesis_dbc, genesis, _token) =
mock::GenesisBuilder::init_genesis_single()?;
let first_output_key_map: BTreeMap<_, _> = output_amounts
.iter()
.map(|amount| {
let main_key = MainKey::random_from_rng(&mut rng);
let derivation_index = random_derivation_index(&mut rng);
let dbc_id = main_key.public_address().new_dbc_id(&derivation_index);
(
dbc_id,
(main_key, derivation_index, Token::from_nano(*amount)),
)
})
.collect();
let derived_key = genesis_dbc.derived_key(&genesis.main_key).unwrap();
let dbc_builder = TransactionBuilder::default()
.add_input_dbc(&genesis_dbc, &derived_key)?
.add_outputs(first_output_key_map.values().map(
|(main_key, derivation_index, amount)| {
(*amount, main_key.public_address(), *derivation_index)
},
))
.build(Hash::default())?;
let check_error = |error: Error| -> Result<()> {
match error {
Error::InconsistentDbcTransaction => {
assert_eq!(n_outputs, 0);
Ok(())
}
_ => Err(error),
}
};
let tx = &dbc_builder.spent_tx;
for signed_spend in dbc_builder.signed_spends() {
match spentbook_node.log_spent(tx, signed_spend) {
Ok(s) => s,
Err(e) => return check_error(e),
};
}
let output_dbcs = dbc_builder.build()?;
for (dbc, output_token) in output_dbcs.iter() {
let (main_key, _, token) = first_output_key_map.get(&dbc.id()).unwrap();
let dbc_token = dbc.token()?;
assert_eq!(token, &dbc_token);
assert_eq!(dbc_token, *output_token);
assert!(dbc.verify(main_key).is_ok());
}
assert_eq!(
{
let mut sum: u64 = 0;
for (dbc, _) in output_dbcs.iter() {
sum += dbc.token()?.as_nano()
}
sum
},
output_amount
);
Ok(())
}
#[quickcheck]
fn prop_dbc_transaction_many_to_many(
input_amounts: TinyVec<TinyInt>,
output_amounts: TinyVec<TinyInt>,
invalid_signed_spends: TinyVec<TinyInt>,
) -> Result<(), Error> {
let mut rng = crate::rng::from_seed([0u8; 32]);
let mut first_input_amounts =
Vec::from_iter(input_amounts.into_iter().map(TinyInt::coerce::<u64>));
first_input_amounts
.push(mock::GenesisMaterial::GENESIS_AMOUNT - first_input_amounts.iter().sum::<u64>());
let mut first_output_amounts =
Vec::from_iter(output_amounts.into_iter().map(TinyInt::coerce::<u64>));
first_output_amounts
.push(mock::GenesisMaterial::GENESIS_AMOUNT - first_output_amounts.iter().sum::<u64>());
let invalid_signed_spends = BTreeSet::from_iter(
invalid_signed_spends
.into_iter()
.map(TinyInt::coerce::<usize>),
);
let (mut spentbook_node, genesis_dbc, genesis_material, _token) =
mock::GenesisBuilder::init_genesis_single()?;
let mut first_output_key_map: BTreeMap<_, _> = first_input_amounts
.iter()
.map(|amount| {
let main_key = MainKey::random_from_rng(&mut rng);
let derivation_index = random_derivation_index(&mut rng);
let dbc_id = main_key.public_address().new_dbc_id(&derivation_index);
(
dbc_id,
(main_key, derivation_index, Token::from_nano(*amount)),
)
})
.collect();
let derived_key = genesis_dbc.derived_key(&genesis_material.main_key).unwrap();
let dbc_builder = TransactionBuilder::default()
.add_input_dbc(&genesis_dbc, &derived_key)?
.add_outputs(first_output_key_map.values().map(
|(main_key, derivation_index, token)| {
(*token, main_key.public_address(), *derivation_index)
},
))
.build(Hash::default())?;
let check_tx_error = |error: Error| -> Result<()> {
match error {
Error::InconsistentDbcTransaction => {
assert!(first_input_amounts.is_empty());
Ok(())
}
_ => Err(error),
}
};
let tx1 = dbc_builder.spent_tx.clone();
for signed_spend in dbc_builder.signed_spends() {
match spentbook_node.log_spent_and_skip_tx_verification(&tx1, signed_spend) {
Ok(s) => s,
Err(e) => return check_tx_error(e),
};
}
let first_output_dbcs = dbc_builder.build()?;
let second_inputs_dbcs: Vec<(Dbc, DerivedKey)> = first_output_dbcs
.into_iter()
.map(|(dbc, _)| {
let (main_key, _, _) = first_output_key_map.remove(&dbc.id()).unwrap();
let derived_key = dbc.derived_key(&main_key).unwrap();
(dbc, derived_key)
})
.collect();
let second_inputs_dbcs_len = second_inputs_dbcs.len();
let second_output_key_map: BTreeMap<_, _> = first_output_amounts
.iter()
.map(|amount| {
let main_key = MainKey::random_from_rng(&mut rng);
let derivation_index = random_derivation_index(&mut rng);
let dbc_id = main_key.public_address().new_dbc_id(&derivation_index);
(
dbc_id,
(main_key, derivation_index, Token::from_nano(*amount)),
)
})
.collect();
let dbc_builder = TransactionBuilder::default()
.add_input_dbcs(&second_inputs_dbcs)?
.add_outputs(second_output_key_map.values().map(
|(main_key, derivation_index, token)| {
(*token, main_key.public_address(), *derivation_index)
},
))
.build(Hash::default())?;
let dbc_output_amounts = first_output_amounts.clone();
let output_total_amount: u64 = dbc_output_amounts.iter().sum();
assert_eq!(second_inputs_dbcs_len, dbc_builder.spent_tx.inputs.len());
assert_eq!(second_inputs_dbcs_len, dbc_builder.signed_spends().len());
let tx2 = dbc_builder.spent_tx.clone();
let check_error = |error: Error| -> Result<()> {
match error {
Error::SignedSpendInputLenMismatch { expected, .. } => {
assert!(!invalid_signed_spends.is_empty());
assert_eq!(second_inputs_dbcs_len, expected);
}
Error::SignedSpendInputIdMismatch => {
assert!(!invalid_signed_spends.is_empty());
}
Error::InconsistentDbcTransaction => {
if mock::GenesisMaterial::GENESIS_AMOUNT == output_total_amount {
assert!(first_output_amounts.is_empty());
assert_eq!(first_input_amounts.iter().sum::<u64>(), 0);
assert!(!first_input_amounts.is_empty());
}
}
Error::MissingTxInputs => {
assert_eq!(first_input_amounts.len(), 0);
}
Error::InvalidSpendSignature(dbc_id) => {
let idx = tx2
.inputs
.iter()
.position(|i| i.dbc_id() == dbc_id)
.unwrap();
assert!(invalid_signed_spends.contains(&idx));
}
_ => panic!("Unexpected err {:#?}", error),
}
Ok(())
};
let tx = &dbc_builder.spent_tx;
for (i, signed_spend) in dbc_builder.signed_spends().into_iter().enumerate() {
let is_invalid_signed_spend = invalid_signed_spends.contains(&i);
let _signed_spend = match i % 2 {
0 if is_invalid_signed_spend => {
continue;
}
1 if is_invalid_signed_spend => {
match spentbook_node.log_spent(tx, signed_spend) {
Ok(s) => s,
Err(e) => return check_error(e),
};
SignedSpend {
spend: Spend {
dbc_id: *signed_spend.dbc_id(),
spent_tx: signed_spend.spend.spent_tx.clone(),
reason: Hash::default(),
token: *signed_spend.token(),
dbc_creation_tx: tx1.clone(),
},
derived_key_sig: SecretKey::random().sign([0u8; 32]),
}
}
_ => {
match spentbook_node.log_spent(tx, signed_spend) {
Ok(()) => signed_spend.clone(),
Err(e) => return check_error(e),
}
}
};
}
let many_to_many_result = dbc_builder.build();
match many_to_many_result {
Ok(second_output_dbcs) => {
assert_eq!(mock::GenesisMaterial::GENESIS_AMOUNT, output_total_amount);
assert_eq!(
BTreeSet::from_iter(dbc_output_amounts.clone()),
BTreeSet::from_iter(first_output_amounts)
);
for (dbc, _) in second_output_dbcs.iter() {
let (main_key, _, _) = second_output_key_map.get(&dbc.id()).unwrap();
let dbc_confirm_result = dbc.verify(main_key);
assert!(dbc_confirm_result.is_ok());
}
assert_eq!(
second_output_dbcs
.iter()
.enumerate()
.map(|(idx, _dbc)| { dbc_output_amounts[idx] })
.sum::<u64>(),
output_total_amount
);
Ok(())
}
Err(err) => check_error(err),
}
}
}