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

Fix list events #27

Merged
merged 2 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 18 additions & 27 deletions oracle/src/db/event_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,14 +598,15 @@ impl EventData {
"observation_date::TEXT",
"locations",
"total_allowed_entries",
"event_entries.total_entries as total_entries",
"COALESCE(event_entries.total_entries,0) as total_entries",
"number_of_places_win",
"number_of_values_per_entry",
"attestation_signature",
"nonce",
))
.from(
"events"
.join("event_entries")
.left_join("event_entries")
.on("event_entries.event_id = events.id"),
);
if let Some(ids) = filter.event_ids.clone() {
Expand Down Expand Up @@ -696,14 +697,14 @@ impl EventData {
"observation_date::TEXT",
"locations",
"total_allowed_entries",
"total_entries",
"COALESCE(event_entries.total_entries, 0) as total_entries",
"number_of_places_win",
"number_of_values_per_entry",
"attestation_signature",
))
.from(
"events"
.join("event_entries")
.left_join("event_entries")
.on("event_entries.event_id = events.id"),
)
.where_("attestation_signature IS NULL"); //Only filter out events that have been signed
Expand Down Expand Up @@ -863,6 +864,11 @@ impl CreateEventData {
let possible_outcome_rankings: Vec<Vec<i64>> = possible_scores
.iter()
.combinations(number_of_places_win)
//Sort possible combinations in desc order
.map(|mut combos| {
combos.sort_by_key(|n| i64::MAX - *n);
combos
})
.filter(|combination| {
// Check if the combination is sorted in descending order, if not filter out of possible outcomes
combination.windows(2).all(|window| window[0] >= window[1])
Expand Down Expand Up @@ -1196,6 +1202,8 @@ pub struct EventSummary {
pub weather: Vec<Weather>,
/// When added it means the oracle has signed that the current data is the final result
pub attestation: Option<MaybeScalar>,
/// Used to sign the result of the event being watched
pub nonce: Scalar,
}

impl EventSummary {
Expand Down Expand Up @@ -1287,34 +1295,17 @@ impl<'a> TryFrom<&Row<'a>> for EventSummary {
}
})
.map_err(|e| duckdb::Error::FromSqlConversionFailure(8, Type::Any, Box::new(e)))?,
weather: row
nonce: row
.get::<usize, Value>(9)
.map(|raw| {
let list_weather = match raw {
Value::List(list) => list,
let blob = match raw {
Value::Blob(val) => val,
_ => vec![],
};
let mut weather_data = vec![];
for value in list_weather.iter() {
if let Value::Struct(data) = value {
let weather: Weather = match data.try_into() {
Ok(val) => val,
Err(e) => return Err(e),
};
weather_data.push(weather)
}
}
Ok(weather_data)
serde_json::from_slice(&blob)
})?
.map_err(|e| {
duckdb::Error::DuckDBFailure(
ffi::Error {
code: ErrorCode::TypeMismatch,
extended_code: 0,
},
Some(e.to_string()),
)
})?,
.map_err(|e| duckdb::Error::FromSqlConversionFailure(9, Type::Any, Box::new(e)))?,
weather: vec![],
};
event_summary.update_status();
Ok(event_summary)
Expand Down
5 changes: 4 additions & 1 deletion oracle/src/oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use dlctix::{
musig2::secp256k1::{rand, PublicKey, SecretKey},
secp::Point,
};
use itertools::Itertools;
use log::{debug, error, info, warn};
use nostr::{key::Keys, nips::nip19::ToBech32};
use pem_rfc7468::{decode_vec, encode_string};
Expand Down Expand Up @@ -501,10 +502,12 @@ impl Oracle {
for event in events.iter_mut() {
let mut entries = self.event_data.get_event_weather_entries(&event.id).await?;
entries.sort_by_key(|entry| cmp::Reverse(entry.score));
// NOTE: there may be issues here if number of unique scores isn't as large as number_of_places_win
let winners: Vec<i64> = entries
.iter()
.take(event.number_of_places_win as usize)
.map(|entry| entry.score.unwrap_or_default()) // default means '0' was winning score
.unique()
.take(event.number_of_places_win as usize)
.collect();

let winner_bytes: Vec<u8> = winners
Expand Down
248 changes: 248 additions & 0 deletions oracle/tests/api/etl_workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,254 @@ async fn can_get_event_run_etl_and_see_it_signed() {
assert_eq!(attested_outcome, res.attestation);
}

#[tokio::test]
async fn can_get_event_run_etl_and_see_it_signed_multiple_winners() {
let mut weather_data = MockWeatherAccess::new();
//called twice per ETL process
weather_data
.expect_forecasts_data()
.times(2)
.returning(|_, _| Ok(mock_forecast_data()));
weather_data
.expect_observation_data()
.times(2)
.returning(|_, _| Ok(mock_observation_data()));

let test_app = spawn_app(Arc::new(weather_data)).await;

// This makes the event window 1 day (what is used by the oracle)
let observation_date = OffsetDateTime::parse("2024-08-12T00:00:00+00:00", &Rfc3339).unwrap();
let signing_date = OffsetDateTime::parse("2024-08-13T00:00:00+00:00", &Rfc3339).unwrap();

let new_event_1 = CreateEvent {
id: Uuid::now_v7(),
observation_date,
signing_date,
locations: vec![
String::from("PFNO"),
String::from("KSAW"),
String::from("PAPG"),
String::from("KWMC"),
],
total_allowed_entries: 4,
number_of_places_win: 2,
number_of_values_per_entry: 6,
};
info!("above create event");
let event = test_app.oracle.create_event(new_event_1).await.unwrap();
let entry_1 = AddEventEntry {
id: Uuid::now_v7(),
event_id: event.id,
expected_observations: vec![
WeatherChoices {
stations: String::from("PFNO"),
temp_low: Some(oracle::ValueOptions::Under),
temp_high: None,
wind_speed: Some(oracle::ValueOptions::Over),
},
WeatherChoices {
stations: String::from("KSAW"),
temp_low: None,
temp_high: None,
wind_speed: Some(oracle::ValueOptions::Over),
},
WeatherChoices {
stations: String::from("KWMC"),
temp_low: Some(oracle::ValueOptions::Par),
temp_high: Some(oracle::ValueOptions::Under),
wind_speed: Some(oracle::ValueOptions::Par),
},
],
};
let entry_2 = AddEventEntry {
id: Uuid::now_v7(),
event_id: event.id,
expected_observations: vec![
WeatherChoices {
stations: String::from("PFNO"),
temp_low: Some(oracle::ValueOptions::Par),
temp_high: None,
wind_speed: Some(oracle::ValueOptions::Par),
},
WeatherChoices {
stations: String::from("KSAW"),
temp_low: Some(oracle::ValueOptions::Par),
temp_high: None,
wind_speed: Some(oracle::ValueOptions::Over),
},
WeatherChoices {
stations: String::from("KWMC"),
temp_low: Some(oracle::ValueOptions::Par),
temp_high: Some(oracle::ValueOptions::Under),
wind_speed: None,
},
],
};
let entry_3 = AddEventEntry {
id: Uuid::now_v7(),
event_id: event.id,
expected_observations: vec![
WeatherChoices {
stations: String::from("PFNO"),
temp_low: Some(oracle::ValueOptions::Par),
temp_high: None,
wind_speed: Some(oracle::ValueOptions::Under),
},
WeatherChoices {
stations: String::from("KSAW"),
temp_low: Some(oracle::ValueOptions::Over),
temp_high: None,
wind_speed: Some(oracle::ValueOptions::Over),
},
WeatherChoices {
stations: String::from("KWMC"),
temp_low: Some(oracle::ValueOptions::Par),
temp_high: None,
wind_speed: Some(oracle::ValueOptions::Under),
},
],
};
let entry_4 = AddEventEntry {
id: Uuid::now_v7(),
event_id: event.id,
expected_observations: vec![
WeatherChoices {
stations: String::from("PFNO"),
temp_low: Some(oracle::ValueOptions::Over),
temp_high: None,
wind_speed: Some(oracle::ValueOptions::Par),
},
WeatherChoices {
stations: String::from("KSAW"),
temp_low: None,
temp_high: Some(oracle::ValueOptions::Under),
wind_speed: Some(oracle::ValueOptions::Over),
},
WeatherChoices {
stations: String::from("KWMC"),
temp_low: Some(oracle::ValueOptions::Par),
temp_high: None,
wind_speed: Some(oracle::ValueOptions::Under),
},
],
};
test_app
.oracle
.add_event_entry(entry_1.clone())
.await
.unwrap();
test_app
.oracle
.add_event_entry(entry_2.clone())
.await
.unwrap();
test_app
.oracle
.add_event_entry(entry_3.clone())
.await
.unwrap();
test_app
.oracle
.add_event_entry(entry_4.clone())
.await
.unwrap();

// 1) get event before etl
let request = Request::builder()
.method(Method::GET)
.uri(format!("/oracle/events/{}", event.id))
.header(header::CONTENT_TYPE, "application/json")
.body(Body::empty())
.unwrap();

let response = test_app
.app
.clone()
.oneshot(request)
.await
.expect("Failed to execute request.");
assert!(response.status().is_success());
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
let res: Event = from_slice(&body).unwrap();
assert_eq!(res.status, EventStatus::Completed);
assert!(res.attestation.is_none());

// 2) request etl to run
let request = Request::builder()
.method(Method::POST)
.uri(String::from("/oracle/update"))
.header(header::CONTENT_TYPE, "application/json")
.body(Body::empty())
.unwrap();

let response = test_app
.app
.clone()
.oneshot(request)
.await
.expect("Failed to execute request.");
assert!(response.status().is_success());

// wait for etl to run in background
sleep(std::time::Duration::from_secs(2)).await;

// 3) get event after etl
let request = Request::builder()
.method(Method::GET)
.uri(format!("/oracle/events/{}", event.id))
.header(header::CONTENT_TYPE, "application/json")
.body(Body::empty())
.unwrap();

let response = test_app
.app
.oneshot(request)
.await
.expect("Failed to execute request.");
assert!(response.status().is_success());
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
let res: Event = from_slice(&body).unwrap();

// Verify the event was signed and status changed
assert_eq!(res.status, EventStatus::Signed);
assert!(res.attestation.is_some());

let mut entries = res.entries;
entries.sort_by_key(|entry| cmp::Reverse(entry.score));
info!("entries: {:?}", entries);
//Make sure the expected entries won and calculated the correct score for each
// We expect a tie between entry_1 and entry_3 with 4 pts
let entry_1_res = entries.iter().find(|entry| entry.id == entry_1.id).unwrap();
assert_eq!(entry_1_res.score.unwrap(), 4);
let entry_2_res = entries.iter().find(|entry| entry.id == entry_2.id).unwrap();
assert_eq!(entry_2_res.score.unwrap(), 3);
let entry_3_res = entries.iter().find(|entry| entry.id == entry_3.id).unwrap();
assert_eq!(entry_3_res.score.unwrap(), 4);
let entry_4_res = entries.iter().find(|entry| entry.id == entry_4.id).unwrap();
assert_eq!(entry_4_res.score.unwrap(), 1);

let winning_score_bytes: Vec<u8> = vec![4, 3]
.into_iter()
.flat_map(|val: i64| val.to_be_bytes())
.collect();

let outcome_index = event
.event_annoucement
.outcome_messages
.iter()
.position(|outcome| *outcome == winning_score_bytes)
.unwrap();

let attested_outcome = res.event_annoucement.attestation_secret(
outcome_index,
test_app.oracle.raw_private_key(),
res.nonce,
);

// Verify the attestation matches what we calculate in the test
assert_eq!(attested_outcome, res.attestation);
}

fn mock_forecast_data() -> Vec<Forecast> {
vec![
Forecast {
Expand Down