use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use vote_commitment_tree::{MerklePath, TreeClient};
use vote_commitment_tree_client::http_sync_api::HttpTreeSyncApi;
use crate::storage::VotingDb;
use crate::types::VotingError;
use crate::HyperTransport;
#[derive(Clone, Debug)]
pub struct VanWitness {
pub auth_path: Vec<[u8; 32]>,
pub position: u32,
pub anchor_height: u32,
}
impl From<(MerklePath, u32)> for VanWitness {
fn from((path, anchor_height): (MerklePath, u32)) -> Self {
let auth_path = path.auth_path().iter().map(|h| h.to_bytes()).collect();
Self {
auth_path,
position: path.position(),
anchor_height,
}
}
}
pub struct VoteTreeSync {
clients: Mutex<HashMap<String, TreeClient>>,
transport: Arc<HyperTransport>,
}
impl VoteTreeSync {
pub fn new() -> Self {
Self {
clients: Mutex::new(HashMap::new()),
transport: Arc::new(HyperTransport::new()),
}
}
pub fn sync(&self, db: &VotingDb, round_id: &str, node_url: &str) -> Result<u32, VotingError> {
let bundle_count = db.get_bundle_count(round_id)?;
let mut guard = self.clients.lock().map_err(|e| VotingError::Internal {
message: format!("tree client lock poisoned: {}", e),
})?;
let client = guard
.entry(round_id.to_string())
.or_insert_with(TreeClient::empty);
for bi in 0..bundle_count {
if let Ok(pos) = db.load_van_position(round_id, bi) {
client.mark_position(pos as u64);
}
}
let api = HttpTreeSyncApi::new(node_url, round_id, self.transport.clone());
client.sync(&api).map_err(|e| VotingError::Internal {
message: format!("vote tree sync failed: {}", e),
})?;
Ok(client.last_synced_height().unwrap_or(0))
}
pub fn generate_van_witness(
&self,
db: &VotingDb,
round_id: &str,
bundle_index: u32,
anchor_height: u32,
) -> Result<VanWitness, VotingError> {
let van_position = db.load_van_position(round_id, bundle_index)?;
let guard = self.clients.lock().map_err(|e| VotingError::Internal {
message: format!("tree client lock poisoned: {}", e),
})?;
let client = guard
.get(round_id)
.ok_or_else(|| VotingError::InvalidInput {
message: "must call sync before generate_van_witness".to_string(),
})?;
let path = client
.witness(van_position as u64, anchor_height)
.ok_or_else(|| VotingError::Internal {
message: format!(
"failed to generate witness for position {} at height {}",
van_position, anchor_height
),
})?;
Ok(VanWitness::from((path, anchor_height)))
}
pub fn reset(&self, round_id: &str) -> Result<(), VotingError> {
let mut guard = self.clients.lock().map_err(|e| VotingError::Internal {
message: format!("tree client lock poisoned: {}", e),
})?;
if round_id.is_empty() {
guard.clear();
} else {
guard.remove(round_id);
}
Ok(())
}
}