Files
coracle-rust/coracle-lib/tests/pow.rs
T
Jon Staab c8f6bc1652 Add pow
2026-04-21 06:18:49 -07:00

218 lines
5.8 KiB
Rust

use coracle_lib::events::{EventContent, OwnedEvent};
use coracle_lib::keys::SecretKey;
use coracle_lib::pow::{get_leading_zero_bits, get_pow, mine_pow, mine_pow_batch};
use coracle_lib::tags::{Tag, Tags};
fn fixed_secret() -> SecretKey {
let bytes: [u8; 32] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32,
];
SecretKey::from_hex(&hex::encode(bytes)).unwrap()
}
fn test_event() -> OwnedEvent {
EventContent::new("hello nostr", Tags::new())
.kind(1)
.stamp(1_700_000_000)
.own(fixed_secret().public_key())
}
// --- get_leading_zero_bits ---
#[test]
fn leading_zeros_empty_slice() {
assert_eq!(get_leading_zero_bits(&[]), 0);
}
#[test]
fn leading_zeros_all_zeros() {
assert_eq!(get_leading_zero_bits(&[0, 0, 0, 0]), 32);
}
#[test]
fn leading_zeros_first_byte_nonzero() {
assert_eq!(get_leading_zero_bits(&[0x80, 0x00]), 0);
assert_eq!(get_leading_zero_bits(&[0x01, 0x00]), 7);
assert_eq!(get_leading_zero_bits(&[0x0F]), 4);
}
#[test]
fn leading_zeros_with_zero_prefix() {
assert_eq!(get_leading_zero_bits(&[0x00, 0x01]), 15);
assert_eq!(get_leading_zero_bits(&[0x00, 0x00, 0x80]), 16);
assert_eq!(get_leading_zero_bits(&[0x00, 0x00, 0x00, 0x01]), 31);
}
#[test]
fn leading_zeros_known_hash() {
// "000006d8..." → 0x00, 0x00, 0x06 → 16 + 5 = 21
let hash = hex::decode("000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358")
.unwrap();
assert_eq!(get_leading_zero_bits(&hash), 21);
}
// --- get_pow (free function) ---
#[test]
fn get_pow_returns_min_of_hash_and_claim() {
let event = test_event();
let hashed = mine_pow(&event, 8);
// The free function should return at least 8
assert!(get_pow(&hashed.id, &hashed.tags) >= 8);
}
#[test]
fn get_pow_zero_without_nonce_tag() {
let hashed = test_event().hash();
assert_eq!(get_pow(&hashed.id, &hashed.tags), 0);
}
#[test]
fn get_pow_capped_by_claimed_difficulty() {
// Mine at difficulty 4 — even if hash has more leading zeros,
// get_pow returns at most 4
let event = test_event();
let hashed = mine_pow(&event, 4);
assert!(get_pow(&hashed.id, &hashed.tags) >= 4);
// Actual leading zeros might exceed 4, but get_pow is min(leading, claimed)
assert_eq!(get_pow(&hashed.id, &hashed.tags), 4);
}
// --- HashedEvent::get_pow method ---
#[test]
fn hashed_event_get_pow_method() {
let event = test_event();
let hashed = mine_pow(&event, 8);
assert!(hashed.get_pow() >= 8);
}
#[test]
fn hashed_event_get_pow_zero_without_nonce() {
let hashed = test_event().hash();
assert_eq!(hashed.get_pow(), 0);
}
// --- Event::get_pow method ---
#[test]
fn signed_event_get_pow_method() {
let sk = fixed_secret();
let event = test_event();
let hashed = mine_pow(&event, 8);
let sig = sk.sign(&hashed.id);
let signed = hashed.sign(sig);
assert!(signed.get_pow() >= 8);
}
// --- OwnedEvent::mine_pow method ---
#[test]
fn owned_event_mine_pow_method() {
let event = test_event();
let result = event.mine_pow(8);
assert!(result.get_pow() >= 8);
}
// --- OwnedEvent::mine_pow_batch method ---
#[test]
fn owned_event_mine_pow_batch_method() {
let event = test_event();
let result = event.mine_pow_batch(1, 0, 1_000_000);
assert!(result.is_some());
assert!(result.unwrap().get_pow() >= 1);
}
// --- mine_pow (free function) ---
#[test]
fn mine_pow_returns_valid_result() {
let event = test_event();
let result = mine_pow(&event, 8);
assert!(get_leading_zero_bits(&result.id) >= 8);
let nonce_tag = result.tags.find("nonce").expect("should have nonce tag");
assert_eq!(nonce_tag.get(2), Some("8"));
let _nonce: u64 = nonce_tag.value().parse().expect("nonce should be a number");
}
#[test]
fn mine_pow_preserves_event_fields() {
let event = test_event();
let result = mine_pow(&event, 4);
assert_eq!(result.content, event.content);
assert_eq!(result.kind, event.kind);
assert_eq!(result.created_at, event.created_at);
assert_eq!(result.pubkey, event.pubkey);
}
#[test]
fn mine_pow_preserves_existing_tags() {
let mut event = test_event();
event.tags.0.push(Tag::new("t", ["nostr"]));
let result = mine_pow(&event, 4);
assert!(result.tags.find("t").is_some());
assert!(result.tags.find("nonce").is_some());
}
#[test]
fn mine_pow_does_not_mutate_input() {
let event = test_event();
let tags_before = event.tags.clone();
let _result = mine_pow(&event, 4);
assert_eq!(event.tags, tags_before);
}
// --- mine_pow_batch (free function) ---
#[test]
fn mine_pow_batch_returns_none_when_exhausted() {
let event = test_event();
let result = mine_pow_batch(&event, 64, 0, 10);
assert!(result.is_none());
}
#[test]
fn mine_pow_batch_finds_solution_in_range() {
let event = test_event();
let result = mine_pow_batch(&event, 1, 0, 1_000_000);
assert!(result.is_some());
assert!(get_leading_zero_bits(&result.unwrap().id) >= 1);
}
#[test]
fn mine_pow_batch_respects_start_offset() {
let event = test_event();
let a = mine_pow_batch(&event, 4, 0, 1_000_000);
let b = mine_pow_batch(&event, 4, 500_000, 1_000_000);
assert!(a.is_some());
assert!(b.is_some());
let nonce_a = a.unwrap().tags.find("nonce").unwrap().value().to_string();
let nonce_b = b.unwrap().tags.find("nonce").unwrap().value().to_string();
assert_ne!(nonce_a, nonce_b);
}
// --- Integration: mine then sign then verify ---
#[test]
fn mined_event_can_be_signed_and_verified() {
let sk = fixed_secret();
let event = test_event();
let hashed = mine_pow(&event, 8);
let sig = sk.sign(&hashed.id);
let signed = hashed.sign(sig);
assert!(signed.verify_id());
signed.verify().expect("mined and signed event should verify");
assert!(signed.get_pow() >= 8);
}