Skip to content

Commit

Permalink
feat: NOM-04 support, relay publishing + .well-known
Browse files Browse the repository at this point in the history
  • Loading branch information
ursuscamp committed Nov 12, 2023
1 parent 07ba72e commit 689976b
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 22 deletions.
23 changes: 21 additions & 2 deletions nomen/src/config/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use nostr_sdk::{
};
use sqlx::{sqlite, SqlitePool};

use crate::util::Nsec;

use super::{Cli, ConfigFile};

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -71,6 +73,19 @@ impl Config {
Ok((keys, client))
}

pub async fn nostr_keys_client(
&self,
keys: &nostr_sdk::Keys,
) -> anyhow::Result<nostr_sdk::Client> {
let client = nostr_sdk::Client::with_opts(keys, Options::new().wait_for_send(true));
let relays = self.relays();
for relay in relays {
client.add_relay(relay, None).await?;
}
client.connect().await;
Ok(client)
}

pub async fn nostr_random_client(
&self,
) -> anyhow::Result<(nostr_sdk::Keys, nostr_sdk::Client)> {
Expand Down Expand Up @@ -98,14 +113,18 @@ impl Config {
(self.publish_index() || self.well_known()) && self.file.nostr.secret.is_none()
}

fn publish_index(&self) -> bool {
pub fn publish_index(&self) -> bool {
self.file.nostr.publish.unwrap_or_default()
}

fn well_known(&self) -> bool {
pub fn well_known(&self) -> bool {
self.file.nostr.well_known.unwrap_or_default()
}

pub fn secret_key(&self) -> Option<Nsec> {
self.file.nostr.secret
}

fn rpc_cookie(&self) -> Option<PathBuf> {
self.file.rpc.cookie.clone()
}
Expand Down
4 changes: 3 additions & 1 deletion nomen/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ use sqlx::{Sqlite, SqlitePool};
mod index;
mod name;
mod raw;
pub mod relay_index;
pub mod stats;

static MIGRATIONS: [&str; 14] = [
static MIGRATIONS: [&str; 15] = [
"CREATE TABLE event_log (id INTEGER PRIMARY KEY, created_at, type, data);",
"CREATE TABLE index_height (blockheight INTEGER PRIMARY KEY, blockhash);",
"CREATE TABLE raw_blockchain (id INTEGER PRIMARY KEY, blockhash, txid, blocktime, blockheight, txheight, vout, data, indexed_at);",
Expand Down Expand Up @@ -49,6 +50,7 @@ static MIGRATIONS: [&str; 14] = [
"CREATE TABLE name_events (name, fingerprint, nsid, pubkey, created_at, event_id, records, indexed_at, raw_event);",
"CREATE UNIQUE INDEX name_events_unique_idx ON name_events(name, pubkey);",
"CREATE INDEX name_events_created_at_idx ON name_events(created_at);",
"CREATE TABLE relay_index_queue (name);"
];

pub async fn initialize(config: &Config) -> anyhow::Result<SqlitePool> {
Expand Down
39 changes: 39 additions & 0 deletions nomen/src/db/relay_index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use sqlx::Sqlite;

pub async fn queue(
conn: impl sqlx::Executor<'_, Database = Sqlite> + Copy,
name: &str,
) -> anyhow::Result<()> {
sqlx::query("INSERT INTO relay_index_queue (name) VALUES (?)")
.bind(name)
.execute(conn)
.await?;
Ok(())
}

#[derive(sqlx::FromRow, Debug)]
pub struct Name {
pub name: String,
pub pubkey: String,
pub records: String,
}

pub async fn fetch_all(
conn: impl sqlx::Executor<'_, Database = Sqlite> + Copy,
) -> anyhow::Result<Vec<Name>> {
let results = sqlx::query_as::<_, Name>(
"SELECT vnr.name, vnr.pubkey, COALESCE(vnr.records, '{}') as records
FROM valid_names_records_vw vnr
JOIN relay_index_queue riq ON vnr.name = riq.name;",
)
.fetch_all(conn)
.await?;
Ok(results)
}

pub async fn clear(conn: impl sqlx::Executor<'_, Database = Sqlite> + Copy) -> anyhow::Result<()> {
sqlx::query("DELETE FROM relay_index_queue;")
.execute(conn)
.await?;
Ok(())
}
1 change: 1 addition & 0 deletions nomen/src/subcommands/index/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ async fn index_output(conn: &SqlitePool, index: BlockchainIndex) -> anyhow::Resu
}
}
}
db::relay_index::queue(conn, name).await?;
}
} else {
db::insert_blockchain_index(conn, &index).await?;
Expand Down
1 change: 1 addition & 0 deletions nomen/src/subcommands/index/events/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod event_data;
mod records;
pub mod relay_index;

pub use event_data::*;
pub use records::*;
2 changes: 2 additions & 0 deletions nomen/src/subcommands/index/events/records.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ async fn save_event(pool: &SqlitePool, ed: EventData) -> anyhow::Result<()> {

db::update_v0_index(pool, name.as_ref(), &pubkey, calculated_nsid).await?;

db::relay_index::queue(pool, name.as_ref()).await?;

Ok(())
}

Expand Down
65 changes: 65 additions & 0 deletions nomen/src/subcommands/index/events/relay_index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use std::collections::HashMap;

use nostr_sdk::{EventBuilder, Keys, Tag};
use secp256k1::SecretKey;
use serde::Serialize;
use sqlx::SqlitePool;

use crate::{
config::Config,
db::{self, relay_index::Name},
};

pub async fn publish(config: &Config, pool: &SqlitePool) -> anyhow::Result<()> {
if !config.publish_index() {
return Ok(());
}
let sk: SecretKey = config
.secret_key()
.expect("Missing config validation for secret")
.into();
let keys = Keys::new(sk);
let client = config.nostr_keys_client(&keys).await?;
tracing::info!("Publishing relay index.");
let names = db::relay_index::fetch_all(pool).await?;

send_event(names, keys, &client).await?;

db::relay_index::clear(pool).await?;
client.disconnect().await.ok();
Ok(())
}

async fn send_event(
names: Vec<Name>,
keys: Keys,
client: &nostr_sdk::Client,
) -> Result<(), anyhow::Error> {
for name in names {
let records: HashMap<String, String> = serde_json::from_str(&name.records)?;
let content = Content {
name: name.name.clone(),
pubkey: name.pubkey,
records,
};
let content_serialize = serde_json::to_string(&content)?;
let event = EventBuilder::new(
nostr_sdk::Kind::ParameterizedReplaceable(38301),
content_serialize,
&[Tag::Identifier(name.name.clone())],
)
.to_event(&keys)?;

if client.send_event(event).await.is_err() {
tracing::error!("Unable to broadcast event during relay index publish");
}
}
Ok(())
}

#[derive(Serialize)]
struct Content {
name: String,
pubkey: String,
records: HashMap<String, String>,
}
1 change: 1 addition & 0 deletions nomen/src/subcommands/index/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub async fn index(config: &Config) -> anyhow::Result<()> {
let pool = config.sqlite().await?;
blockchain::index(config, &pool).await?;
events::records(config, &pool).await?;
events::relay_index::publish(config, &pool).await?;

db::save_event(&pool, "index", "").await?;
Ok(())
Expand Down
44 changes: 34 additions & 10 deletions nomen/src/subcommands/server/explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use serde::Deserialize;
use crate::{
db::{self, NameDetails},
subcommands::util::{extend_psbt, name_event},
util::{format_time, KeyVal, Pubkey},
util::{format_time, KeyVal, Npub},
};

use super::{AppState, WebError};
Expand Down Expand Up @@ -129,7 +129,7 @@ pub struct NewNameTemplate {
pub struct NewNameForm {
upgrade: bool,
name: String,
pubkey: Pubkey,
pubkey: Npub,
psbt: String,
}

Expand Down Expand Up @@ -197,7 +197,7 @@ pub struct NewRecordsTemplate {
#[derive(Deserialize)]
pub struct NewRecordsQuery {
name: Option<String>,
pubkey: Option<Pubkey>,
pubkey: Option<Npub>,
}

pub async fn new_records_form(
Expand Down Expand Up @@ -240,7 +240,7 @@ async fn records_from_query(query: &NewRecordsQuery, state: &AppState) -> Result
pub struct NewRecordsForm {
records: String,
name: String,
pubkey: Pubkey,
pubkey: Npub,
}

#[allow(clippy::unused_async)]
Expand Down Expand Up @@ -297,7 +297,7 @@ pub mod transfer {

use crate::{
subcommands::{AppState, WebError},
util::Pubkey,
util::Npub,
};

#[derive(askama::Template)]
Expand All @@ -307,8 +307,8 @@ pub mod transfer {
#[derive(Deserialize)]
pub struct InitiateTransferForm {
name: String,
pubkey: Pubkey,
old_pubkey: Pubkey,
pubkey: Npub,
old_pubkey: Npub,
}

#[allow(clippy::unused_async)]
Expand All @@ -320,8 +320,8 @@ pub mod transfer {
#[template(path = "transfer/sign.html")]
pub struct SignEventTemplate {
name: String,
pubkey: Pubkey,
old_pubkey: Pubkey,
pubkey: Npub,
old_pubkey: Npub,
event: String,
}

Expand All @@ -346,7 +346,7 @@ pub mod transfer {
#[derive(Deserialize)]
pub struct FinalTransferForm {
name: String,
pubkey: Pubkey,
pubkey: Npub,
sig: Signature,
}

Expand Down Expand Up @@ -376,3 +376,27 @@ pub mod transfer {
})
}
}

pub mod well_known {
use axum::{extract::State, Json};
use nostr_sdk::Keys;

use crate::subcommands::{AppState, WebError};

#[allow(clippy::unused_async)]
pub async fn nomen(
State(state): State<AppState>,
) -> anyhow::Result<Json<serde_json::Value>, WebError> {
let sk = state.config.secret_key().ok_or(anyhow::anyhow!(
"Config: secret key required for .well-known"
))?;
let pk = Keys::new(*sk.as_ref()).public_key();
let result = serde_json::json!({
"indexer": {
"pubkey": pk.to_string()
}
});

Ok(Json(result))
}
}
4 changes: 4 additions & 0 deletions nomen/src/subcommands/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ pub async fn start(config: &Config, conn: &SqlitePool) -> anyhow::Result<()> {
.route("/stats", get(explorer::index_stats));
}

if config.well_known() {
app = app.route("/.well-known/nomen.json", get(explorer::well_known::nomen));
}

if config.api() {
let api_router = Router::new()
.route("/names", get(api::names))
Expand Down
4 changes: 2 additions & 2 deletions nomen/src/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
mod keyval;
mod npub;
mod nsec;
mod pubkey;

pub use keyval::*;
pub use npub::*;
pub use nsec::*;
pub use pubkey::*;

use time::{macros::format_description, OffsetDateTime};

Expand Down
14 changes: 7 additions & 7 deletions nomen/src/util/pubkey.rs → nomen/src/util/npub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@ use serde::Serialize;

#[derive(Debug, Clone, Copy, Serialize, serde_with::DeserializeFromStr)]

pub struct Pubkey(XOnlyPublicKey);
pub struct Npub(XOnlyPublicKey);

impl AsRef<XOnlyPublicKey> for Pubkey {
impl AsRef<XOnlyPublicKey> for Npub {
fn as_ref(&self) -> &XOnlyPublicKey {
&self.0
}
}

impl FromStr for Pubkey {
impl FromStr for Npub {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let keys = Keys::from_pk_str(s)?;
Ok(Pubkey(keys.public_key()))
Ok(Npub(keys.public_key()))
}
}

impl Display for Pubkey {
impl Display for Npub {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0.to_string())
}
Expand All @@ -35,14 +35,14 @@ mod tests {

#[test]
fn test_npub() {
let _pubkey: Pubkey = "npub1u50q2x85utgcgqrmv607crvmk8x3k2nvyun84dxlj6034kajje0s2cm3r0"
let _pubkey: Npub = "npub1u50q2x85utgcgqrmv607crvmk8x3k2nvyun84dxlj6034kajje0s2cm3r0"
.parse()
.unwrap();
}

#[test]
fn test_hex() {
let _pubkey: Pubkey = "e51e0518f4e2d184007b669fec0d9bb1cd1b2a6c27267ab4df969f1adbb2965f"
let _pubkey: Npub = "e51e0518f4e2d184007b669fec0d9bb1cd1b2a6c27267ab4df969f1adbb2965f"
.parse()
.unwrap();
}
Expand Down
6 changes: 6 additions & 0 deletions nomen/src/util/nsec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ impl From<SecretKey> for Nsec {
}
}

impl From<Nsec> for SecretKey {
fn from(value: Nsec) -> Self {
value.0
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit 689976b

Please sign in to comment.