Skip to content

Commit 0d2df38

Browse files
committed
Merge #232: Preserve Event de/serialization JSON field order
2 parents e87e6aa + a0f138f commit 0d2df38

File tree

12 files changed

+320
-124
lines changed

12 files changed

+320
-124
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/nostr-database/src/flatbuffers/mod.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,15 @@ impl FlatBufferDecode for Event {
109109
})
110110
.collect::<Result<Vec<Tag>, _>>()?;
111111

112-
Ok(Self {
113-
id: EventId::from_slice(&ev.id().ok_or(Error::NotFound)?.0)?,
114-
pubkey: XOnlyPublicKey::from_slice(&ev.pubkey().ok_or(Error::NotFound)?.0)?,
115-
created_at: Timestamp::from(ev.created_at()),
116-
kind: Kind::from(ev.kind()),
112+
Ok(Self::new(
113+
EventId::from_slice(&ev.id().ok_or(Error::NotFound)?.0)?,
114+
XOnlyPublicKey::from_slice(&ev.pubkey().ok_or(Error::NotFound)?.0)?,
115+
Timestamp::from(ev.created_at()),
116+
Kind::from(ev.kind()),
117117
tags,
118-
content: ev.content().ok_or(Error::NotFound)?.to_owned(),
119-
sig: Signature::from_slice(&ev.sig().ok_or(Error::NotFound)?.0)?,
120-
})
118+
ev.content().ok_or(Error::NotFound)?.to_owned(),
119+
Signature::from_slice(&ev.sig().ok_or(Error::NotFound)?.0)?,
120+
))
121121
}
122122
}
123123

crates/nostr-database/src/index.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! Nostr Database Indexes
66
77
use std::cmp::Ordering;
8-
use std::collections::{BTreeSet, HashMap, HashSet};
8+
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
99
//use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
1010
use std::sync::Arc;
1111

