Use traits to keep event methods dry
This commit is contained in:
@@ -525,6 +525,86 @@ Deserialization does not verify the signature. Parsing is cheap; `verify()`
|
|||||||
is not, and there are many places where you want to read an event off disk
|
is not, and there are many places where you want to read an event off disk
|
||||||
without paying for a signature check.
|
without paying for a signature check.
|
||||||
|
|
||||||
|
## A shared surface across event stages
|
||||||
|
|
||||||
|
There is a lot of overlap between various event structs. A few minimal accessor
|
||||||
|
traits capture read access to various fields so that we can define extensions
|
||||||
|
to events without duplication.
|
||||||
|
|
||||||
|
```rust {file=coracle-lib/src/events.rs}
|
||||||
|
pub trait HasTags {
|
||||||
|
fn tags(&self) -> &Tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasId {
|
||||||
|
fn id(&self) -> &[u8; 32];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasTags for HashedEvent {
|
||||||
|
fn tags(&self) -> &Tags {
|
||||||
|
&self.tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasTags for Event {
|
||||||
|
fn tags(&self) -> &Tags {
|
||||||
|
&self.tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasId for HashedEvent {
|
||||||
|
fn id(&self) -> &[u8; 32] {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasId for Event {
|
||||||
|
fn id(&self) -> &[u8; 32] {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Subsequent chapters will add *extension traits* bounded on these accessors,
|
||||||
|
supply the method bodies as defaults, and close with a blanket impl over every
|
||||||
|
type that satisfies the bound. The logic is written once and reaches both event
|
||||||
|
types — and any event type added later — without another line per struct.
|
||||||
|
The expiration chapter's version reads like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait EventExtensionExpiration: HasTags {
|
||||||
|
fn is_expired(&self) -> bool {
|
||||||
|
is_expired(self.tags())
|
||||||
|
}
|
||||||
|
// ...the rest of the expiration queries
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasTags> EventExtensionExpiration for T {}
|
||||||
|
```
|
||||||
|
|
||||||
|
A trait's methods are only callable where the trait is in scope, so the
|
||||||
|
library gathers these extension traits in a `prelude`. One glob import brings
|
||||||
|
the whole behavior-query surface along:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use coracle_lib::prelude::*;
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust {file=coracle-lib/src/lib.rs}
|
||||||
|
pub mod prelude;
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust {file=coracle-lib/src/prelude.rs}
|
||||||
|
//! One-stop import for the extension traits that add behavior-tag queries to
|
||||||
|
//! events. With `coracle_lib::prelude::*` in scope, methods like
|
||||||
|
//! `is_expired`, `is_protected`, and `get_pow` become callable on
|
||||||
|
//! `HashedEvent` and `Event`.
|
||||||
|
|
||||||
|
pub use crate::events::{HasId, HasTags};
|
||||||
|
```
|
||||||
|
|
||||||
|
Each later chapter that defines an extension trait adds it to this prelude.
|
||||||
|
|
||||||
## What's next
|
## What's next
|
||||||
|
|
||||||
The next chapter introduces kinds — the integer that decides how content
|
The next chapter introduces kinds — the integer that decides how content
|
||||||
|
|||||||
+17
-17
@@ -44,7 +44,7 @@ pub mod pow;
|
|||||||
|
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use crate::events::{Event, HashedEvent, OwnedEvent};
|
use crate::events::{HasId, HasTags, HashedEvent, OwnedEvent};
|
||||||
use crate::tags::{Tag, Tags};
|
use crate::tags::{Tag, Tags};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -119,31 +119,31 @@ commitment was made.
|
|||||||
|
|
||||||
### Methods on event types
|
### Methods on event types
|
||||||
|
|
||||||
Both `HashedEvent` and `Event` carry an id and tags, so both get a
|
Proof of work is the first query that needs the event id as well as its
|
||||||
`get_pow` method that delegates to the free function. This lets callers
|
tags, so its extension trait is bounded on both `HasId` and `HasTags` from
|
||||||
write `event.get_pow() >= 20` regardless of whether the event has been
|
the events chapter. The method body delegates to the free function through
|
||||||
signed yet.
|
those accessors, and the blanket impl puts `get_pow` on every type that
|
||||||
|
satisfies the bound — both `HashedEvent` and `Event` do. Callers write
|
||||||
|
`event.get_pow() >= 20` regardless of whether the event has been signed yet,
|
||||||
|
as long as the trait is in scope (via `coracle_lib::prelude::*`).
|
||||||
|
|
||||||
```rust {file=coracle-lib/src/pow.rs}
|
```rust {file=coracle-lib/src/pow.rs}
|
||||||
impl HashedEvent {
|
/// Proof-of-work queries on any event that has an id and tags.
|
||||||
|
pub trait EventExtensionProofOfWork: HasId + HasTags {
|
||||||
/// Return the validated proof-of-work difficulty of this event.
|
/// Return the validated proof-of-work difficulty of this event.
|
||||||
///
|
///
|
||||||
/// The result is the minimum of the actual leading zero bits in the
|
/// The result is the minimum of the actual leading zero bits in the
|
||||||
/// event id and the difficulty claimed in the nonce tag.
|
/// event id and the difficulty claimed in the nonce tag.
|
||||||
pub fn get_pow(&self) -> u8 {
|
fn get_pow(&self) -> u8 {
|
||||||
get_pow(&self.id, &self.tags)
|
get_pow(self.id(), self.tags())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event {
|
impl<T: HasId + HasTags> EventExtensionProofOfWork for T {}
|
||||||
/// Return the validated proof-of-work difficulty of this event.
|
```
|
||||||
///
|
|
||||||
/// The result is the minimum of the actual leading zero bits in the
|
```rust {file=coracle-lib/src/prelude.rs}
|
||||||
/// event id and the difficulty claimed in the nonce tag.
|
pub use crate::pow::EventExtensionProofOfWork;
|
||||||
pub fn get_pow(&self) -> u8 {
|
|
||||||
get_pow(&self.id, &self.tags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Mining
|
## Mining
|
||||||
|
|||||||
+24
-32
@@ -33,13 +33,13 @@ pub mod expiration;
|
|||||||
//! NIP-40 expiring events: reading the `expiration` tag and checking
|
//! NIP-40 expiring events: reading the `expiration` tag and checking
|
||||||
//! whether an event has expired.
|
//! whether an event has expired.
|
||||||
//!
|
//!
|
||||||
//! The free functions in this module operate on [`Tags`] so they can
|
//! The free functions in this module operate on [`Tags`] so they can be
|
||||||
//! be used wherever a tag slice is in hand. Method versions on
|
//! used wherever a tag slice is in hand. The [`EventExtensionExpiration`]
|
||||||
//! [`HashedEvent`] and [`Event`] delegate to them for a readable call
|
//! trait layers method-call sugar on top for any event type, reachable
|
||||||
//! site. The tag itself is built with [`Tag::expiration`] from the
|
//! through the crate prelude. The tag itself is built with
|
||||||
//! tags module.
|
//! [`Tag::expiration`] from the tags module.
|
||||||
|
|
||||||
use crate::events::{Event, HashedEvent};
|
use crate::events::HasTags;
|
||||||
use crate::tags::Tags;
|
use crate::tags::Tags;
|
||||||
use crate::util::now;
|
use crate::util::now;
|
||||||
```
|
```
|
||||||
@@ -128,45 +128,37 @@ impl Tag {
|
|||||||
|
|
||||||
## Methods on event types
|
## Methods on event types
|
||||||
|
|
||||||
Both `HashedEvent` and `Event` carry a `Tags` field, and callers who
|
Rather than repeat these queries on each event struct, expiration exposes
|
||||||
have an event in hand almost always want to ask questions of its tags
|
them through an extension trait bounded on [`HasTags`]. The method bodies
|
||||||
without a separate import. The method impls delegate to the free
|
live once, as defaults, and a blanket impl makes them available on every
|
||||||
functions verbatim.
|
type that can hand back its tags — `HashedEvent`, `Event`, and anything
|
||||||
|
added later. With `coracle_lib::prelude::*` in scope, `event.is_expired()`
|
||||||
|
reads the same whether the event is hashed or signed.
|
||||||
|
|
||||||
```rust {file=coracle-lib/src/expiration.rs}
|
```rust {file=coracle-lib/src/expiration.rs}
|
||||||
impl HashedEvent {
|
/// Expiration queries on any event that carries tags.
|
||||||
|
pub trait EventExtensionExpiration: HasTags {
|
||||||
/// Return the expiration timestamp of this event, if any.
|
/// Return the expiration timestamp of this event, if any.
|
||||||
pub fn get_expiration(&self) -> Option<u64> {
|
fn get_expiration(&self) -> Option<u64> {
|
||||||
get_expiration(&self.tags)
|
get_expiration(self.tags())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this event has expired relative to the given timestamp.
|
/// Whether this event has expired relative to the given timestamp.
|
||||||
pub fn is_expired_at(&self, now: u64) -> bool {
|
fn is_expired_at(&self, now: u64) -> bool {
|
||||||
is_expired_at(&self.tags, now)
|
is_expired_at(self.tags(), now)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this event has expired relative to the system clock.
|
/// Whether this event has expired relative to the system clock.
|
||||||
pub fn is_expired(&self) -> bool {
|
fn is_expired(&self) -> bool {
|
||||||
is_expired(&self.tags)
|
is_expired(self.tags())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event {
|
impl<T: HasTags> EventExtensionExpiration for T {}
|
||||||
/// Return the expiration timestamp of this event, if any.
|
```
|
||||||
pub fn get_expiration(&self) -> Option<u64> {
|
|
||||||
get_expiration(&self.tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this event has expired relative to the given timestamp.
|
```rust {file=coracle-lib/src/prelude.rs}
|
||||||
pub fn is_expired_at(&self, now: u64) -> bool {
|
pub use crate::expiration::EventExtensionExpiration;
|
||||||
is_expired_at(&self.tags, now)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this event has expired relative to the system clock.
|
|
||||||
pub fn is_expired(&self) -> bool {
|
|
||||||
is_expired(&self.tags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage patterns
|
## Usage patterns
|
||||||
|
|||||||
+14
-12
@@ -48,7 +48,7 @@ pub mod protected;
|
|||||||
//! challenge followed by a pubkey comparison) needs a live, authenticated
|
//! challenge followed by a pubkey comparison) needs a live, authenticated
|
||||||
//! connection and is handled in the relay layer.
|
//! connection and is handled in the relay layer.
|
||||||
|
|
||||||
use crate::events::{Event, HashedEvent};
|
use crate::events::HasTags;
|
||||||
use crate::tags::Tags;
|
use crate::tags::Tags;
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -91,23 +91,25 @@ impl Tag {
|
|||||||
## Methods on event types
|
## Methods on event types
|
||||||
|
|
||||||
A caller holding an event wants to ask whether it is protected without
|
A caller holding an event wants to ask whether it is protected without
|
||||||
reaching for its tags directly. Both `HashedEvent` and `Event` carry a
|
reaching for its tags directly. Following the pattern from expiration, the
|
||||||
`Tags` field, so the method facades delegate to the free function.
|
query is an extension trait bounded on [`HasTags`], so one default method
|
||||||
|
serves every event type. With `coracle_lib::prelude::*` in scope,
|
||||||
|
`event.is_protected()` works on both `HashedEvent` and `Event`.
|
||||||
|
|
||||||
```rust {file=coracle-lib/src/protected.rs}
|
```rust {file=coracle-lib/src/protected.rs}
|
||||||
impl HashedEvent {
|
/// Protected-event queries on any event that carries tags.
|
||||||
|
pub trait EventExtensionProtected: HasTags {
|
||||||
/// Whether this event carries the NIP-70 protected marker.
|
/// Whether this event carries the NIP-70 protected marker.
|
||||||
pub fn is_protected(&self) -> bool {
|
fn is_protected(&self) -> bool {
|
||||||
is_protected(&self.tags)
|
is_protected(self.tags())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event {
|
impl<T: HasTags> EventExtensionProtected for T {}
|
||||||
/// Whether this event carries the NIP-70 protected marker.
|
```
|
||||||
pub fn is_protected(&self) -> bool {
|
|
||||||
is_protected(&self.tags)
|
```rust {file=coracle-lib/src/prelude.rs}
|
||||||
}
|
pub use crate::protected::EventExtensionProtected;
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage patterns
|
## Usage patterns
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use coracle_lib::events::{EventContent, HashedEvent};
|
use coracle_lib::events::{EventContent, HashedEvent};
|
||||||
use coracle_lib::expiration::{get_expiration, is_expired, is_expired_at};
|
use coracle_lib::expiration::{get_expiration, is_expired, is_expired_at};
|
||||||
|
use coracle_lib::prelude::*;
|
||||||
use coracle_lib::keys::SecretKey;
|
use coracle_lib::keys::SecretKey;
|
||||||
use coracle_lib::tags::{Tag, Tags};
|
use coracle_lib::tags::{Tag, Tags};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use coracle_lib::events::{EventContent, OwnedEvent};
|
use coracle_lib::events::{EventContent, OwnedEvent};
|
||||||
use coracle_lib::keys::SecretKey;
|
use coracle_lib::keys::SecretKey;
|
||||||
|
use coracle_lib::prelude::*;
|
||||||
use coracle_lib::pow::{get_leading_zero_bits, get_pow, mine_pow, mine_pow_batch};
|
use coracle_lib::pow::{get_leading_zero_bits, get_pow, mine_pow, mine_pow_batch};
|
||||||
use coracle_lib::tags::{Tag, Tags};
|
use coracle_lib::tags::{Tag, Tags};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use coracle_lib::events::{EventContent, HashedEvent};
|
use coracle_lib::events::{EventContent, HashedEvent};
|
||||||
use coracle_lib::keys::SecretKey;
|
use coracle_lib::keys::SecretKey;
|
||||||
|
use coracle_lib::prelude::*;
|
||||||
use coracle_lib::protected::is_protected;
|
use coracle_lib::protected::is_protected;
|
||||||
use coracle_lib::tags::{Tag, Tags};
|
use coracle_lib::tags::{Tag, Tags};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user