#[cfg(feature = "erlay")]
use anyhow::{Context, Result};
#[cfg(feature = "erlay")]
use blvm_protocol::Hash;
#[cfg(feature = "erlay")]
use minisketch_rs::Minisketch;
#[cfg(feature = "erlay")]
use std::collections::HashSet;
#[derive(Debug, Clone)]
pub struct ErlayConfig {
pub capacity: usize,
pub field_size: u8,
}
impl Default for ErlayConfig {
fn default() -> Self {
Self {
capacity: 100_000, field_size: 64, }
}
}
#[cfg(feature = "erlay")]
pub struct ErlayReconciler {
config: ErlayConfig,
}
#[cfg(feature = "erlay")]
impl ErlayReconciler {
pub fn new(config: ErlayConfig) -> Self {
Self { config }
}
pub fn create_sketch(
&self,
local_txs: &HashSet<Hash>,
remote_tx_count: usize,
) -> Result<Vec<u8>> {
if local_txs.is_empty() {
return Ok(vec![]);
}
let mut sketch = Minisketch::new(self.config.field_size, 0, self.config.capacity)
.context("Failed to create minisketch")?;
for tx_hash in local_txs {
let mut hash_u64 = [0u8; 8];
hash_u64.copy_from_slice(&tx_hash[..8]);
let value = u64::from_le_bytes(hash_u64);
sketch
.add(value)
.context("Failed to add transaction to sketch")?;
}
sketch.serialize().context("Failed to serialize sketch")
}
pub fn reconcile_sets(
&self,
local_txs: &HashSet<Hash>,
local_sketch: &[u8],
remote_sketch: &[u8],
) -> Result<Vec<Hash>> {
if local_sketch.is_empty() && remote_sketch.is_empty() {
return Ok(vec![]);
}
let mut combined = Minisketch::new(self.config.field_size, 0, self.config.capacity)
.context("Failed to create combined minisketch")?;
if !local_sketch.is_empty() {
let mut local = Minisketch::new(self.config.field_size, 0, self.config.capacity)
.context("Failed to create local minisketch")?;
local
.deserialize(local_sketch)
.context("Failed to deserialize local sketch")?;
combined
.merge(&local)
.map_err(|e| anyhow::anyhow!("Failed to merge local sketch: {:?}", e))?;
}
if !remote_sketch.is_empty() {
let mut remote = Minisketch::new(self.config.field_size, 0, self.config.capacity)
.context("Failed to create remote minisketch")?;
remote
.deserialize(remote_sketch)
.context("Failed to deserialize remote sketch")?;
combined
.merge(&remote)
.map_err(|e| anyhow::anyhow!("Failed to merge remote sketch: {:?}", e))?;
}
let differences: Vec<u64> = combined
.decode()
.map_err(|e| anyhow::anyhow!("Failed to decode sketch: {:?}", e))?;
let mut missing_txs = Vec::new();
for diff in differences {
let mut hash = [0u8; 32];
let diff_bytes = diff.to_le_bytes();
hash[..8].copy_from_slice(&diff_bytes);
if !local_txs.contains(&hash) {
missing_txs.push(hash);
}
}
Ok(missing_txs)
}
}
#[cfg(feature = "erlay")]
pub struct ErlayTxSet {
txs: HashSet<Hash>,
reconciler: ErlayReconciler,
}
#[cfg(feature = "erlay")]
impl ErlayTxSet {
pub fn new() -> Self {
Self {
txs: HashSet::new(),
reconciler: ErlayReconciler::new(ErlayConfig::default()),
}
}
pub fn with_config(config: ErlayConfig) -> Self {
Self {
txs: HashSet::new(),
reconciler: ErlayReconciler::new(config),
}
}
pub fn add(&mut self, tx_hash: Hash) {
self.txs.insert(tx_hash);
}
pub fn remove(&mut self, tx_hash: &Hash) {
self.txs.remove(tx_hash);
}
pub fn size(&self) -> usize {
self.txs.len()
}
pub fn contains(&self, tx_hash: &Hash) -> bool {
self.txs.contains(tx_hash)
}
pub fn create_reconciliation_sketch(&self, remote_tx_count: usize) -> Result<Vec<u8>> {
self.reconciler.create_sketch(&self.txs, remote_tx_count)
}
pub fn reconcile_with_peer(
&self,
local_sketch: &[u8],
remote_sketch: &[u8],
) -> Result<Vec<Hash>> {
self.reconciler
.reconcile_sets(&self.txs, local_sketch, remote_sketch)
}
pub fn get_all_hashes(&self) -> Vec<Hash> {
self.txs.iter().cloned().collect()
}
}
#[cfg(feature = "erlay")]
impl Default for ErlayTxSet {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "erlay")]
use super::*;
#[cfg(feature = "erlay")]
#[test]
fn test_erlay_reconciliation() {
let reconciler = ErlayReconciler::new(ErlayConfig::default());
let mut local_txs = HashSet::new();
local_txs.insert([1u8; 32]);
local_txs.insert([2u8; 32]);
local_txs.insert([3u8; 32]);
let mut remote_txs = HashSet::new();
remote_txs.insert([2u8; 32]);
remote_txs.insert([3u8; 32]);
remote_txs.insert([4u8; 32]);
let local_sketch = reconciler
.create_sketch(&local_txs, remote_txs.len())
.expect("Failed to create local sketch");
let remote_sketch = reconciler
.create_sketch(&remote_txs, local_txs.len())
.expect("Failed to create remote sketch");
let missing_local = reconciler
.reconcile_sets(&local_txs, &local_sketch, &remote_sketch)
.expect("Failed to reconcile");
let missing_remote = reconciler
.reconcile_sets(&remote_txs, &remote_sketch, &local_sketch)
.expect("Failed to reconcile");
assert!(missing_local.contains(&[4u8; 32]));
assert!(missing_remote.contains(&[1u8; 32]));
}
#[cfg(feature = "erlay")]
#[test]
fn test_erlay_tx_set() {
let mut tx_set = ErlayTxSet::new();
let tx1 = [1u8; 32];
let tx2 = [2u8; 32];
tx_set.add(tx1);
tx_set.add(tx2);
assert_eq!(tx_set.size(), 2);
assert!(tx_set.contains(&tx1));
assert!(tx_set.contains(&tx2));
tx_set.remove(&tx1);
assert_eq!(tx_set.size(), 1);
assert!(!tx_set.contains(&tx1));
}
}