use crate::{TestChain, TestDelegatedSpace, TestHandleTree};
use libveritas::cert::{HandleSubtree, NumsSubtree, SpacesSubtree};
use libveritas::msg::{Bundle, ChainProof};
use libveritas::{SovereigntyState, Veritas, msg};
use spacedb::subtree::SubTree;
use spaces_nums::RootAnchor;
use spaces_nums::constants::COMMITMENT_FINALITY_INTERVAL;
use spaces_protocol::sname::SName;
#[derive(Clone, Debug)]
pub enum Step {
Stage(&'static [&'static str]),
Commit,
Finalize,
}
#[derive(Clone, Debug)]
pub struct Fixture {
pub name: &'static str,
pub steps: Vec<Step>,
}
pub struct HandleStates {
pub commits: Vec<Vec<&'static str>>,
pub staged: Vec<&'static str>,
pub finalized_count: usize,
}
impl HandleStates {
pub fn all_committed(&self) -> Vec<&'static str> {
self.commits.iter().flatten().copied().collect()
}
pub fn in_commit(&self, index: usize) -> &[&'static str] {
self.commits.get(index).map(|v| v.as_slice()).unwrap_or(&[])
}
pub fn is_committed(&self, handle: &str) -> bool {
self.commits.iter().flatten().any(|&h| h == handle)
}
pub fn is_staged(&self, handle: &str) -> bool {
self.staged.contains(&handle)
}
pub fn commit_index(&self, handle: &str) -> Option<usize> {
self.commits.iter().position(|c| c.contains(&handle))
}
pub fn latest_commit(&self) -> Option<&Vec<&str>> {
self.commits.last()
}
pub fn pending_commit(&self) -> Option<&Vec<&str>> {
if !self.has_pending_commit() {
return None;
}
self.latest_commit()
}
pub fn commit_count(&self) -> usize {
self.commits.len()
}
pub fn has_pending_commit(&self) -> bool {
self.commits.len() > self.finalized_count
}
pub fn needs_receipt(&self, handle: &str) -> bool {
self.commit_index(handle).map(|i| i > 0).unwrap_or(false)
}
pub fn sovereignty(&self, handle: &str) -> Option<SovereigntyState> {
if self.staged.iter().find(|&&s| s == handle).is_some() {
return Some(SovereigntyState::Dependent);
}
let commit_idx = self.commit_index(handle)?;
if commit_idx < self.finalized_count {
Some(SovereigntyState::Sovereign)
} else {
Some(SovereigntyState::Pending)
}
}
}
impl Fixture {
pub fn new(name: &'static str) -> Self {
Self {
name,
steps: vec![],
}
}
pub fn stage(mut self, handles: &'static [&'static str]) -> Self {
self.steps.push(Step::Stage(handles));
self
}
pub fn commit(mut self) -> Self {
self.steps.push(Step::Commit);
self
}
pub fn finalize(mut self) -> Self {
self.steps.push(Step::Finalize);
self
}
pub fn then(mut self, other: Fixture) -> Self {
self.steps.extend(other.steps);
self
}
pub fn handle_states(&self) -> HandleStates {
let mut commits: Vec<Vec<&'static str>> = vec![];
let mut staged: Vec<&'static str> = vec![];
let mut finalized_count: usize = 0;
for step in &self.steps {
match step {
Step::Stage(handles) => {
staged.extend(*handles);
}
Step::Commit => {
commits.push(std::mem::take(&mut staged));
}
Step::Finalize => {
finalized_count += 1;
}
}
}
HandleStates {
commits,
staged,
finalized_count,
}
}
}
#[derive(Clone)]
pub struct ChainState {
pub chain: TestChain,
pub anchors: Vec<RootAnchor>,
}
impl Default for ChainState {
fn default() -> Self {
Self::new()
}
}
impl ChainState {
pub fn new() -> Self {
Self {
chain: TestChain::new(),
anchors: vec![],
}
}
pub fn veritas(&self) -> Veritas {
let mut anchors = self.anchors.clone();
if anchors.is_empty() {
anchors.push(self.chain.current_root_anchor());
}
anchors.reverse();
Veritas::new().with_anchors(anchors).expect("valid anchors")
}
pub fn message(&self, bundles: Vec<Bundle>) -> msg::Message {
msg::Message {
chain: ChainProof {
anchor: self.chain.current_root_anchor().block,
spaces: SpacesSubtree(self.chain.spaces_tree.clone()),
nums: NumsSubtree(self.chain.nums_tree.clone()),
},
spaces: bundles,
}
}
}
#[derive(Clone)]
pub struct FixtureRunner {
pub fixture: Fixture,
pub step: std::vec::IntoIter<Step>,
pub space: TestDelegatedSpace,
pub handles: TestHandleTree,
pub anchors: Vec<RootAnchor>,
}
impl FixtureRunner {
pub fn new(state: &mut ChainState, fixture: Fixture) -> Self {
let space = state.chain.add_space_with_delegation(fixture.name);
let handles = TestHandleTree::new(&space);
Self {
step: fixture.steps.clone().into_iter(),
space,
fixture,
anchors: vec![],
handles,
}
}
pub fn build_bundle(&mut self) -> msg::Bundle {
let mut bundle = msg::Bundle {
subject: self.space.space.label(),
receipt: None,
epochs: vec![],
records: None,
delegate_records: None,
};
let space_label = self.space.space.label();
for c in &mut self.handles.commitments {
bundle.receipt = c.receipt.clone();
let mut epoch = msg::Epoch {
tree: HandleSubtree(c.handle_tree.clone()),
handles: vec![],
};
for handle in c.handles.values_mut() {
let signer = SName::join(&handle.name, &space_label).expect("join handle name");
handle.set_records(
sip7::RecordSet::pack(vec![sip7::Record::txt(
"name",
&[&handle.name.to_string()],
)])
.expect("pack records"),
&signer,
);
epoch.handles.push(msg::Handle {
name: handle.name.clone(),
genesis_spk: handle.genesis_spk.clone(),
records: handle.records.clone(),
signature: None,
})
}
bundle.epochs.push(epoch);
}
let mut empty_epoch = msg::Epoch {
tree: HandleSubtree(SubTree::empty()),
handles: vec![],
};
let staging = bundle.epochs.last_mut().unwrap_or(&mut empty_epoch);
for staged in self.handles.staged.values_mut() {
let signer = SName::join(&staged.handle.name, &space_label).expect("join handle name");
staged.handle.set_records(
sip7::RecordSet::pack(vec![sip7::Record::txt(
"name",
&[&staged.handle.name.to_string()],
)])
.expect("pack records"),
&signer,
);
staging.handles.push(msg::Handle {
name: staged.handle.name.clone(),
genesis_spk: staged.handle.genesis_spk.clone(),
records: staged.handle.records.clone(),
signature: Some(staged.signature),
})
}
if !empty_epoch.handles.is_empty() {
bundle.epochs.push(empty_epoch);
}
bundle
}
pub fn run_next(&mut self, state: &mut ChainState) -> Option<Step> {
let step = self.step.next()?;
match &step {
Step::Stage(stage) => {
for &name in *stage {
self.handles.add_handle(name);
}
}
Step::Commit => {
self.handles.commit(&mut state.chain);
state.anchors.push(state.chain.current_root_anchor())
}
Step::Finalize => {
state.chain.increase_time(COMMITMENT_FINALITY_INTERVAL + 1);
}
}
Some(step)
}
pub fn run(&mut self, state: &mut ChainState) {
while self.run_next(state).is_some() {}
}
}
pub fn staged_only() -> Fixture {
Fixture::new("@staged").stage(&["alice", "bob"])
}
pub fn single_commit_pending() -> Fixture {
Fixture::new("@pending").stage(&["alice", "bob"]).commit()
}
pub fn single_commit_finalized() -> Fixture {
Fixture::new("@sovereign")
.stage(&["alice", "bob"])
.commit()
.finalize()
}
pub fn two_commits_second_pending() -> Fixture {
Fixture::new("@two-pending")
.stage(&["alice", "bob"])
.commit()
.finalize()
.stage(&["charlie"])
.commit()
}
pub fn two_commits_both_finalized() -> Fixture {
Fixture::new("@two-finalized")
.stage(&["alice", "bob"])
.commit()
.finalize()
.stage(&["charlie"])
.commit()
.finalize()
}
pub fn finalized_with_staged() -> Fixture {
Fixture::new("@finalized-staged")
.stage(&["alice"])
.commit()
.finalize()
.stage(&["bob"])
}
pub fn kitchen_sink() -> Fixture {
Fixture::new("@kitchensink")
.stage(&["alice", "bob"])
.commit()
.finalize()
.stage(&["charlie", "dave"])
.commit()
.finalize()
.stage(&["eve", "frank"])
.commit()
.stage(&["grace", "heidi"])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_handle_states_staged_only() {
let states = staged_only().handle_states();
assert_eq!(states.commits.len(), 0);
assert_eq!(states.staged, vec!["alice", "bob"]);
assert!(states.is_staged("alice"));
assert!(!states.is_committed("alice"));
assert!(!states.has_pending_commit());
}
#[test]
fn test_handle_states_single_commit_finalized() {
let states = single_commit_finalized().handle_states();
assert_eq!(states.commits.len(), 1);
assert_eq!(states.in_commit(0), &["alice", "bob"]);
assert!(states.staged.is_empty());
assert!(states.is_committed("alice"));
assert_eq!(states.commit_index("alice"), Some(0));
assert!(!states.has_pending_commit());
assert_eq!(
states.sovereignty("alice"),
Some(SovereigntyState::Sovereign)
);
}
#[test]
fn test_handle_states_two_commits_second_pending() {
let states = two_commits_second_pending().handle_states();
assert_eq!(states.commits.len(), 2);
assert_eq!(states.finalized_count, 1);
assert!(states.has_pending_commit());
assert_eq!(states.commit_index("alice"), Some(0));
assert_eq!(
states.sovereignty("alice"),
Some(SovereigntyState::Sovereign)
);
assert!(!states.needs_receipt("alice"));
assert_eq!(states.commit_index("charlie"), Some(1));
assert_eq!(
states.sovereignty("charlie"),
Some(SovereigntyState::Pending)
);
assert!(states.needs_receipt("charlie"));
}
#[test]
fn test_handle_states_kitchen_sink() {
let states = kitchen_sink().handle_states();
assert_eq!(states.commits.len(), 3);
assert_eq!(states.finalized_count, 2);
assert!(states.has_pending_commit());
assert_eq!(states.in_commit(0), &["alice", "bob"]);
assert_eq!(
states.sovereignty("alice"),
Some(SovereigntyState::Sovereign)
);
assert!(!states.needs_receipt("alice"));
assert_eq!(states.in_commit(1), &["charlie", "dave"]);
assert_eq!(
states.sovereignty("charlie"),
Some(SovereigntyState::Sovereign)
);
assert!(states.needs_receipt("charlie"));
assert_eq!(states.in_commit(2), &["eve", "frank"]);
assert_eq!(states.sovereignty("eve"), Some(SovereigntyState::Pending));
assert!(states.needs_receipt("eve"));
assert_eq!(states.staged, vec!["grace", "heidi"]);
assert!(states.is_staged("grace"));
assert!(!states.is_committed("grace"));
assert_eq!(
states.sovereignty("grace"),
Some(SovereigntyState::Dependent)
);
}
}