use std::collections::{HashMap, HashSet};
use crate::transaction::Transaction;
pub type InterpreterFn<T, C> =
Box<dyn Fn(&Transaction, usize, Option<&C>) -> Option<T> + Send + Sync>;
pub struct Historian<T, C> {
interpreter: InterpreterFn<T, C>,
cache: Option<HashMap<String, Vec<T>>>,
}
impl<T: Clone, C> Historian<T, C> {
pub fn new(interpreter: InterpreterFn<T, C>) -> Self {
Historian {
interpreter,
cache: None,
}
}
pub fn with_cache(interpreter: InterpreterFn<T, C>) -> Self {
Historian {
interpreter,
cache: Some(HashMap::new()),
}
}
pub fn build_history(&mut self, start_tx: &Transaction, context: Option<&C>) -> Vec<T> {
let cache_key = start_tx.id().unwrap_or_default();
if let Some(ref cache) = self.cache {
if let Some(cached) = cache.get(&cache_key) {
return cached.clone();
}
}
let mut history = Vec::new();
let mut visited = HashSet::new();
self.traverse(start_tx, context, &mut history, &mut visited);
history.reverse();
if let Some(ref mut cache) = self.cache {
cache.insert(cache_key, history.clone());
}
history
}
fn traverse(
&self,
tx: &Transaction,
context: Option<&C>,
history: &mut Vec<T>,
visited: &mut HashSet<String>,
) {
let txid = tx.id().unwrap_or_default();
if visited.contains(&txid) {
return;
}
visited.insert(txid);
for output_index in 0..tx.outputs.len() {
if let Some(value) = (self.interpreter)(tx, output_index, context) {
history.push(value);
}
}
for input in &tx.inputs {
if let Some(ref source_tx) = input.source_transaction {
self.traverse(source_tx, context, history, visited);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transaction::Transaction;
#[test]
fn test_empty_transaction_returns_empty_history() {
let tx = Transaction::new();
let mut historian: Historian<String, ()> = Historian::new(Box::new(|_tx, _idx, _ctx| None));
let history = historian.build_history(&tx, None);
assert!(history.is_empty());
}
#[test]
fn test_interpreter_called_for_each_output() {
use crate::transaction::TransactionOutput;
let mut tx = Transaction::new();
tx.outputs.push(TransactionOutput {
satoshis: Some(100),
..Default::default()
});
tx.outputs.push(TransactionOutput {
satoshis: Some(200),
..Default::default()
});
tx.outputs.push(TransactionOutput {
satoshis: Some(300),
..Default::default()
});
let mut historian: Historian<u64, ()> =
Historian::new(Box::new(|tx, idx, _ctx| tx.outputs[idx].satoshis));
let history = historian.build_history(&tx, None);
assert_eq!(history.len(), 3);
assert_eq!(history, vec![300, 200, 100]);
}
#[test]
fn test_caching_returns_same_result() {
let tx = Transaction::new();
let mut historian: Historian<String, ()> =
Historian::with_cache(Box::new(|_tx, _idx, _ctx| None));
let h1 = historian.build_history(&tx, None);
let h2 = historian.build_history(&tx, None);
assert_eq!(h1, h2);
}
}