build: upgrade rust-nostr to stable 0.45.0

Move the relay and audit workspace from the alpha.8 prerelease to the published stable rust-nostr 0.45 line so the embedded relay receives the upstream NEG-OPEN fix through a supported release.

Adapt removed Alphabet constructors to the stable named SingleLetterTag constants and replace the removed all-zero EventId helper with an explicit zero byte array. Raise grasp-audit's declared MSRV to the 1.85 required by rust-nostr 0.45 and refresh the shared lockfile.

This commit deliberately excludes any pagination-policy changes; the stable dependency revealed a reproducible failure in the existing large REQ+EOSE concurrency scenario that requires separate diagnosis before this upgrade is merge-ready.

Validated with workspace all-target compilation and both Nix package builds. The NIP-77 and NEG-concurrency scenarios pass; the full suite is blocked only by the separately noted REQ+EOSE historic-pagination regression.
This commit is contained in:
DanConwayDev
2026-08-05 15:33:22 +00:00
parent 0a73d68da4
commit 42bf3777b2
26 changed files with 177 additions and 182 deletions
+6 -1
View File
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
- Upgraded the rust-nostr crates from `0.45.0-alpha.8` to the stable `0.45.0`
release, including the upstream NEG-OPEN handling fix.
### Security
- Restricted event-directed sync targets to globally reachable endpoints.
@@ -57,7 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
IDs that fail with `Invalid input length 64` - closed the session, forcing
clients to reconnect and re-subscribe. Invalid messages are now answered with
a `NOTICE` and the connection stays open. Fixed upstream in rust-nostr and
picked up by upgrading to `0.45.0-alpha.8`.
picked up by upgrading to the stable `0.45.0` release.
- Fixed proactive sync losing repository events when public relays cap active
subscriptions. Compatible GRASP filters now share bounded NIP-01 REQs while
retaining per-filter history pagination.
Generated
+58 -24
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "adler2"
@@ -114,9 +114,9 @@ dependencies = [
[[package]]
name = "async-wsocket"
version = "0.16.0"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efa9ef232c1731ddbe4a306b5b5dcf9e50a924d6bd086d4784939d14f8e78daa"
checksum = "2c713e1f14c7b82e32ea159af1c6e2f070cfadbdf23fb2512acce9af0a26f1a2"
dependencies = [
"futures",
"futures-util",
@@ -156,11 +156,26 @@ version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc"
dependencies = [
"bitcoin_hashes",
"bitcoin_hashes 0.14.100",
"serde",
"unicode-normalization",
]
[[package]]
name = "bitcoin-consensus-encoding"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "207311705279250ba465076a1bac4b1ac982855fff73fc5f67e22158ac58cdc9"
dependencies = [
"bitcoin-internals",
]
[[package]]
name = "bitcoin-internals"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5"
[[package]]
name = "bitcoin-io"
version = "0.1.100"
@@ -174,7 +189,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f"
dependencies = [
"bitcoin-io",
"hex-conservative",
"hex-conservative 0.2.2",
]
[[package]]
name = "bitcoin_hashes"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5304e53726dbe5f93141535e102ed97b5bf4714fbecefdda8f9fb98d7fdaff0e"
dependencies = [
"bitcoin-consensus-encoding",
"bitcoin-internals",
"hex-conservative 1.2.0",
"serde",
]
@@ -880,6 +906,15 @@ dependencies = [
"arrayvec",
]
[[package]]
name = "hex-conservative"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35431185f361ccf3ffc58254628af5f1f5d5f28531da2e02e5d6c82bbc282a10"
dependencies = [
"arrayvec",
]
[[package]]
name = "http"
version = "1.4.2"
@@ -1274,7 +1309,7 @@ dependencies = [
"anyhow",
"async-trait",
"base64",
"bitcoin_hashes",
"bitcoin_hashes 0.14.100",
"clap",
"dashmap",
"dotenvy",
@@ -1306,14 +1341,14 @@ dependencies = [
[[package]]
name = "nostr"
version = "0.45.0-alpha.8"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0334ad7a19f229983c808840f65d813d09c7fc030775db5d325abffa1295fe6c"
checksum = "7279feef28d43a04c0b3529acd4fea923ac816729bd643191b292296f1f6cc13"
dependencies = [
"base64",
"bech32",
"bip39",
"bitcoin_hashes",
"bitcoin_hashes 1.2.0",
"cbc",
"chacha20 0.9.1",
"chacha20poly1305",
@@ -1330,21 +1365,19 @@ dependencies = [
[[package]]
name = "nostr-database"
version = "0.45.0-alpha.8"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e3827aa36205889d9950bb07e9ab9380f7881ab3b059956f2d87651fa64281"
checksum = "81c7ffd33114d62c737dd2b8f77ffec4693c64b7a1c6090cab0d0009f9986e22"
dependencies = [
"btreecap",
"flatbuffers",
"nostr",
"opaquerr",
]
[[package]]
name = "nostr-gossip"
version = "0.45.0-alpha.8"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4696e01b02eb84daa6d8419f529c6e6f3063b742a54925dc0300a3230871477"
checksum = "fa07539e52a71cb91fe0d693facaa298f03fcf9edcd66a521094e18e286e2336"
dependencies = [
"nostr",
"opaquerr",
@@ -1352,11 +1385,12 @@ dependencies = [
[[package]]
name = "nostr-lmdb"
version = "0.45.0-alpha.8"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2832ab8243313733630c90208f36b9859dd939b13c745c977f2a0c465b5ec117"
checksum = "b7a313418ff58bfa444ba4b71200ff8fe2d3eb31f2b5d0d8fdb7f5e60a703d31"
dependencies = [
"async-utility",
"flatbuffers",
"flume",
"heed",
"nostr",
@@ -1367,9 +1401,9 @@ dependencies = [
[[package]]
name = "nostr-memory"
version = "0.45.0-alpha.8"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1688a4bfea60f72ca212c55f23cc9f068bfb5e787829d85fc7f689c3df9de842"
checksum = "1a827de4b11c4f2b235eabe37f1a67460db68e453606f4755e1ac5a522347a86"
dependencies = [
"btreecap",
"nostr",
@@ -1379,9 +1413,9 @@ dependencies = [
[[package]]
name = "nostr-sdk"
version = "0.45.0-alpha.8"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ffff8a1d69b9828574ead3b34d615bffe4c75ff3e3335a28f111867c7f7cdd"
checksum = "359881fbaded2d19a5003609673744b8b68c4973817598eb0e262d049ec9c3f3"
dependencies = [
"async-utility",
"async-wsocket",
@@ -1430,9 +1464,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "opaquerr"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e55a7b7f0accc9fdd8e514f122a697adf824d55de481ca26c31ff8f9a8e813c5"
checksum = "4f933a4265d5cdad61d19bbdfc972ea5726d56cd8d3d57b8f2d3c365dd42bee9"
[[package]]
name = "openssl"
@@ -1920,7 +1954,7 @@ version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252"
dependencies = [
"bitcoin_hashes",
"bitcoin_hashes 0.14.100",
"rand 0.8.6",
"secp256k1-sys",
]
+7 -12
View File
@@ -18,23 +18,18 @@ http-body-util = "0.1"
# Nostr
#
# These are prerelease (alpha) requirements and are pinned exactly with `=`.
# Cargo treats `"0.45.0-alpha.8"` as `^0.45.0-alpha.8`, which accepts *any*
# later prerelease of 0.45.0 even though alphas carry no compatibility
# guarantee (alpha.4 deleted the `nostr-relay-builder` crate outright). Exact
# pins keep lockfile-less builds - `cargo install`, downstream consumers of the
# `ngit_grasp` library - on the version this tree is tested against.
# Relax to caret requirements once 0.45.0 is released.
nostr = "=0.45.0-alpha.8"
# The stable 0.45 series contains the upstream NEG-OPEN handling fix used by
# the embedded relay. Caret requirements accept compatible patch releases.
nostr = "0.45.0"
# `local-relay` carries the embedded relay implementation, previously the
# separate `nostr-relay-builder` crate.
nostr-sdk = { version = "=0.45.0-alpha.8", features = ["local-relay"] }
nostr-lmdb = "=0.45.0-alpha.8"
nostr-memory = "=0.45.0-alpha.8"
nostr-sdk = { version = "0.45.0", features = ["local-relay"] }
nostr-lmdb = "0.45.0"
nostr-memory = "0.45.0"
# Utilities
# SHA-1 for the `Sec-WebSocket-Accept` handshake. `nostr` uses this same crate
# internally but stopped re-exporting it as `nostr::hashes` in 0.45.0-alpha.8,
# internally but stopped re-exporting it as `nostr::hashes` in 0.45,
# so depend on it directly rather than pulling in a second hash implementation.
bitcoin_hashes = "0.14"
futures-util = "0.3"
+1 -1
View File
@@ -71,7 +71,7 @@ advertised, and many relays omit `limitation` entirely. Consequence: filter
sizing cannot be negotiated per relay — it must be statically conservative,
with reactive fallback as the backstop.
### Our own embedded relay (nostr-sdk `LocalRelay`, 0.45.0-alpha.8)
### Our own embedded relay (nostr-sdk `LocalRelay`, 0.45.0)
- `max_reqs` = 500, enforced for REQ only (`src/nostr/builder.rs`).
- Negentropy: **no concurrency limit at all** (upstream `TODO`), 60000-byte
+2 -3
View File
@@ -2,7 +2,7 @@
name = "grasp-audit"
version = "0.2.0"
edition = "2021"
rust-version = "1.75"
rust-version = "1.85"
description = "Audit and compliance testing tool for GRASP protocol implementations"
license = "MIT"
repository = "https://gitworkshop.dev/danconwaydev.com/ngit-grasp"
@@ -13,8 +13,7 @@ path = "src/bin/grasp-audit.rs"
[dependencies]
# Nostr
# Pinned exactly: see the rationale in the workspace root Cargo.toml.
nostr-sdk = "=0.45.0-alpha.8"
nostr-sdk = "0.45.0"
# Async
tokio = { version = "1", features = ["full"] }
+2 -2
View File
@@ -434,12 +434,12 @@ impl AuditClient {
/// Query events, optionally filtered to this audit run
pub async fn query(&self, mut filter: Filter) -> Result<Vec<Event>> {
use nostr_sdk::prelude::{Alphabet, SingleLetterTag};
use nostr_sdk::prelude::SingleLetterTag;
if self.config.mode == AuditMode::Isolated {
// In Isolated mode, only see our own audit events
// Filter by "t" tags (hashtags)
let t_tag = SingleLetterTag::lowercase(Alphabet::T);
let t_tag = SingleLetterTag::LOWERCASE_T;
filter = filter
.custom_tag(t_tag, "grasp-audit-test-event")
.custom_tag(t_tag, format!("audit-{}", self.config.run_id));
+2 -4
View File
@@ -913,7 +913,7 @@ pub async fn run_probe_with_options(
let r2 = if r1.is_ok() {
client.send_event(state.clone()).await
} else {
r1.map(|_| EventId::all_zeros())
r1.map(|_| EventId::from_byte_array([0; 32]))
};
r2
}
@@ -1482,9 +1482,7 @@ pub async fn run_probe_with_options(
// The relay only serves authorized state events (owner + full
// recursive maintainer chain already resolved by the relay).
let state_filter = Filter::new().kind(Kind::RepoState).custom_tag(
nostr_sdk::prelude::SingleLetterTag::lowercase(
nostr_sdk::prelude::Alphabet::D,
),
nostr_sdk::prelude::SingleLetterTag::LOWERCASE_D,
ann_id.clone(),
);
let state_events = client
+1 -1
View File
@@ -261,7 +261,7 @@ impl Nip01SmokeTests {
// Create event JSON with corrupted ID
let invalid_event_json = serde_json::json!({
"id": EventId::all_zeros().to_hex(), // Wrong ID!
"id": EventId::from_byte_array([0; 32]).to_hex(), // Wrong ID!
"pubkey": event.pubkey.to_hex(),
"created_at": event.created_at.as_secs(),
"kind": event.kind.as_u16(),
+2 -8
View File
@@ -214,10 +214,7 @@ pub async fn fetch_repository_data_excluding_purgatory(
) -> Result<RepositoryData> {
let filter = Filter::new()
.kinds([Kind::GitRepoAnnouncement, Kind::RepoState])
.custom_tag(
SingleLetterTag::lowercase(Alphabet::D),
identifier.to_string(),
);
.custom_tag(SingleLetterTag::LOWERCASE_D, identifier.to_string());
let events: Vec<Event> = database
.query(filter)
@@ -770,10 +767,7 @@ impl AuthorizationContext {
pub fn create_filter(identifier: &str) -> Filter {
Filter::new()
.kinds([Kind::GitRepoAnnouncement, Kind::RepoState])
.custom_tag(
SingleLetterTag::lowercase(Alphabet::D),
identifier.to_string(),
)
.custom_tag(SingleLetterTag::LOWERCASE_D, identifier.to_string())
}
/// Get the latest authorized state for a repository
+3 -4
View File
@@ -691,10 +691,9 @@ impl Nip34WritePolicy {
/// chain apply its preferred state before considering older candidates,
/// without requiring an invitee-authored state event or push.
async fn reconcile_stored_state_events_for_identifier(&self, identifier: &str) {
let filter = Filter::new().kind(Kind::RepoState).custom_tag(
SingleLetterTag::lowercase(Alphabet::D),
identifier.to_string(),
);
let filter = Filter::new()
.kind(Kind::RepoState)
.custom_tag(SingleLetterTag::LOWERCASE_D, identifier.to_string());
let mut states: Vec<Event> = match self.ctx.database.query(filter).await {
Ok(events) => events.into_iter().collect(),
Err(error) => {
+3 -11
View File
@@ -4,9 +4,7 @@ use std::path::PathBuf;
use flate2::write::GzEncoder;
use flate2::Compression;
use nostr_sdk::prelude::{
Alphabet, Event, EventId, Filter, Kind, PublicKey, SingleLetterTag, Timestamp,
};
use nostr_sdk::prelude::{Event, EventId, Filter, Kind, PublicKey, SingleLetterTag, Timestamp};
use tar::Builder as TarBuilder;
use super::{
@@ -278,10 +276,7 @@ impl DeletionPolicy {
let kind = Kind::from(kind_num);
let mut filter = Filter::new().kind(kind).author(*author);
if kind.is_addressable() {
filter = filter.custom_tag(
SingleLetterTag::lowercase(Alphabet::D),
identifier.to_string(),
);
filter = filter.custom_tag(SingleLetterTag::LOWERCASE_D, identifier.to_string());
}
// Per NIP-09, only versions up to the deletion's created_at are deleted.
filter = filter.until(deletion_created_at);
@@ -478,10 +473,7 @@ impl DeletionPolicy {
let remaining_filter = Filter::new()
.kind(Kind::GitRepoAnnouncement)
.author(scope.owner)
.custom_tag(
SingleLetterTag::lowercase(Alphabet::D),
scope.identifier.clone(),
);
.custom_tag(SingleLetterTag::LOWERCASE_D, scope.identifier.clone());
match self.ctx.database.query(remaining_filter).await {
Ok(remaining) if !remaining.is_empty() => {
+14 -18
View File
@@ -1,8 +1,6 @@
use std::collections::{HashMap, HashSet, VecDeque};
use nostr_sdk::prelude::{
Alphabet, Event, EventId, Filter, Kind, PublicKey, SingleLetterTag, Timestamp,
};
use nostr_sdk::prelude::{Event, EventId, Filter, Kind, PublicKey, SingleLetterTag, Timestamp};
use super::policy::{
announcement_address_of, identifier_from_event, owner_directory_component, DeletionPolicy,
@@ -344,10 +342,9 @@ impl DeletionPolicy {
moved_ids: &mut HashSet<EventId>,
source: DeletionSource,
) -> DeletionOutcome {
let announcement_filter = Filter::new().kind(Kind::GitRepoAnnouncement).custom_tag(
SingleLetterTag::lowercase(Alphabet::D),
identifier.to_string(),
);
let announcement_filter = Filter::new()
.kind(Kind::GitRepoAnnouncement)
.custom_tag(SingleLetterTag::LOWERCASE_D, identifier.to_string());
let remaining_announcements = match self.ctx.database.query(announcement_filter).await {
Ok(events) => events,
@@ -368,10 +365,9 @@ impl DeletionPolicy {
return DeletionOutcome::default();
}
let state_filter = Filter::new().kind(Kind::RepoState).custom_tag(
SingleLetterTag::lowercase(Alphabet::D),
identifier.to_string(),
);
let state_filter = Filter::new()
.kind(Kind::RepoState)
.custom_tag(SingleLetterTag::LOWERCASE_D, identifier.to_string());
let metadata = HoldingMetadata {
deleted_at: deletion_created_at,
@@ -616,7 +612,7 @@ async fn query_address_events_with_until(
let mut filter = Filter::new().kind(parsed.kind).author(parsed.pubkey);
if let Some(identifier) = parsed.identifier {
filter = filter.custom_tag(SingleLetterTag::lowercase(Alphabet::D), identifier);
filter = filter.custom_tag(SingleLetterTag::LOWERCASE_D, identifier);
}
if let Some(until) = until {
filter = filter.until(until);
@@ -666,9 +662,9 @@ async fn query_incoming_event_refs(
query_tag_variants(
database,
&[
SingleLetterTag::lowercase(Alphabet::E),
SingleLetterTag::uppercase(Alphabet::E),
SingleLetterTag::lowercase(Alphabet::Q),
SingleLetterTag::LOWERCASE_E,
SingleLetterTag::UPPERCASE_E,
SingleLetterTag::LOWERCASE_Q,
],
&hex,
)
@@ -682,9 +678,9 @@ async fn query_incoming_address_refs(
query_tag_variants(
database,
&[
SingleLetterTag::lowercase(Alphabet::A),
SingleLetterTag::uppercase(Alphabet::A),
SingleLetterTag::lowercase(Alphabet::Q),
SingleLetterTag::LOWERCASE_A,
SingleLetterTag::UPPERCASE_A,
SingleLetterTag::LOWERCASE_Q,
],
address,
)
+2 -2
View File
@@ -165,7 +165,7 @@ mod tests {
fn deletion() -> Event {
EventBuilder::new(Kind::EventDeletion, "")
.tags(vec![Tag::event(EventId::all_zeros())])
.tags(vec![Tag::event(EventId::from_byte_array([0; 32]))])
.finalize(&Keys::generate())
.unwrap()
}
@@ -236,7 +236,7 @@ mod tests {
service
.ctx
.tombstones()
.event_deletion_candidates(&EventId::all_zeros(), &request.pubkey)
.event_deletion_candidates(&EventId::from_byte_array([0; 32]), &request.pubkey)
.await
.unwrap()
.len(),
+5 -7
View File
@@ -34,8 +34,7 @@ use std::collections::HashSet;
use nostr::nips::nip19::ToBech32;
use nostr_sdk::prelude::{
Alphabet, Event, EventId, Filter, Kind, PublicKey, SingleLetterTag, Timestamp,
WritePolicyResult,
Event, EventId, Filter, Kind, PublicKey, SingleLetterTag, Timestamp, WritePolicyResult,
};
use super::service::{request_is_served_with_config, UNSERVED_REPLAY_MESSAGE};
@@ -275,8 +274,7 @@ impl DeletionPolicy {
.author(owner)
.until(event.created_at);
if kind.is_addressable() {
filter =
filter.custom_tag(SingleLetterTag::lowercase(Alphabet::D), identifier.clone());
filter = filter.custom_tag(SingleLetterTag::LOWERCASE_D, identifier.clone());
}
if !self.ctx.database.query(filter).await?.is_empty() {
return Ok(true);
@@ -551,7 +549,7 @@ mod tests {
async fn distinct_overlapping_deletion_requests_are_independently_accepted() {
let ctx = make_context();
let keys = Keys::generate();
let target = EventId::all_zeros();
let target = EventId::from_byte_array([0; 32]);
let first = EventBuilder::new(Kind::EventDeletion, "")
.tags(vec![Tag::event(target)])
@@ -622,7 +620,7 @@ mod tests {
let ctx = make_context();
let keys = Keys::generate();
let deletion = EventBuilder::new(Kind::EventDeletion, "")
.tags(vec![Tag::event(EventId::all_zeros())])
.tags(vec![Tag::event(EventId::from_byte_array([0; 32]))])
.finalize(&keys)
.unwrap();
@@ -1005,7 +1003,7 @@ mod tests {
async fn disrespector_records_unused_lifecycle_without_enforcing_a_gate() {
let ctx = make_disrespector_context();
let keys = Keys::generate();
let target = EventId::all_zeros();
let target = EventId::from_byte_array([0; 32]);
let deletion = EventBuilder::new(Kind::EventDeletion, "")
.tags(vec![Tag::event(target)])
+2 -7
View File
@@ -1,8 +1,6 @@
use std::collections::{BTreeSet, HashSet};
use nostr_sdk::prelude::{
Alphabet, Event, EventId, Filter, Kind, PublicKey, SingleLetterTag, Timestamp,
};
use nostr_sdk::prelude::{Event, EventId, Filter, Kind, PublicKey, SingleLetterTag, Timestamp};
use super::policy::{identifier_from_event, DeletionPolicy};
use crate::git;
@@ -161,10 +159,7 @@ impl DeletionPolicy {
) -> Option<Event> {
let mut filter = Filter::new().kind(kind).author(*author);
if kind.is_addressable() {
filter = filter.custom_tag(
SingleLetterTag::lowercase(Alphabet::D),
identifier.to_string(),
);
filter = filter.custom_tag(SingleLetterTag::LOWERCASE_D, identifier.to_string());
}
if let Some(cutoff) = until {
filter = filter.until(cutoff);
+1 -1
View File
@@ -305,7 +305,7 @@ mod tests {
Duration::from_secs(60),
);
let request = EventBuilder::new(Kind::EventDeletion, "")
.tags(vec![Tag::event(EventId::all_zeros())])
.tags(vec![Tag::event(EventId::from_byte_array([0; 32]))])
.finalize(&Keys::generate())
.expect("deletion request should sign");
+7 -8
View File
@@ -1,7 +1,6 @@
use anyhow::Result;
use nostr_sdk::prelude::{
nip62, Alphabet, Event, Filter, Kind, PublicKey, RelayUrl, SingleLetterTag, Timestamp,
WritePolicyResult,
nip62, Event, Filter, Kind, PublicKey, RelayUrl, SingleLetterTag, Timestamp, WritePolicyResult,
};
use crate::nostr::events::RepositoryAnnouncement;
@@ -523,7 +522,7 @@ impl DeletionService {
.kind(Kind::GitRepoAnnouncement)
.author(incoming.pubkey)
.custom_tag(
SingleLetterTag::lowercase(Alphabet::D),
SingleLetterTag::LOWERCASE_D,
incoming_announcement.identifier.clone(),
);
@@ -948,9 +947,9 @@ mod tests {
deletion_request_retention_used_unserved_gating_additional_secs: 3,
..crate::config::Config::for_testing()
}));
let request = deletion(&Keys::generate(), EventId::all_zeros());
let request = deletion(&Keys::generate(), EventId::from_byte_array([0; 32]));
let unused = RequestLifecycleRecord {
metadata_event_id: EventId::all_zeros(),
metadata_event_id: EventId::from_byte_array([0; 32]),
request: request.clone(),
first_seen_at: Timestamp::from_secs(10),
last_used_at: None,
@@ -978,16 +977,16 @@ mod tests {
fn winner_order_prefers_used_then_first_seen_then_event_id() {
let keys = Keys::generate();
let first_request = EventBuilder::new(Kind::EventDeletion, "first")
.tags(vec![Tag::event(EventId::all_zeros())])
.tags(vec![Tag::event(EventId::from_byte_array([0; 32]))])
.finalize(&keys)
.unwrap();
let second_request = EventBuilder::new(Kind::EventDeletion, "second")
.tags(vec![Tag::event(EventId::all_zeros())])
.tags(vec![Tag::event(EventId::from_byte_array([0; 32]))])
.finalize(&keys)
.unwrap();
let record = |request: Event, first_seen_at: u64, last_used_at: Option<u64>| {
RequestLifecycleRecord {
metadata_event_id: EventId::all_zeros(),
metadata_event_id: EventId::from_byte_array([0; 32]),
request,
first_seen_at: Timestamp::from_secs(first_seen_at),
last_used_at: last_used_at.map(Timestamp::from_secs),
+2 -5
View File
@@ -10,7 +10,7 @@ use std::sync::Arc;
use nostr_lmdb::NostrLmdb;
use nostr_memory::MemoryDatabase;
use nostr_sdk::prelude::{
Alphabet, Event, EventBuilder, EventId, Filter, FinalizeEvent, Keys, Kind, NostrDatabase,
Event, EventBuilder, EventId, Filter, FinalizeEvent, Keys, Kind, NostrDatabase,
SingleLetterTag, Tag, Timestamp,
};
@@ -145,10 +145,7 @@ impl ReplaceableHistoryStore {
) -> Vec<SupersededRecord> {
let filter = Filter::new()
.kind(Kind::from(HISTORY_METADATA_KIND))
.custom_tag(
SingleLetterTag::lowercase(Alphabet::A),
coordinate.to_string(),
);
.custom_tag(SingleLetterTag::LOWERCASE_A, coordinate.to_string());
let events = match self.db.query(filter).await {
Ok(events) => events,
+3 -3
View File
@@ -12,7 +12,7 @@ use std::{collections::BTreeSet, io::ErrorKind};
use nostr_lmdb::NostrLmdb;
use nostr_memory::MemoryDatabase;
use nostr_sdk::prelude::{
Alphabet, Event, EventBuilder, EventId, Filter, FinalizeEvent, Keys, Kind, NostrDatabase,
Event, EventBuilder, EventId, Filter, FinalizeEvent, Keys, Kind, NostrDatabase,
SingleLetterTag, Tag, Timestamp,
};
@@ -220,7 +220,7 @@ impl HoldingStore {
pub async fn metadata_for_event(&self, id: &EventId) -> Vec<Event> {
let filter = Filter::new()
.kind(Kind::from(HOLDING_METADATA_KIND))
.custom_tag(SingleLetterTag::lowercase(Alphabet::E), id.to_hex());
.custom_tag(SingleLetterTag::LOWERCASE_E, id.to_hex());
match self.db.query(filter).await {
Ok(events) => events.into_iter().collect(),
@@ -922,7 +922,7 @@ mod tests {
let store = HoldingStore::in_memory();
let metadata = EventBuilder::new(Kind::from(HOLDING_METADATA_KIND), "")
.tags(vec![
Tag::custom("e", vec![EventId::all_zeros().to_hex()]),
Tag::custom("e", vec![EventId::from_byte_array([0; 32]).to_hex()]),
Tag::custom("holding-deleted-at", vec!["not-a-number".to_string()]),
])
.finalize(&Keys::generate())
+17 -18
View File
@@ -51,8 +51,8 @@ use tokio::sync::{Mutex, MutexGuard};
use nostr_lmdb::NostrLmdb;
use nostr_memory::MemoryDatabase;
use nostr_sdk::prelude::{
Alphabet, Event, EventBuilder, EventId, Filter, FinalizeEvent, Keys, Kind, NostrDatabase,
PublicKey, SingleLetterTag, Tag, Timestamp,
Event, EventBuilder, EventId, Filter, FinalizeEvent, Keys, Kind, NostrDatabase, PublicKey,
SingleLetterTag, Tag, Timestamp,
};
/// Directory name (under `relay_data_path`) for the LMDB tombstone database.
@@ -765,7 +765,7 @@ impl Tombstones {
) -> anyhow::Result<Vec<Event>> {
let filter = Filter::new()
.kind(Kind::from(TOMBSTONE_REQUEST_METADATA_KIND))
.custom_tag(SingleLetterTag::lowercase(Alphabet::E), request_id.to_hex());
.custom_tag(SingleLetterTag::LOWERCASE_E, request_id.to_hex());
self.db
.query(filter)
.await
@@ -868,7 +868,7 @@ impl Tombstones {
let filter = Filter::new()
.kind(Kind::EventDeletion)
.author(*author)
.custom_tag(SingleLetterTag::lowercase(Alphabet::E), id.to_hex());
.custom_tag(SingleLetterTag::LOWERCASE_E, id.to_hex());
self.lifecycle_candidates(filter, |request| {
request.kind == Kind::EventDeletion
&& request.pubkey == *author
@@ -892,10 +892,9 @@ impl Tombstones {
// it. Extract it so we can require the kind-5 author to match.
let coord_owner_hex = coordinate.split(':').nth(1);
let filter = Filter::new().kind(Kind::EventDeletion).custom_tag(
SingleLetterTag::lowercase(Alphabet::A),
coordinate.to_string(),
);
let filter = Filter::new()
.kind(Kind::EventDeletion)
.custom_tag(SingleLetterTag::LOWERCASE_A, coordinate.to_string());
self.lifecycle_candidates(filter, |request| {
request.kind == Kind::EventDeletion
&& request.created_at >= event_created_at
@@ -980,7 +979,7 @@ mod tests {
async fn lifecycle_round_trip_for_kind_five_preserves_relay_first_seen_at() {
let store = Tombstones::in_memory();
let keys = Keys::generate();
let request = deletion_by_event(&keys, EventId::all_zeros());
let request = deletion_by_event(&keys, EventId::from_byte_array([0; 32]));
let first_seen_at = Timestamp::from_secs(1234);
store
@@ -1036,7 +1035,7 @@ mod tests {
#[tokio::test]
async fn exact_replay_does_not_reset_first_seen_at() {
let store = Tombstones::in_memory();
let request = deletion_by_event(&Keys::generate(), EventId::all_zeros());
let request = deletion_by_event(&Keys::generate(), EventId::from_byte_array([0; 32]));
store
.record_request(
&request,
@@ -1070,7 +1069,7 @@ mod tests {
#[tokio::test]
async fn marking_request_used_advances_without_resetting_first_seen_at() {
let store = Tombstones::in_memory();
let request = deletion_by_event(&Keys::generate(), EventId::all_zeros());
let request = deletion_by_event(&Keys::generate(), EventId::from_byte_array([0; 32]));
store
.record_request(
&request,
@@ -1107,7 +1106,7 @@ mod tests {
#[tokio::test]
async fn canonical_metadata_selection_is_deterministic_and_compacted_on_update() {
let store = Tombstones::in_memory();
let request = deletion_by_event(&Keys::generate(), EventId::all_zeros());
let request = deletion_by_event(&Keys::generate(), EventId::from_byte_array([0; 32]));
store
.record_request(
&request,
@@ -1154,7 +1153,7 @@ mod tests {
#[tokio::test]
async fn malformed_and_orphan_metadata_are_ignored_safely() {
let store = Tombstones::in_memory();
let request = deletion_by_event(&Keys::generate(), EventId::all_zeros());
let request = deletion_by_event(&Keys::generate(), EventId::from_byte_array([0; 32]));
let malformed = EventBuilder::new(Kind::from(TOMBSTONE_REQUEST_METADATA_KIND), "")
.tags(vec![Tag::event(request.id)])
.finalize(&store.metadata_signer)
@@ -1166,7 +1165,7 @@ mod tests {
.unwrap()
.is_none());
let orphan_id = EventId::all_zeros();
let orphan_id = EventId::from_byte_array([0; 32]);
let orphan = store
.build_metadata(
orphan_id,
@@ -1187,7 +1186,7 @@ mod tests {
async fn metadata_events_do_not_affect_deletion_or_vanish_queries() {
let store = Tombstones::in_memory();
let keys = Keys::generate();
let target = EventId::all_zeros();
let target = EventId::from_byte_array([0; 32]);
let metadata = store
.build_metadata(
target,
@@ -1215,7 +1214,7 @@ mod tests {
let store = Tombstones::in_memory();
let owner = Keys::generate();
let other = Keys::generate();
let target = EventId::all_zeros();
let target = EventId::from_byte_array([0; 32]);
let request = deletion_by_event(&owner, target);
store
.record_request(
@@ -1243,7 +1242,7 @@ mod tests {
async fn event_deletion_candidates_resolve_duplicate_requests_together() {
let store = Tombstones::in_memory();
let owner = Keys::generate();
let target = EventId::all_zeros();
let target = EventId::from_byte_array([0; 32]);
let mut actionable_ids = Vec::new();
// A single event may have many valid deletion requests. Save their
@@ -1383,7 +1382,7 @@ mod tests {
async fn lifecycle_metadata_survives_lmdb_reopen() {
let directory = tempfile::tempdir().unwrap();
let keys = Keys::generate();
let request = deletion_by_event(&keys, EventId::all_zeros());
let request = deletion_by_event(&keys, EventId::from_byte_array([0; 32]));
let store = Tombstones::open_lmdb(directory.path()).await.unwrap();
store
.record_request(
+5 -9
View File
@@ -2,7 +2,7 @@
///
/// Handles validation of NIP-34 repository announcements (kind 30617)
/// according to GRASP-01 specification.
use nostr_sdk::prelude::{Alphabet, Event, Filter, Kind, PublicKey, SingleLetterTag};
use nostr_sdk::prelude::{Event, Filter, Kind, PublicKey, SingleLetterTag};
use std::collections::HashSet;
use std::time::Duration;
@@ -316,10 +316,7 @@ impl AnnouncementPolicy {
let filter = Filter::new()
.kind(Kind::GitRepoAnnouncement)
.author(*pubkey)
.custom_tag(
SingleLetterTag::lowercase(Alphabet::D),
identifier.to_string(),
);
.custom_tag(SingleLetterTag::LOWERCASE_D, identifier.to_string());
let events: Vec<Event> = match self.ctx.database.query(filter).await {
Ok(events) => events.into_iter().collect(),
@@ -434,10 +431,9 @@ impl AnnouncementPolicy {
author: &PublicKey,
) -> Result<bool, String> {
// Query all announcements with this identifier that are already in the database
let filter = Filter::new().kind(Kind::GitRepoAnnouncement).custom_tag(
SingleLetterTag::lowercase(Alphabet::D),
identifier.to_string(),
);
let filter = Filter::new()
.kind(Kind::GitRepoAnnouncement)
.custom_tag(SingleLetterTag::LOWERCASE_D, identifier.to_string());
let db_announcements: Vec<Event> = match self.ctx.database.query(filter).await {
Ok(events) => events.into_iter().collect(),
+7 -7
View File
@@ -3,7 +3,7 @@
/// Handles validation of events that reference accepted repositories or events
/// (backward references) and events that are referenced by accepted events
/// (forward references).
use nostr_sdk::prelude::{Alphabet, Event, EventId, Filter, Kind, PublicKey, SingleLetterTag};
use nostr_sdk::prelude::{Event, EventId, Filter, Kind, PublicKey, SingleLetterTag};
use super::PolicyContext;
@@ -230,9 +230,9 @@ impl RelatedEventPolicy {
// Check addressable reference tags: a, A, q (with address format)
let addressable_tags = [
SingleLetterTag::lowercase(Alphabet::A), // 'a' - addressable event reference
SingleLetterTag::uppercase(Alphabet::A), // 'A' - uppercase addressable reference
SingleLetterTag::lowercase(Alphabet::Q), // 'q' - quote (can be address or ID)
SingleLetterTag::LOWERCASE_A, // 'a' - addressable event reference
SingleLetterTag::UPPERCASE_A, // 'A' - uppercase addressable reference
SingleLetterTag::LOWERCASE_Q, // 'q' - quote (can be address or ID)
];
for tag_type in &addressable_tags {
@@ -252,9 +252,9 @@ impl RelatedEventPolicy {
let event_id_hex = event.id.to_hex();
let event_id_tags = [
SingleLetterTag::lowercase(Alphabet::E), // 'e' - standard event reference
SingleLetterTag::uppercase(Alphabet::E), // 'E' - NIP-22 root event reference
SingleLetterTag::lowercase(Alphabet::Q), // 'q' - quote reference
SingleLetterTag::LOWERCASE_E, // 'e' - standard event reference
SingleLetterTag::UPPERCASE_E, // 'E' - NIP-22 root event reference
SingleLetterTag::LOWERCASE_Q, // 'q' - quote reference
];
for tag_type in &event_id_tags {
+4 -4
View File
@@ -242,7 +242,7 @@ mod tests {
relays.insert("wss://relay1.com".to_string());
let mut root_events = HashSet::new();
root_events.insert(EventId::all_zeros());
root_events.insert(EventId::from_byte_array([0; 32]));
repo_index.insert(
"repo1".to_string(),
@@ -359,7 +359,7 @@ mod tests {
let mut relays1 = HashSet::new();
relays1.insert("wss://relay1.com".to_string());
let mut root_events1 = HashSet::new();
root_events1.insert(EventId::all_zeros());
root_events1.insert(EventId::from_byte_array([0; 32]));
repo_index.insert(
"repo1".to_string(),
@@ -374,7 +374,7 @@ mod tests {
let mut relays2 = HashSet::new();
relays2.insert("wss://relay1.com".to_string());
let mut root_events2 = HashSet::new();
root_events2.insert(EventId::all_zeros()); // Same event
root_events2.insert(EventId::from_byte_array([0; 32])); // Same event
repo_index.insert(
"repo2".to_string(),
@@ -729,7 +729,7 @@ mod tests {
#[test]
fn test_compute_actions_with_root_events() {
let event_id = EventId::all_zeros();
let event_id = EventId::from_byte_array([0; 32]);
let mut targets = HashMap::new();
targets.insert(
+14 -15
View File
@@ -120,8 +120,7 @@ pub fn state_event_filters_for_our_repos(
// Add #d tags for all identifiers in this chunk
for identifier in chunk {
filter =
filter.custom_tag(SingleLetterTag::lowercase(Alphabet::D), identifier.as_str());
filter = filter.custom_tag(SingleLetterTag::LOWERCASE_D, identifier.as_str());
}
if let Some(ts) = since {
@@ -160,19 +159,19 @@ pub fn tagged_one_of_our_repo_event_filters(
// Lowercase 'a' tag - standard addressable reference
let mut f1 = Filter::new();
for repo in chunk {
f1 = f1.custom_tag(SingleLetterTag::lowercase(Alphabet::A), repo.as_str());
f1 = f1.custom_tag(SingleLetterTag::LOWERCASE_A, repo.as_str());
}
// Uppercase 'A' tag - some clients use this
let mut f2 = Filter::new();
for repo in chunk {
f2 = f2.custom_tag(SingleLetterTag::uppercase(Alphabet::A), repo.as_str());
f2 = f2.custom_tag(SingleLetterTag::UPPERCASE_A, repo.as_str());
}
// Quote 'q' tag - NIP-10 quote references to addressable events
let mut f3 = Filter::new();
for repo in chunk {
f3 = f3.custom_tag(SingleLetterTag::lowercase(Alphabet::Q), repo.as_str());
f3 = f3.custom_tag(SingleLetterTag::LOWERCASE_Q, repo.as_str());
}
if let Some(ts) = since {
@@ -223,19 +222,19 @@ pub fn tagged_one_of_our_root_event_filters(
// Lowercase 'e' tag - standard event reference
let mut f1 = Filter::new();
for event_id in chunk {
f1 = f1.custom_tag(SingleLetterTag::lowercase(Alphabet::E), event_id.as_str());
f1 = f1.custom_tag(SingleLetterTag::LOWERCASE_E, event_id.as_str());
}
// Uppercase 'E' tag - some clients use this
let mut f2 = Filter::new();
for event_id in chunk {
f2 = f2.custom_tag(SingleLetterTag::uppercase(Alphabet::E), event_id.as_str());
f2 = f2.custom_tag(SingleLetterTag::UPPERCASE_E, event_id.as_str());
}
// Quote 'q' tag - NIP-10 quote references to events
let mut f3 = Filter::new();
for event_id in chunk {
f3 = f3.custom_tag(SingleLetterTag::lowercase(Alphabet::Q), event_id.as_str());
f3 = f3.custom_tag(SingleLetterTag::LOWERCASE_Q, event_id.as_str());
}
if let Some(ts) = since {
@@ -439,7 +438,7 @@ mod tests {
fn test_root_event_filters_single_event() {
let mut root_events = HashSet::new();
// Create a valid event ID (all zeros for testing)
root_events.insert(EventId::all_zeros());
root_events.insert(EventId::from_byte_array([0; 32]));
let filters = tagged_one_of_our_root_event_filters(&root_events, None);
@@ -450,11 +449,11 @@ mod tests {
#[test]
fn test_root_event_filters_batching() {
let mut root_events = HashSet::new();
// EventId::all_zeros() will deduplicate, so we need unique IDs
// EventId::from_byte_array([0; 32]) will deduplicate, so we need unique IDs
// For testing purposes, we'll just verify with one ID since HashSet
// deduplicates all_zeros(). In real usage these would be unique.
for _ in 0..250 {
root_events.insert(EventId::all_zeros());
root_events.insert(EventId::from_byte_array([0; 32]));
}
let filters = tagged_one_of_our_root_event_filters(&root_events, None);
@@ -467,7 +466,7 @@ mod tests {
#[test]
fn test_root_event_filters_with_since() {
let mut root_events = HashSet::new();
root_events.insert(EventId::all_zeros());
root_events.insert(EventId::from_byte_array([0; 32]));
let since = Timestamp::from(1700000000);
let filters = tagged_one_of_our_root_event_filters(&root_events, Some(since));
@@ -491,7 +490,7 @@ mod tests {
repos.insert("30617:abc123:repo1".to_string());
let mut root_events = HashSet::new();
root_events.insert(EventId::all_zeros());
root_events.insert(EventId::from_byte_array([0; 32]));
let filters = build_layer2_and_layer3_filters(&repos, &root_events, None);
@@ -517,7 +516,7 @@ mod tests {
let repos: HashSet<String> = HashSet::new();
let mut root_events = HashSet::new();
root_events.insert(EventId::all_zeros());
root_events.insert(EventId::from_byte_array([0; 32]));
let filters = build_layer2_and_layer3_filters(&repos, &root_events, None);
@@ -531,7 +530,7 @@ mod tests {
repos.insert("30617:abc123:repo1".to_string());
let mut root_events = HashSet::new();
root_events.insert(EventId::all_zeros());
root_events.insert(EventId::from_byte_array([0; 32]));
let since = Timestamp::from(1700000000);
let filters = build_layer2_and_layer3_filters(&repos, &root_events, Some(since));
+1 -1
View File
@@ -5488,7 +5488,7 @@ mod tests {
fn group_filters_for_req_respects_count_and_byte_budgets() {
// Many small filters group by the count cap.
let small: Vec<Filter> = (0..25)
.map(|i| Filter::new().id(EventId::all_zeros()).limit(i))
.map(|i| Filter::new().id(EventId::from_byte_array([0; 32])).limit(i))
.collect();
let groups = group_filters_for_req(&small);
assert_eq!(
+6 -6
View File
@@ -972,7 +972,7 @@ mod tests {
#[test]
fn test_build_layer3_comment_kind_1111() {
let keys = Keys::generate();
let parent_id = EventId::all_zeros();
let parent_id = EventId::from_byte_array([0; 32]);
let event = build_layer3_comment_event(&keys, &parent_id, "Test comment", Kind::Comment)
.expect("Should create event");
@@ -990,7 +990,7 @@ mod tests {
#[test]
fn test_build_layer3_comment_kind_1() {
let keys = Keys::generate();
let parent_id = EventId::all_zeros();
let parent_id = EventId::from_byte_array([0; 32]);
let event = build_layer3_comment_event(&keys, &parent_id, "Test reply", Kind::Custom(1))
.expect("Should create event");
@@ -1008,7 +1008,7 @@ mod tests {
#[test]
fn test_build_layer3_reply_with_e_tag() {
let keys = Keys::generate();
let parent_id = EventId::all_zeros();
let parent_id = EventId::from_byte_array([0; 32]);
let event = build_layer3_reply_with_e_tag(&keys, &parent_id, "Reply content")
.expect("Should create event");
@@ -1025,7 +1025,7 @@ mod tests {
#[test]
fn test_build_layer3_comment_with_uppercase_e() {
let keys = Keys::generate();
let parent_id = EventId::all_zeros();
let parent_id = EventId::from_byte_array([0; 32]);
let event = build_layer3_comment_with_uppercase_e_tag(&keys, &parent_id, "Comment content")
.expect("Should create event");
@@ -1042,7 +1042,7 @@ mod tests {
#[test]
fn test_build_layer3_quote_with_q() {
let keys = Keys::generate();
let parent_id = EventId::all_zeros();
let parent_id = EventId::from_byte_array([0; 32]);
let event = build_layer3_quote_with_q_tag(&keys, &parent_id, "Quote content")
.expect("Should create event");
@@ -1578,7 +1578,7 @@ mod sync_helper_tests {
let coord = repo_coord(&keys, "test");
let historic =
build_layer2_issue_event(&keys, &coord, "Historic").expect("Should create event");
let live = build_layer3_reply_with_e_tag(&keys, &EventId::all_zeros(), "Live")
let live = build_layer3_reply_with_e_tag(&keys, &EventId::from_byte_array([0; 32]), "Live")
.expect("Should create event");
// Should panic - both slices provided