use super::GenesisMaterial;
use crate::{
dbc_id::PublicAddress, transaction::DbcTransaction, DbcId, Error, Hash, Output, Result,
SignedSpend, Token,
};
use std::collections::{BTreeMap, HashMap};
#[derive(Debug, Clone)]
pub struct SpentbookNode {
pub id: PublicAddress,
pub transactions: HashMap<Hash, DbcTransaction>,
pub dbc_ids: BTreeMap<DbcId, Hash>,
pub outputs_by_input_id: BTreeMap<DbcId, Output>,
pub genesis: (DbcId, Token),
}
impl Default for SpentbookNode {
fn default() -> Self {
let genesis_material = GenesisMaterial::default();
let token = genesis_material.genesis_tx.0.inputs[0].token;
Self {
id: PublicAddress::new(blsttc::SecretKey::random().public_key()),
transactions: Default::default(),
dbc_ids: Default::default(),
outputs_by_input_id: Default::default(),
genesis: (genesis_material.input_dbc_id, token),
}
}
}
impl SpentbookNode {
pub fn iter(&self) -> impl Iterator<Item = (&DbcId, &DbcTransaction)> + '_ {
self.dbc_ids.iter().map(move |(k, h)| {
(
k,
match self.transactions.get(h) {
Some(tx) => tx,
None => panic!("Spentbook is in an inconsistent state"),
},
)
})
}
pub fn is_spent(&self, dbc_id: &DbcId) -> bool {
self.dbc_ids.contains_key(dbc_id)
}
pub fn log_spent(&mut self, tx: &DbcTransaction, signed_spend: &SignedSpend) -> Result<()> {
self.log_spent_worker(tx, signed_spend, true)
}
#[cfg(test)]
pub fn log_spent_and_skip_tx_verification(
&mut self,
spent_tx: &DbcTransaction,
signed_spend: &SignedSpend,
) -> Result<()> {
self.log_spent_worker(spent_tx, signed_spend, false)
}
fn log_spent_worker(
&mut self,
spent_tx: &DbcTransaction,
signed_spend: &SignedSpend,
verify_tx: bool,
) -> Result<()> {
let input_id = signed_spend.dbc_id();
let spent_tx_hash = signed_spend.spent_tx_hash();
let tx_hash = spent_tx.hash();
if tx_hash != spent_tx_hash {
return Err(Error::InvalidTransactionHash);
}
if verify_tx {
spent_tx.verify()?;
}
let existing_tx_hash = self.dbc_ids.entry(*input_id).or_insert_with(|| tx_hash);
if *existing_tx_hash == tx_hash {
let existing_tx = self
.transactions
.entry(tx_hash)
.or_insert_with(|| spent_tx.clone());
for output in existing_tx.outputs.iter() {
let output_id = *output.dbc_id();
self.outputs_by_input_id
.entry(output_id)
.or_insert_with(|| output.clone());
}
Ok(())
} else {
Err(crate::mock::Error::DbcAlreadySpent.into())
}
}
}