@@ -134,12 +134,12 @@ impl From<[u8; 32]> for PublicKeyPrefix {
134134

135135
#[derive(Default)]
136136
struct FilterIndex {
137-
ids: HashSet<EventId>,
138-
authors: HashSet<PublicKeyPrefix>,
139-
kinds: HashSet<Kind>,
137+
ids: BTreeSet<EventId>,
138+
authors: BTreeSet<PublicKeyPrefix>,
139+
kinds: BTreeSet<Kind>,
140140
since: Option<Timestamp>,
141141
until: Option<Timestamp>,
142-
generic_tags: HashMap<Alphabet, HashSet<GenericTagValue>>,
142+
generic_tags: BTreeMap<Alphabet, BTreeSet<GenericTagValue>>,
143143
}
144144

145145
impl FilterIndex {

crates/nostr/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ std = [
2828
"negentropy/std",
2929
"serde/std",
3030
"serde_json/std",
31+
"serde_json/preserve_order",
3132
"tracing/std",
3233
"url-fork/std",
3334
"wasm-bindgen?/std",

crates/nostr/src/event/mod.rs

Lines changed: 206 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77
88
use alloc::string::String;
99
use alloc::vec::Vec;
10+
use core::cmp::Ordering;
1011
use core::fmt;
12+
use core::hash::{Hash, Hasher};
1113

1214
use bitcoin::secp256k1::schnorr::Signature;
1315
use bitcoin::secp256k1::{self, Message, Secp256k1, Verification, XOnlyPublicKey};
16+
use serde::ser::SerializeStruct;
17+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
1418
use serde_json::Value;
1519

1620
pub mod builder;
@@ -83,7 +87,7 @@ impl From<bitcoin::hashes::hex::Error> for Error {
8387
}
8488

8589
/// [`Event`] struct
86-
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
90+
#[derive(Debug, Clone)]
8791
pub struct Event {
8892
/// Id
8993
pub id: EventId,
@@ -99,9 +103,76 @@ pub struct Event {
99103
pub content: String,
100104
/// Signature
101105
pub sig: Signature,
106+
/// JSON deserialization key order
107+
deser_order: Vec<String>,
108+
}
109+
110+
impl PartialEq for Event {
111+
fn eq(&self, other: &Self) -> bool {
112+
self.id == other.id
113+
&& self.pubkey == other.pubkey
114+
&& self.created_at == other.created_at
115+
&& self.kind == other.kind
116+
&& self.tags == other.tags
117+
&& self.content == other.content
118+
&& self.sig == other.sig
119+
}
120+
}
121+
122+
impl Eq for Event {}
123+
124+
impl PartialOrd for Event {
125+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
126+
Some(self.cmp(other))
127+
}
128+
}
129+
130+
impl Ord for Event {
131+
fn cmp(&self, other: &Self) -> Ordering {
132+
// TODO: cmp all fields?
133+
self.id.cmp(&other.id)
134+
}
135+
}
136+
137+
impl Hash for Event {
138+
fn hash<H: Hasher>(&self, state: &mut H) {
139+
self.id.hash(state);
140+
self.pubkey.hash(state);
141+
self.created_at.hash(state);
142+
self.kind.hash(state);
143+
self.tags.hash(state);
144+
self.content.hash(state);
145+
self.sig.hash(state);
146+
}
102147
}
103148

104149
impl Event {
150+
/// Compose event
151+
pub fn new<I, S>(
152+
id: EventId,
153+
public_key: XOnlyPublicKey,
154+
created_at: Timestamp,
155+
kind: Kind,
156+
tags: I,
157+
content: S,
158+
sig: Signature,
159+
) -> Self
160+
where
161+
I: IntoIterator<Item = Tag>,
162+
S: Into<String>,
163+
{
164+
Self {
165+
id,
166+
pubkey: public_key,
167+
created_at,
168+
kind,
169+
tags: tags.into_iter().collect(),
170+
content: content.into(),
171+
sig,
172+
deser_order: Vec::new(),
173+
}
174+
}
175+
105176
/// Deserialize [`Event`] from [`Value`]
106177
///
107178
/// **This method NOT verify the signature!**
@@ -304,33 +375,89 @@ impl JsonUtil for Event {
304375
}
305376
}
306377

307-
#[cfg(test)]
308-
impl Event {
309-
/// This is just for serde sanity checking
310-
#[allow(clippy::too_many_arguments)]
311-
pub(crate) fn new_dummy<S>(
312-
id: &str,
313-
pubkey: &str,
314-
created_at: Timestamp,
315-
kind: u64,
316-
tags: Vec<Tag>,
317-
content: S,
318-
sig: &str,
319-
) -> Self
378+
#[derive(Serialize, Deserialize)]
379+
struct EventIntermediate {
380+
id: EventId,
381+
pubkey: XOnlyPublicKey,
382+
created_at: Timestamp,
383+
kind: Kind,
384+
tags: Vec<Tag>,
385+
content: String,
386+
sig: Signature,
387+
}
388+
389+
impl From<Event> for EventIntermediate {
390+
fn from(event: Event) -> Self {
391+
Self {
392+
id: event.id,
393+
pubkey: event.pubkey,
394+
created_at: event.created_at,
395+
kind: event.kind,
396+
tags: event.tags,
397+
content: event.content,
398+
sig: event.sig,
399+
}
400+
}
401+
}
402+
403+
impl Serialize for Event {
404+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
320405
where
321-
S: Into<String>,
406+
S: Serializer,
322407
{
323-
use core::str::FromStr;
408+
if self.deser_order.is_empty() {
409+
let event: EventIntermediate = self.clone().into();
410+
event.serialize(serializer)
411+
} else {
412+
let mut s = serializer.serialize_struct("Event", 7)?;
413+
for key in self.deser_order.iter() {
414+
match key.as_str() {
415+
"id" => s.serialize_field("id", &self.id)?,
416+
"pubkey" => s.serialize_field("pubkey", &self.pubkey)?,
417+
"created_at" => s.serialize_field("created_at", &self.created_at)?,
418+
"kind" => s.serialize_field("kind", &self.kind)?,
419+
"tags" => s.serialize_field("tags", &self.tags)?,
420+
"content" => s.serialize_field("content", &self.content)?,
421+
"sig" => s.serialize_field("sig", &self.sig)?,
422+
_ => return Err(serde::ser::Error::custom(format!("Unknown key: {}", key))),
423+
}
424+
}
425+
s.end()
426+
}
427+
}
428+
}
324429

325-
Self {
326-
id: EventId::from_hex(id).unwrap(),
327-
pubkey: XOnlyPublicKey::from_str(pubkey).unwrap(),
430+
impl<'de> Deserialize<'de> for Event {
431+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
432+
where
433+
D: Deserializer<'de>,
434+
{
435+
let value: Value = Value::deserialize(deserializer)?;
436+
437+
let mut deser_order: Vec<String> = Vec::with_capacity(7);
438+
if let Value::Object(map) = &value {
439+
deser_order = map.keys().cloned().collect();
440+
}
441+
442+
let EventIntermediate {
443+
id,
444+
pubkey,
328445
created_at,
329-
kind: Kind::from(kind),
446+
kind,
330447
tags,
331-
content: content.into(),
332-
sig: Signature::from_str(sig).unwrap(),
333-
}
448+
content,
449+
sig,
450+
} = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
451+
Ok(Self {
452+
id,
453+
pubkey,
454+
created_at,
455+
kind,
456+
tags,
457+
content,
458+
sig,
459+
deser_order,
460+
})
334461
}
335462
}
336463

@@ -413,4 +540,60 @@ mod tests {
413540
let event = Event::from_json(r#"{"content":"Think about this.\n\nThe most powerful centralized institutions in the world have been replaced by a protocol that protects the individual. #bitcoin\n\nDo you doubt that we can replace everything else?\n\nBullish on the future of humanity\nnostr:nevent1qqs9ljegkuk2m2ewfjlhxy054n6ld5dfngwzuep0ddhs64gc49q0nmqpzdmhxue69uhhyetvv9ukzcnvv5hx7un8qgsw3mfhnrr0l6ll5zzsrtpeufckv2lazc8k3ru5c3wkjtv8vlwngksrqsqqqqqpttgr27","created_at":1703184271,"id":"38acf9b08d06859e49237688a9fd6558c448766f47457236c2331f93538992c6","kind":1,"pubkey":"e8ed3798c6ffebffa08501ac39e271662bfd160f688f94c45d692d8767dd345a","sig":"f76d5ecc8e7de688ac12b9d19edaacdcffb8f0c8fa2a44c00767363af3f04dbc069542ddc5d2f63c94cb5e6ce701589d538cf2db3b1f1211a96596fabb6ecafe","tags":[["e","5fcb28b72cadab2e4cbf7311f4acf5f6d1a99a1c2e642f6b6f0d5518a940f9ec","","mention"],["p","e8ed3798c6ffebffa08501ac39e271662bfd160f688f94c45d692d8767dd345a","","mention"],["t","bitcoin"],["t","bitcoin"]]}"#).unwrap();
414541
event.verify_id().unwrap();
415542
}
543+
544+
// Test only with `std` feature due to `serde_json` preserve_order feature.
545+
#[test]
546+
#[cfg(feature = "std")]
547+
fn test_event_de_serialization_order_preservation() {
548+
let json = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#;
549+
let event = Event::from_json(json).unwrap();
550+
let reserialized_json = event.as_json();
551+
assert_eq!(json, reserialized_json);
552+
553+
let json = r#"{"kind":3,"pubkey":"f831caf722214748c72db4829986bd0cbb2bb8b3aeade1c959624a52a9629046","content":"","created_at":1698412975,"id":"f55c30722f056e330d8a7a6a9ba1522f7522c0f1ced1c93d78ea833c78a3d6ec","sig":"5092a9ffaecdae7d7794706f085ff5852befdf79df424cc3419bb797bf515ae05d4f19404cb8324b8b4380a4bd497763ac7b0f3b1b63ef4d3baa17e5f5901808","tags":[["p","4ddeb9109a8cd29ba279a637f5ec344f2479ee07df1f4043f3fe26d8948cfef9","",""],["p","bb6fd06e156929649a73e6b278af5e648214a69d88943702f1fb627c02179b95","",""],["p","b8b8210f33888fdbf5cedee9edf13c3e9638612698fe6408aff8609059053420","",""],["p","9dcee4fabcd690dc1da9abdba94afebf82e1e7614f4ea92d61d52ef9cd74e083","",""],["p","3eea9e831fefdaa8df35187a204d82edb589a36b170955ac5ca6b88340befaa0","",""],["p","885238ab4568f271b572bf48b9d6f99fa07644731f288259bd395998ee24754e","",""],["p","568a25c71fba591e39bebe309794d5c15d27dbfa7114cacb9f3586ea1314d126","",""]]}"#;
554+
let event = Event::from_json(json).unwrap();
555+
let reserialized_json = event.as_json();
556+
assert_eq!(
557+
event.deser_order,
558+
vec![
559+
"kind",
560+
"pubkey",
561+
"content",
562+
"created_at",
563+
"id",
564+
"sig",
565+
"tags"
566+
]
567+
);
568+
assert_eq!(json, reserialized_json);
569+
570+
let json = r#"{"content":"[[\"e\",\"fd40fc62d6349408c5b63d364c1f695b435cc596b58cfaa449519fbc5f2a41a4\"],[\"e\",\"a515bc18a06f0a3561075870f488365e71c5e90aa429a82845e9f7f0d66b6119\"],[\"e\",\"0eb6c73ed0af393a6a2fd9d8200534be064af9d244ef4b211e38503853755b57\"],[\"e\",\"1e8115cb2ba0e14eeb79fcb5ce6cb88f2db59e156aae9ad9302e86e8529e5e7c\"],[\"e\",\"6138b278802611f0685a75d5156f7bd3702a2acab4ba3864665901b1ffd58055\"],[\"e\",\"42105a71922acd113d77d876220fc49aabfa38ba9f34d2267e4f1d45d98b8eaf\"],[\"e\",\"dcd64141fa7af67e61fb28d02085e5c50bb0ccb72270b95e983183179903ef54\"],[\"e\",\"802f72b45a14639477a6ad9d89df9926d59e15d20387ab276dbe92dc48ddc21e\"],[\"e\",\"67ccd79069e27330480e1111f939c0770548e4222f4b5bcdf87ea9ec09e37abf\"],[\"e\",\"c45f94f3c8648536333b657287f0820c4ff1857fb1849a8ce8a541762f233063\"],[\"e\",\"afd22572b31ab14d0c6f65880e626d8e7fe20407ef1486e3ef78820be37e27d8\"],[\"e\",\"bd6a1a577ecfc5ba2ac5a391cae8f21a6238a7ad61a4ebcdd2a44ca488dd03c9\"],[\"e\",\"044ac6073a9cf1b723028a7828fdca098bcd0b79e5e58c21e2372c6b48bd67ca\"],[\"e\",\"2585dcecf6033f82d689a6456af2c82e7d5d9d9e64f90e2c7e86a80eb7dc765b\"],[\"e\",\"08a579677eee0b1796060dbd1e71dcc7ad0937be64ca278b61ef4c3dde149252\"],[\"e\",\"3ed3eaa26cdd1a35808775a8f0c6bd432c0dd1b9c2bc326c9dd249ecf2fe0270\"],[\"e\",\"a2bc2e1149d952a9af202529f3bdd4e8f11a9fda1bd2ad5c6dbbc8b83a1ebc2f\"],[\"e\",\"82e5c6ee536832ababb8eba47e1255d8b1820ca360d2c467f2f32fc610fe3047\"],[\"e\",\"1990b084eb9d0d524ff52f7fb2f0e7f1a1fee977b893c191af7893f53acf7d05\"],[\"e\",\"8df981ac84ca018c7972874770dbf19996f28e9c785eac473bab246e2ad92661\"],[\"e\",\"b975c677ee7517d9124ec8d69d3fafee7ddf6b1d291cc19dffd2678c2241f095\"],[\"e\",\"972599d1139da7e33dc39f049656935ae3b576492f1c535a0eda8d10b1eeb27d\"],[\"e\",\"eaaa6e0cda6315fa30841e9124a526c23dc631fcbf0ffc5e166bbd41d3585efa\"],[\"e\",\"e5eb71fe3dc364d51b6bd6cef73009704df5ee90674a54cb16168e78bbf8fa95\"],[\"e\",\"a49dd0610479b1d81b26f84b949d88d19abc4c3a6b86a1b6501ff393e9618700\"]]","created_at":1701278715,"id":"d05e7ae9271fe2d8968cccb67c01e3458dbafa4a415e306d49b22729b088c8a1","kind":6300,"pubkey":"6b37d5dc88c1cbd32d75b713f6d4c2f7766276f51c9337af9d32c8d715cc1b93","sig":"ee590cf98548039ccbeccb246e55310ad14bb0a307452dacca3f9d1760ac5fdb22d1f1bd932c5fc41d97b8cc16d82719c8ad24440b8d99c38ff2eb0486576253","tags":[["status","success"],["request","{\"created_at\":1701278699,\"content\":\"\",\"tags\":[[\"relays\",\"wss://pablof7z.nostr1.com\",\"wss://purplepag.es\",\"wss://nos.lol\",\"wss://relay.f7z.io\",\"wss://relay.damus.io\",\"wss://relay.snort.social\",\"wss://offchain.pub/\",\"wss://nostr-pub.wellorder.net\"],[\"output\",\"text/plain\"],[\"param\",\"user\",\"99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64\"],[\"relays\",\"wss://relay.damus.io\",\"wss://offchain.pub/\",\"wss://pablof7z.nostr1.com\",\"wss://nos.lol\"]],\"kind\":5300,\"pubkey\":\"99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64\",\"id\":\"5635e5dd930b3c831f6ab1e348bb488f3c9aca2f13190e93ab5e5e1e1ba1835e\",\"sig\":\"babbf39cf1875271d99be7319667f6f83349ffa0ad9262a7ca4719b60601e19642763733840fd7cbef2e883a19fd7829102709fb6af25a6d978b82fba2673140\"}"],["e","5635e5dd930b3c831f6ab1e348bb488f3c9aca2f13190e93ab5e5e1e1ba1835e"],["p","99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64"],["p","99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64"]]}"#;
571+
let event = Event::from_json(json).unwrap();
572+
let reserialized_json = event.as_json();
573+
assert_eq!(json, reserialized_json);
574+
}
575+
}
576+
577+
#[cfg(bench)]
578+
mod benches {
579+
use test::{black_box, Bencher};
580+
581+
use super::*;
582+
583+
#[bench]
584+
pub fn deserialize_event(bh: &mut Bencher) {
585+
let json = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#;
586+
bh.iter(|| {
587+
black_box(Event::from_json(json)).unwrap();
588+
});
589+
}
590+
591+
#[bench]
592+
pub fn serialize_event(bh: &mut Bencher) {
593+
let json = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#;
594+
let event = Event::from_json(json).unwrap();
595+
bh.iter(|| {
596+
black_box(event.as_json());
597+
});
598+
}
416599
}

crates/nostr/src/event/partial.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,15 @@ impl PartialEvent {
9797
tags.push(Tag::parse(tag)?);
9898
}
9999

100-
Ok(Event {
101-
id: self.id,
102-
pubkey: self.pubkey,
103-
created_at: missing.created_at,
104-
kind: missing.kind,
100+
Ok(Event::new(
101+
self.id,
102+
self.pubkey,
103+
missing.created_at,
104+
missing.kind,
105105
tags,
106-
content: missing.content,
107-
sig: self.sig,
108-
})
106+
missing.content,
107+
self.sig,
108+
))
109109
}
110110
}
111111

0 commit comments

Comments
 (0)