Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for numeric events #6

Merged
merged 14 commits into from
Oct 29, 2024
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
*.env
*.env.local
.env
.env.local
.env.local
.idea
.vscode
2 changes: 2 additions & 0 deletions kormir-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ async fn main() -> anyhow::Result<()> {
.route("/attestation/:event_id", get(get_oracle_attestation))
.route("/create-enum", post(create_enum_event))
.route("/sign-enum", post(sign_enum_event))
.route("/create-numeric", post(create_numeric_event))
.route("/sign-numeric", post(sign_numeric_event))
.fallback(fallback)
.layer(Extension(state));

Expand Down
6 changes: 3 additions & 3 deletions kormir-server/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use kormir::error::Error;
use kormir::storage::{OracleEventData, Storage};
use kormir::lightning::util::ser::Writeable;
use nostr::EventId;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;

Expand Down Expand Up @@ -65,7 +64,8 @@ impl PostgresStorage {
.flat_map(|nonce| nonce.outcome_and_sig())
.collect();

let announcement_event_id = event.announcement_event_id().map(|ann| ann.to_string());
let announcement_event_id =
event.announcement_event_id().map(|ann| ann.to_string());
let attestation_event_id = event.attestation_event_id().map(|att| att.to_string());

let data = OracleEventData {
Expand Down Expand Up @@ -215,7 +215,7 @@ impl Storage for PostgresStorage {
async fn save_signatures(
&self,
id: u32,
signatures: HashMap<String, Signature>,
signatures: Vec<(String, Signature)>,
) -> Result<OracleEventData, Error> {
let id = id as i32;
let mut conn = self.db_pool.get().map_err(|_| Error::StorageFailure)?;
Expand Down
6 changes: 1 addition & 5 deletions kormir-server/src/models/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,4 @@ diesel::table! {

diesel::joinable!(event_nonces -> events (event_id));

diesel::allow_tables_to_appear_in_same_query!(
event_nonces,
events,
oracle_metadata,
);
diesel::allow_tables_to_appear_in_same_query!(event_nonces, events, oracle_metadata,);
242 changes: 239 additions & 3 deletions kormir-server/src/routes.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use crate::State;
use axum::extract::Path;
use axum::extract::Query;
use axum::http::StatusCode;
use axum::{Extension, Json};
use bitcoin::key::XOnlyPublicKey;
use dlc_messages::ser_impls::write_as_tlv;
use kormir::lightning::util::ser::Writeable;
use kormir::storage::{OracleEventData, Storage};
use kormir::{OracleAnnouncement, OracleAttestation, Signature};
use nostr::{EventId, JsonUtil};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::time::SystemTime;

pub async fn health_check() -> Result<Json<()>, (StatusCode, String)> {
Expand All @@ -21,15 +25,29 @@ pub async fn get_pubkey(
}

pub async fn list_events(
Query(params): Query<HashMap<String, String>>,
Extension(state): Extension<State>,
) -> Result<Json<Vec<OracleEventData>>, (StatusCode, String)> {
) -> Result<Json<Value>, (StatusCode, String)> {
let events = state.oracle.storage.list_events().await.map_err(|_| {
(
StatusCode::INTERNAL_SERVER_ERROR,
"Failed to list events".to_string(),
)
})?;
Ok(Json(events))

if let Some(format) = params.get("format") {
if format == "json" {
Ok(list_events_json(&events))
} else if format == "hex" {
Ok(list_events_hex(&events))
} else if format == "tlv" {
Ok(list_events_tlv(&events))
} else {
Err((StatusCode::BAD_REQUEST, "Invalid format".into()))
}
} else {
Ok(list_events_json(&events))
}
}

#[derive(Debug, Clone, Deserialize)]
Expand Down Expand Up @@ -164,6 +182,94 @@ pub async fn sign_enum_event(
}
}

#[derive(Debug, Clone, Deserialize)]
pub struct CreateNumericEvent {
pub event_id: String,
pub num_digits: Option<u16>,
pub is_signed: Option<bool>,
pub precision: Option<i32>,
pub unit: String,
pub event_maturity_epoch: u32,
}

async fn create_numeric_event_impl(
state: &State,
body: crate::routes::CreateNumericEvent,
) -> anyhow::Result<String> {
let (id, ann) = state
.oracle
.create_numeric_event(
body.event_id,
body.num_digits.unwrap_or(18),
body.is_signed.unwrap_or(false),
body.precision.unwrap_or(0),
body.unit,
body.event_maturity_epoch,
)
.await?;
let hex = hex::encode(ann.encode());

log::info!("Created numeric event: {hex}");

let relays = state
.client
.relays()
.await
.keys()
.map(|x| x.to_string())
.collect::<Vec<_>>();

let event =
kormir::nostr_events::create_announcement_event(&state.oracle.nostr_keys(), &ann, &relays)?;

log::debug!("Broadcasting nostr event: {}", event.as_json());

state
.oracle
.storage
.add_announcement_event_id(id, event.id)
.await?;

log::debug!(
"Added announcement event id to storage: {}",
event.id.to_hex()
);

state.client.send_event(event).await?;

Ok(hex)
}

pub async fn create_numeric_event(
Extension(state): Extension<State>,
Json(body): Json<crate::routes::CreateNumericEvent>,
) -> Result<Json<String>, (StatusCode, String)> {
if body.num_digits.is_some() && body.num_digits.unwrap() == 0 {
return Err((
StatusCode::BAD_REQUEST,
"Number of digits must be greater than 0".to_string(),
));
}

if body.event_maturity_epoch < now() {
return Err((
StatusCode::BAD_REQUEST,
"Event maturity epoch must be in the future".to_string(),
));
}

match crate::routes::create_numeric_event_impl(&state, body).await {
Ok(hex) => Ok(Json(hex)),
Err(e) => {
eprintln!("Error creating numeric event: {:?}", e);
Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Error creating numeric event".to_string(),
))
}
}
}

pub fn get_oracle_announcement_impl(
state: &State,
event_id: String,
Expand Down Expand Up @@ -244,9 +350,139 @@ pub async fn get_oracle_attestation(
}
}

#[derive(Debug, Clone, Deserialize)]
pub struct SignNumericEvent {
pub id: u32,
pub outcome: i64,
}

async fn sign_numeric_event_impl(
state: &State,
body: crate::routes::SignNumericEvent,
) -> anyhow::Result<String> {
let att = state
.oracle
.sign_numeric_event(body.id, body.outcome)
.await?;
let hex = hex::encode(att.encode());

log::info!("Signed numeric event: {hex}");

let data = state.oracle.storage.get_event(body.id).await?;
let event_id = data
.and_then(|d| {
d.announcement_event_id
.and_then(|s| EventId::from_hex(s).ok())
})
.ok_or_else(|| anyhow::anyhow!("Failed to get announcement event id"))?;

let event =
kormir::nostr_events::create_attestation_event(&state.oracle.nostr_keys(), &att, event_id)?;

log::debug!("Broadcasting nostr event: {}", event.as_json());

state
.oracle
.storage
.add_attestation_event_id(body.id, event.id)
.await?;

log::debug!(
"Added announcement event id to storage: {}",
event.id.to_hex()
);

state.client.send_event(event).await?;

Ok(hex)
}

pub async fn sign_numeric_event(
Extension(state): Extension<State>,
Json(body): Json<crate::routes::SignNumericEvent>,
) -> Result<Json<String>, (StatusCode, String)> {
match crate::routes::sign_numeric_event_impl(&state, body).await {
Ok(hex) => Ok(Json(hex)),
Err(e) => {
eprintln!("Error signing numeric event: {:?}", e);
Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Error signing numeric event".to_string(),
))
}
}
}

fn now() -> u32 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs() as u32
}

fn list_events_json(events: &Vec<OracleEventData>) -> Json<Value> {
Json(serde_json::to_value(events).unwrap())
}

#[derive(Debug, Clone, Serialize)]
struct HexEvent {
pub id: Option<u32>,
pub event_id: String,
pub event_maturity_epoch: u32,
pub announcement: String,
pub attestation: Option<String>,
}

fn list_events_hex(events: &Vec<OracleEventData>) -> Json<Value> {
let hex_events = events
.iter()
.map(|e| {
let attestation = assemble_attestation(e);
HexEvent {
id: e.id,
event_id: e.announcement.oracle_event.event_id.clone(),
event_maturity_epoch: e.announcement.oracle_event.event_maturity_epoch,
announcement: hex::encode(e.announcement.encode()),
attestation: attestation.map(|a| hex::encode(a.encode())),
}
})
.collect::<Vec<_>>();
Json(serde_json::to_value(hex_events).unwrap())
}

fn list_events_tlv(events: &Vec<OracleEventData>) -> Json<Value> {
let tlv_events = events
.iter()
.map(|e| {
let attestation = assemble_attestation(e);
HexEvent {
id: e.id,
event_id: e.announcement.oracle_event.event_id.clone(),
event_maturity_epoch: e.announcement.oracle_event.event_maturity_epoch,
announcement: {
let mut bytes = Vec::new();
write_as_tlv(&e.announcement, &mut bytes).unwrap();
hex::encode(bytes)
},
attestation: attestation.map(|a| {
let mut bytes = Vec::new();
write_as_tlv(&a, &mut bytes).unwrap();
hex::encode(bytes)
}),
}
})
.collect::<Vec<_>>();
Json(serde_json::to_value(tlv_events).unwrap())
}

fn assemble_attestation(e: &OracleEventData) -> Option<OracleAttestation> {
if e.signatures.is_empty() {
None
} else {
Some(OracleAttestation {
oracle_public_key: e.announcement.oracle_public_key,
signatures: e.signatures.iter().map(|x| x.1).collect(),
outcomes: e.signatures.iter().map(|x| x.0.clone()).collect(),
})
}
}
Loading