use super::{node_state::RelocationTrigger, NodeState, SectionKeysProvider};
use crate::{
dbcs::DbcReason,
network_knowledge::{section_keys::build_spent_proof_share, Error, MyNodeInfo, MIN_ADULT_AGE},
types::{keys::ed25519, NodeId},
SectionAuthorityProvider,
};
use eyre::{eyre, Context, ContextCompat, Result};
use sn_consensus::{Ballot, Consensus, Decision, Proposition, Vote, VoteResponse};
use sn_dbc::{
get_blinded_amounts_from_transaction, BlindedAmount, Dbc, DbcTransaction, Owner, OwnerOnce,
Token, TransactionBuilder,
};
use std::{
cell::Cell,
collections::{BTreeMap, BTreeSet},
fmt,
net::SocketAddr,
};
use xor_name::Prefix;
pub fn prefix(s: &str) -> Prefix {
s.parse().expect("Failed to parse prefix")
}
pub fn gen_addr() -> SocketAddr {
thread_local! {
static NEXT_PORT: Cell<u16> = Cell::new(1000);
}
let port = NEXT_PORT.with(|cell| cell.replace(cell.get().wrapping_add(1)));
([192, 0, 2, 0], port).into()
}
pub fn gen_node_id(age: u8) -> NodeId {
let name = ed25519::gen_name_with_age(age);
NodeId::new(name, gen_addr())
}
pub fn gen_node_id_in_prefix(age: u8, prefix: Prefix) -> NodeId {
let name = ed25519::gen_name_with_age(age);
NodeId::new(prefix.substituted_in(name), gen_addr())
}
pub fn gen_info(age: u8, prefix: Option<Prefix>) -> MyNodeInfo {
MyNodeInfo::new(
ed25519::gen_keypair(&prefix.unwrap_or_default().range_inclusive(), age),
gen_addr(),
)
}
pub fn gen_node_infos(
prefix: &Prefix,
elders: usize,
adults: usize,
elder_age_pattern: Option<&[u8]>,
adult_age_pattern: Option<&[u8]>,
) -> (Vec<MyNodeInfo>, Vec<MyNodeInfo>) {
let elder_age_pattern = expand_age_pattern(elder_age_pattern, elders);
let adult_age_pattern = expand_age_pattern(adult_age_pattern, adults);
let elder_nodes = (0..elders)
.map(|idx| gen_info(elder_age_pattern[idx], Some(*prefix)))
.collect();
let adult_nodes = (0..adults)
.map(|idx| gen_info(adult_age_pattern[idx], Some(*prefix)))
.collect();
(elder_nodes, adult_nodes)
}
pub fn expand_age_pattern(age_pattern: Option<&[u8]>, len: usize) -> Vec<u8> {
if let Some(pattern) = age_pattern {
if pattern.is_empty() {
vec![MIN_ADULT_AGE; len]
} else if pattern.len() < len {
let last_element = pattern[pattern.len() - 1];
let mut op_pattern = vec![last_element; len - pattern.len()];
op_pattern.extend_from_slice(pattern);
op_pattern
} else {
Vec::from(pattern)
}
} else {
vec![MIN_ADULT_AGE; len]
}
}
pub fn section_decision<P: Proposition>(
secret_key_set: &bls::SecretKeySet,
gen: u64,
proposal: P,
) -> Result<Decision<P>> {
let n = secret_key_set.threshold() + 1;
let mut nodes = Vec::from_iter((1..=n).map(|idx| {
let secret = (idx as u8, secret_key_set.secret_key_share(idx));
Consensus::from(secret, secret_key_set.public_keys(), n)
}));
let first_vote = nodes[0]
.sign_vote(Vote {
gen,
ballot: Ballot::Propose(proposal),
faults: Default::default(),
})
.wrap_err("Failed to sign first vote")?;
let mut votes = vec![nodes[0]
.cast_vote(first_vote)
.wrap_err("Failed to cast vote")?];
while let Some(vote) = votes.pop() {
for node in &mut nodes {
match node
.handle_signed_vote(vote.clone())
.wrap_err("Failed to handle vote")?
{
VoteResponse::WaitingForMoreVotes => (),
VoteResponse::Broadcast(vote) => votes.push(vote),
}
}
}
assert_eq!(
BTreeSet::from_iter(nodes.iter().map(|n| {
if let Some(d) = n.decision.clone() {
d.proposals
} else {
BTreeMap::new()
}
}))
.len(),
1
);
nodes[0]
.decision
.clone()
.wrap_err("We should have seen a decision, this is a bug")
}
struct FakeProofKeyVerifier {}
impl sn_dbc::SpentProofKeyVerifier for FakeProofKeyVerifier {
type Error = Error;
fn verify_known_key(&self, _key: &bls::PublicKey) -> std::result::Result<(), Error> {
Ok(())
}
}
pub fn reissue_dbc(
input: &Dbc,
amount: u64,
output_owner_sk: &bls::SecretKey,
sap: &SectionAuthorityProvider,
section_keys_provider: &SectionKeysProvider,
) -> Result<Dbc> {
let output_amount = Token::from_nano(amount);
let input_amount = Token::from_nano(input.revealed_amount_bearer()?.value());
let change_amount = input_amount
.checked_sub(output_amount)
.ok_or_else(|| eyre!("The input amount minus the amount must evaluate to a valid value"))?;
let mut rng = rand::thread_rng();
let output_owner = Owner::from(output_owner_sk.clone());
let mut dbc_builder = TransactionBuilder::default()
.add_input_dbc_bearer(input)?
.add_output_by_amount(
output_amount,
OwnerOnce::from_owner_base(output_owner, &mut rng),
)
.add_output_by_amount(
change_amount,
OwnerOnce::from_owner_base(input.owner_base().clone(), &mut rng),
)
.build(rng)?;
for (public_key, tx) in dbc_builder.inputs() {
let blinded_amounts = get_blinded_amounts_from_transaction(
&tx,
&input.inputs_spent_proofs,
&input.inputs_spent_transactions,
)?;
let blinded_amount: BlindedAmount = blinded_amounts
.into_iter()
.find(|(k, _c)| k == &public_key)
.map(|(_k, c)| c)
.ok_or_else(|| {
eyre!("Found no blinded amount for Tx input with pubkey: {public_key:?}")
})?;
let spent_proof_share = build_spent_proof_share(
&public_key,
&tx,
DbcReason::none(),
sap,
section_keys_provider,
blinded_amount,
)?;
dbc_builder = dbc_builder
.add_spent_proof_share(spent_proof_share)
.add_spent_transaction(tx);
}
let verifier = FakeProofKeyVerifier {};
let output_dbcs = dbc_builder.build(&verifier)?;
let (output_dbc, ..) = output_dbcs
.into_iter()
.next()
.ok_or_else(|| eyre!("At least one output DBC should have been generated"))?;
Ok(output_dbc)
}
pub fn get_input_dbc_spend_info(
input: &Dbc,
amount: u64,
output_owner_sk: &bls::SecretKey,
) -> Result<(bls::PublicKey, DbcTransaction)> {
let output_amount = Token::from_nano(amount);
let input_amount = Token::from_nano(input.revealed_amount_bearer()?.value());
let change_amount = input_amount
.checked_sub(output_amount)
.ok_or_else(|| eyre!("The input amount minus the amount must evaluate to a valid value"))?;
let mut rng = rand::thread_rng();
let output_owner = Owner::from(output_owner_sk.clone());
let dbc_builder = TransactionBuilder::default()
.add_input_dbc_bearer(input)?
.add_output_by_amount(
output_amount,
OwnerOnce::from_owner_base(output_owner, &mut rng),
)
.add_output_by_amount(
change_amount,
OwnerOnce::from_owner_base(input.owner_base().clone(), &mut rng),
)
.build(rng)?;
let inputs = dbc_builder.inputs();
let first = inputs
.first()
.ok_or_else(|| eyre!("There must be at least one input on the transaction"))?;
Ok(first.clone())
}
pub fn assert_lists<I, J, K>(a: I, b: J)
where
K: fmt::Debug + Eq,
I: IntoIterator<Item = K>,
J: IntoIterator<Item = K>,
{
let vec1: Vec<_> = a.into_iter().collect();
let mut vec2: Vec<_> = b.into_iter().collect();
assert_eq!(vec1.len(), vec2.len());
for item1 in &vec1 {
let idx2 = vec2
.iter()
.position(|item2| item1 == item2)
.expect("Item not found in second list");
vec2.swap_remove(idx2);
}
assert_eq!(vec2.len(), 0);
}
pub fn try_create_relocation_trigger(
node_id: NodeId,
sk_set: &bls::SecretKeySet,
gen: u64,
age: u8,
) -> Result<Option<(RelocationTrigger, Decision<NodeState>)>> {
use super::relocation_check;
let node_state = NodeState::joined(node_id, None);
let decision = section_decision(sk_set, gen, node_state)?;
let relocation_trigger = RelocationTrigger::new(decision.clone());
let churn_id = relocation_trigger.churn_id();
if relocation_check(age, &churn_id) && !relocation_check(age + 1, &churn_id) {
Ok(Some((relocation_trigger, decision)))
} else {
Ok(None)
}
}
pub fn create_relocation_trigger(
sk_set: &bls::SecretKeySet,
gen: u64,
age: u8,
) -> Result<(RelocationTrigger, Decision<NodeState>)> {
loop {
let node_id = gen_node_id(MIN_ADULT_AGE);
if let Some((trigger, decision)) = try_create_relocation_trigger(node_id, sk_set, gen, age)?
{
return Ok((trigger, decision));
}
}
}