tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit c3a816e70ec033c7d485385fe59778ef93d39be4
parent 7efaeeb81a06a3a7dd2b61c23fc283b030da7504
Author: triesap <tyson@radroots.org>
Date:   Sat,  6 Jun 2026 02:35:15 -0700

bench: add deterministic dataset generator

Diffstat:
MCargo.lock | 9+++++++++
MCargo.toml | 1+
Acrates/tangle_bench/Cargo.toml | 16++++++++++++++++
Acrates/tangle_bench/src/lib.rs | 163+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/tangle_test_support/src/lib.rs | 27+++++++++++++++++++++------
5 files changed, 210 insertions(+), 6 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -4040,6 +4040,15 @@ dependencies = [ ] [[package]] +name = "tangle_bench" +version = "0.1.0" +dependencies = [ + "tangle_nips", + "tangle_protocol", + "tangle_test_support", +] + +[[package]] name = "tangle_core" version = "0.1.0" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "crates/tangle", + "crates/tangle_bench", "crates/tangle_core", "crates/tangle_crypto", "crates/tangle_nips", diff --git a/crates/tangle_bench/Cargo.toml b/crates/tangle_bench/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tangle_bench" +version.workspace = true +edition.workspace = true +authors.workspace = true +rust-version.workspace = true +license.workspace = true +description = "Deterministic benchmark and proof-gate harnesses for tangle" + +[dependencies] +tangle_nips = { path = "../tangle_nips" } +tangle_protocol = { path = "../tangle_protocol" } +tangle_test_support = { path = "../tangle_test_support" } + +[lints] +workspace = true diff --git a/crates/tangle_bench/src/lib.rs b/crates/tangle_bench/src/lib.rs @@ -0,0 +1,163 @@ +#![forbid(unsafe_code)] + +use tangle_protocol::Event; +use tangle_test_support::{FixtureKey, build_fixture_event_from_parts}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BenchDatasetConfig { + pub listing_count: usize, + pub note_count: usize, +} + +impl BenchDatasetConfig { + pub fn new(listing_count: usize, note_count: usize) -> Self { + Self { + listing_count, + note_count, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BenchDataset { + listings: Vec<Event>, + notes: Vec<Event>, +} + +impl BenchDataset { + pub fn generate(config: BenchDatasetConfig) -> Result<Self, String> { + let mut listings = Vec::with_capacity(config.listing_count); + for index in 0..config.listing_count { + listings.push(bench_listing(index)?); + } + let mut notes = Vec::with_capacity(config.note_count); + for index in 0..config.note_count { + notes.push(bench_note(index)?); + } + Ok(Self { listings, notes }) + } + + pub fn listings(&self) -> &[Event] { + &self.listings + } + + pub fn notes(&self) -> &[Event] { + &self.notes + } + + pub fn events(&self) -> Vec<Event> { + self.listings + .iter() + .chain(self.notes.iter()) + .cloned() + .collect() + } +} + +fn bench_listing(index: usize) -> Result<Event, String> { + let created_at = 1_714_200_000 + index as u64; + let price_major = 10 + (index % 50); + let price_minor = (index * 7) % 100; + let d = format!("bench-listing-{index:04}"); + let title = format!("Bench carrots {index:04}"); + let content = format!("Deterministic bench listing body {index:04}"); + build_fixture_event_from_parts( + FixtureKey::Seller, + created_at, + 30_402, + vec![ + vec!["d".to_owned(), d], + vec!["title".to_owned(), title], + vec![ + "price".to_owned(), + format!("{price_major}.{price_minor:02}"), + "USD".to_owned(), + ], + vec!["unit".to_owned(), "lb".to_owned()], + vec!["fulfillment".to_owned(), "pickup".to_owned()], + vec!["g".to_owned(), format!("c22yzu{}", index % 10)], + vec!["category".to_owned(), bench_category(index).to_owned()], + vec!["t".to_owned(), bench_topic(index).to_owned()], + vec!["practice".to_owned(), "no spray".to_owned()], + vec!["certification".to_owned(), "organic".to_owned()], + ], + &content, + ) +} + +fn bench_note(index: usize) -> Result<Event, String> { + build_fixture_event_from_parts( + FixtureKey::Buyer, + 1_714_300_000 + index as u64, + 1, + vec![vec!["t".to_owned(), "bench".to_owned()]], + &format!("Deterministic generic relay note {index:04}"), + ) +} + +fn bench_category(index: usize) -> &'static str { + match index % 3 { + 0 => "vegetables", + 1 => "fruit", + _ => "herbs", + } +} + +fn bench_topic(index: usize) -> &'static str { + match index % 4 { + 0 => "carrots", + 1 => "greens", + 2 => "apples", + _ => "basil", + } +} + +#[cfg(test)] +mod tests { + use super::{BenchDataset, BenchDatasetConfig}; + use std::collections::BTreeSet; + use tangle_nips::{ListingProjectionEvaluation, evaluate_listing_projection}; + + #[test] + fn deterministic_dataset_generator_produces_stable_signed_events() { + let first = BenchDataset::generate(BenchDatasetConfig::new(4, 2)).expect("first"); + let second = BenchDataset::generate(BenchDatasetConfig::new(4, 2)).expect("second"); + let listing_ids = first + .listings() + .iter() + .map(|event| event.id().as_str()) + .collect::<BTreeSet<_>>(); + let note_ids = first + .notes() + .iter() + .map(|event| event.id().as_str()) + .collect::<BTreeSet<_>>(); + + assert_eq!( + first + .events() + .iter() + .map(|event| event.id().as_str().to_owned()) + .collect::<Vec<_>>(), + second + .events() + .iter() + .map(|event| event.id().as_str().to_owned()) + .collect::<Vec<_>>() + ); + assert_eq!(first.listings().len(), 4); + assert_eq!(first.notes().len(), 2); + assert_eq!(listing_ids.len(), 4); + assert_eq!(note_ids.len(), 2); + assert!(first.listings().iter().all(|event| matches!( + evaluate_listing_projection(event), + ListingProjectionEvaluation::Eligible(_) + ))); + assert!( + first + .notes() + .iter() + .all(|event| event.unsigned().kind().as_u32() == 1) + ); + } +} diff --git a/crates/tangle_test_support/src/lib.rs b/crates/tangle_test_support/src/lib.rs @@ -126,15 +126,30 @@ pub fn fixture_spec_from_json(raw: &str) -> Result<FixtureEventSpec, String> { pub fn build_fixture_event(spec: &FixtureEventSpec) -> Result<Event, String> { let fixture_key = spec.fixture_key()?; + build_fixture_event_from_parts( + fixture_key, + spec.created_at, + spec.kind, + spec.tags.clone(), + &spec.content, + ) +} + +pub fn build_fixture_event_from_parts( + fixture_key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Vec<String>>, + content: &str, +) -> Result<Event, String> { let unsigned = UnsignedEvent::new( fixture_key.public_key(), - UnixTimestamp::new(spec.created_at), - Kind::new(spec.kind)?, - spec.tags - .iter() - .map(|values| Tag::new(values.clone())) + UnixTimestamp::new(created_at), + Kind::new(kind)?, + tags.into_iter() + .map(Tag::new) .collect::<Result<Vec<_>, _>>()?, - &spec.content, + content, ); sign_unsigned_event(fixture_key, unsigned) }