From 2fd6555e305bf3cb1592bb4ec46beaa70c56ee94 Mon Sep 17 00:00:00 2001 From: tee8z Date: Sat, 5 Oct 2024 21:33:32 -0400 Subject: [PATCH] add permutation of entry winners and fix to one winning score --- Cargo.lock | 53 +- daemon/Cargo.toml | 2 +- oracle/Cargo.toml | 3 +- oracle/src/db/event_data.rs | 6 +- oracle/src/db/mod.rs | 16 +- oracle/src/db/outcome_generator.rs | 758 +------------------------ oracle/src/oracle.rs | 73 ++- oracle/tests/api/create_event.rs | 14 +- oracle/tests/api/create_event_entry.rs | 6 +- oracle/tests/api/etl_workflow.rs | 300 ++-------- oracle/tests/api/get_events.rs | 13 +- 11 files changed, 159 insertions(+), 1085 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59495a2..73bca8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1011,6 +1011,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -1057,7 +1076,7 @@ dependencies = [ [[package]] name = "daemon" -version = "1.1.0" +version = "1.2.0" dependencies = [ "anyhow", "arrow", @@ -2141,11 +2160,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -2199,9 +2217,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -2294,7 +2312,7 @@ dependencies = [ [[package]] name = "oracle" -version = "1.1.0" +version = "1.2.0" dependencies = [ "anyhow", "axum", @@ -2319,6 +2337,7 @@ dependencies = [ "openssl", "pem-rfc7468", "rand", + "rayon", "regex", "rustix", "scooby", @@ -2781,6 +2800,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.2.16" diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml index 5049471..01fb375 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "daemon" -version = "1.1.0" +version = "1.2.0" edition = "2021" repository = "https://github.com/tee8z/noaa-data-pipeline" diff --git a/oracle/Cargo.toml b/oracle/Cargo.toml index 1081b0f..b356bae 100644 --- a/oracle/Cargo.toml +++ b/oracle/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oracle" -version = "1.1.0" +version = "1.2.0" edition = "2021" repository = "https://github.com/tee8z/noaa-data-pipeline" @@ -34,6 +34,7 @@ num_cpus = "1.16.0" openssl = { version = "0.10.60", features = ["vendored"] } pem-rfc7468 = { version = "0.7.0", features = ["alloc"] } rand = "0.8.5" +rayon = "1.8" regex = "1.10.3" rustix = "0.38.19" scooby = "0.5.0" diff --git a/oracle/src/db/event_data.rs b/oracle/src/db/event_data.rs index c33b700..99d4435 100644 --- a/oracle/src/db/event_data.rs +++ b/oracle/src/db/event_data.rs @@ -351,15 +351,15 @@ impl EventData { .flat_map(|(a, b, c, d, e)| { let temp_low = match c { Some(c) => Value::Text(c.to_string()), - None => Value::Null, + _ => Value::Null, }; let temp_high = match d { Some(d) => Value::Text(d.to_string()), - None => Value::Null, + _ => Value::Null, }; let wind_speed = match e { Some(e) => Value::Text(e.to_string()), - None => Value::Null, + _ => Value::Null, }; vec![ Value::Text(a.to_string()), diff --git a/oracle/src/db/mod.rs b/oracle/src/db/mod.rs index 3f2880c..ba5f7ce 100644 --- a/oracle/src/db/mod.rs +++ b/oracle/src/db/mod.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeMap; - use anyhow::anyhow; use dlctix::bitcoin::{hashes::sha256, XOnlyPublicKey}; use dlctix::musig2::secp256k1::schnorr::Signature; @@ -43,8 +41,6 @@ pub struct CreateEvent { pub number_of_values_per_entry: usize, /// Total number of allowed entries into the event pub total_allowed_entries: usize, - /// Total amount of places that are part of the winnings split - pub number_of_places_win: usize, /// Add a coordinator that will use the event entries in a competition pub coordinator: Option, } @@ -57,7 +53,7 @@ pub struct CreateEventMessage { /// Time at which the attestation will be added to the event, needs to be after the observation date pub signing_date: OffsetDateTime, #[serde(with = "time::serde::rfc3339")] - /// Date of when the weather observations occured (midnight UTC), all entries must be made before this time + /// Date of when the weather observations occurred (midnight UTC), all entries must be made before this time pub observation_date: OffsetDateTime, /// NOAA observation stations used in this event pub locations: Vec, @@ -65,8 +61,6 @@ pub struct CreateEventMessage { pub number_of_values_per_entry: usize, /// Total number of allowed entries into the event pub total_allowed_entries: usize, - /// Total amount of places that are part of the winnings split - pub number_of_places_win: usize, } impl CreateEventMessage { @@ -86,7 +80,6 @@ impl From for CreateEventMessage { locations: value.locations, number_of_values_per_entry: value.number_of_values_per_entry, total_allowed_entries: value.total_allowed_entries, - number_of_places_win: value.number_of_places_win, } } } @@ -138,9 +131,8 @@ impl CreateEventData { event.observation_date.format(&Rfc3339).unwrap() )); } - - let possible_user_outcomes: Vec>> = - generate_ranked_players(event.total_allowed_entries, event.number_of_places_win); + let possible_user_outcomes: Vec> = + generate_winner_permutations(event.total_allowed_entries); info!("user outcomes: {:?}", possible_user_outcomes); let outcome_messages: Vec> = generate_outcome_messages(possible_user_outcomes); @@ -168,7 +160,7 @@ impl CreateEventData { signing_date: event.signing_date, nonce, total_allowed_entries: event.total_allowed_entries as i64, - number_of_places_win: event.number_of_places_win as i64, + number_of_places_win: 1_i64, // Default to 1 winning score to simplify possible outcomes number_of_values_per_entry: event.number_of_values_per_entry as i64, locations: event.clone().locations, event_annoucement, diff --git a/oracle/src/db/outcome_generator.rs b/oracle/src/db/outcome_generator.rs index e3c7758..6c290fe 100644 --- a/oracle/src/db/outcome_generator.rs +++ b/oracle/src/db/outcome_generator.rs @@ -1,754 +1,54 @@ -use std::collections::{BTreeMap, HashMap, HashSet}; - use itertools::Itertools; -use log::info; - -pub fn generate_ranked_players( - number_of_places_win: usize, - total_allowed_entries: usize, -) -> Vec>> { - // add another place to generate the "unranked" values - let matrix = generate_partitions(total_allowed_entries, number_of_places_win + 1); - println!("matrix: {:?}", matrix); - let mut permutations = vec![]; - - for permutation in matrix { - let mut game_results = BTreeMap::new(); - - //can never have first place be empty with later ranks having a value - if permutation[0].len() == 0 { - continue; - } - for (rank, player_indexies) in permutation.iter().enumerate() { - game_results.insert(rank + 1, player_indexies.clone()); - } - permutations.push(game_results); - } - permutations -} - -fn generate_partitions(n: usize, k: usize) -> Vec>> { - let mut partitions = Vec::new(); - - // Generate all partitions of `n` players into up to `k` groups - for num_groups in 1..=k { - // Generate all ways to partition the players into exactly `num_groups` groups - let partition_iter = (0..n).combinations(num_groups - 1); - for breaks in partition_iter { - let mut partition = vec![Vec::new(); num_groups]; - let mut current_group = 0; - for player in 0..n { - if breaks.contains(&player) { - current_group += 1; - } - partition[current_group].push(player); - } - partitions.push(partition); - } - } - - partitions +use rayon::prelude::*; + +pub fn generate_winner_permutations(num_players: usize) -> Vec> { + (0..=num_players) + .into_par_iter() + .flat_map(|r| { + (0..num_players) + .combinations(r) + .map(|v| v.into_iter().collect::>()) + .collect::>() + }) + .collect() } -pub fn generate_outcome_messages( - possible_user_outcomes: Vec>>, -) -> Vec> { +pub fn generate_outcome_messages(possible_user_outcomes: Vec>) -> Vec> { possible_user_outcomes .into_iter() .map(|inner_vec| { inner_vec .into_iter() - .flat_map(|(_, values)| { - values - .iter() - .flat_map(|val| val.to_be_bytes()) - .collect::>() - }) - .collect() + .flat_map(|num| num.to_be_bytes()) + .collect::>() }) .collect() } #[cfg(test)] mod test { - use std::collections::{BTreeMap, HashSet}; - use maplit::btreemap; - - use crate::generate_ranked_players; - - use super::generate_partitions; + use super::generate_winner_permutations; #[test] - fn can_generate_matrix_permutations() { - let total_allowed_entries = 3; // Number of players - let number_of_places_win = 2; // We are only capturing the top 3 rankings - let matrix = generate_partitions(total_allowed_entries, number_of_places_win); - println!("matrix: {:?}", matrix); - let ranked = generate_ranked_players(number_of_places_win, total_allowed_entries); - println!("ranked: {:?}", ranked); - assert_eq!(matrix.is_empty(), false); + fn can_generate_list_of_winners_small() { + let num_players = 5; + let permutations: Vec> = generate_winner_permutations(num_players); + println!("permutations: {:?}", permutations); + assert_eq!(permutations.len(), 32); } #[test] - fn can_generate_matrix_one_winning_rank() { - let expected: Vec>> = vec![ - btreemap! { - 3=>vec![2], - }, - btreemap! { - 2=>vec![2], - }, - btreemap! { - 1=>vec![2], - }, - btreemap! { - 3=>vec![1], - }, - btreemap! { - 2=>vec![1], - }, - btreemap! { - 1=>vec![1], - }, - btreemap! { - 3=>vec![0], - }, - btreemap! { - 2=>vec![0], - }, - btreemap! { - 1=>vec![0], - }, - btreemap! { - 3=>vec![1,2], - }, - btreemap! { - 2=>vec![1,2], - }, - btreemap! { - 1=>vec![1,2], - }, - btreemap! { - 3=>vec![0,2], - }, - btreemap! { - 2=>vec![0,2], - }, - btreemap! { - 1=>vec![0,2], - }, - btreemap! { - 3=>vec![0,1], - }, - btreemap! { - 2=>vec![0,1], - }, - btreemap! { - 1=>vec![0,1], - }, - btreemap! { - 3=>vec![0,1,2], - }, - btreemap! { - 2=>vec![0,1,2], - }, - btreemap! { - 1=>vec![0,1,2], - }, - btreemap! { - 0=>vec![0,1,2] - }, - ]; - - let total_allowed_entries = 3; - let number_of_places_win = 1; - - let matrix = generate_ranked_players(number_of_places_win, total_allowed_entries); - let mut matrix_iter = matrix.iter(); - println!("matrix {:?}", matrix); - for outcome in expected { - println!("expected_outcome {:?}", outcome); - let result = matrix_iter.find(|possible_outcome| **possible_outcome == outcome); - println!("result {:?}", result); - assert_ne!(result, None); - assert_eq!(*(result.unwrap()), outcome); - } + fn can_generate_list_of_winners_default_size() { + let num_players = 20; + let permutations: Vec> = generate_winner_permutations(num_players); + assert_eq!(permutations.len(), 1_048_576); } #[test] - fn can_generate_matrix_three_winning_ranks() { - let expected_matrix: Vec>> = vec![ - btreemap! { - 3=>vec![0,1,2], - }, - btreemap! { - 3=>vec![1,2,3], - }, - btreemap! { - 3=>vec![0,2,3], - }, - btreemap! { - 3=>vec![0,1,2,3], - }, - ////////////// - btreemap! { - 2=>vec![0,1,2], - }, - btreemap! { - 2=>vec![1,2,3], - }, - btreemap! { - 2=>vec![0,2,3], - }, - btreemap! { - 2=>vec![0,1,2,3], - }, - ////////////// - btreemap! { - 1=>vec![0,1,2], - }, - btreemap! { - 1=>vec![1,2,3], - }, - btreemap! { - 1=>vec![0,2,3], - }, - btreemap! { - 1=>vec![0,1,2,3], - }, - ////////////// - btreemap! { - 0=>vec![0,1,2,3], - }, - ////////////// - btreemap! { - 3=>vec![3], - 2=>vec![0,1], - }, - btreemap! { - 3=>vec![3], - 2=>vec![0,2], - }, - btreemap! { - 3=>vec![3], - 2=>vec![1,2], - }, - btreemap! { - 3=>vec![3], - 2=>vec![0,1,2], - }, - ////------ - btreemap! { - 3=>vec![2], - 2=>vec![0,3], - }, - btreemap! { - 3=>vec![2], - 2=>vec![0,1], - }, - btreemap! { - 3=>vec![2], - 2=>vec![1,3], - }, - btreemap! { - 3=>vec![2], - 2=>vec![0,1,3], - }, - ////------ - btreemap! { - 3=>vec![1], - 2=>vec![0,3], - }, - btreemap! { - 3=>vec![1], - 2=>vec![0,2], - }, - btreemap! { - 3=>vec![1], - 2=>vec![2,3], - }, - btreemap! { - 3=>vec![1], - 2=>vec![0,2,3], - }, - ////------ - btreemap! { - 3=>vec![0], - 2=>vec![1,3], - }, - btreemap! { - 3=>vec![0], - 2=>vec![1,2], - }, - btreemap! { - 3=>vec![0], - 2=>vec![2,3], - }, - btreemap! { - 3=>vec![0], - 2=>vec![1,2,3], - }, - ////------ - btreemap! { - 3=>vec![0,2,3], - 2=>vec![1], - }, - btreemap! { - 3=>vec![0,1,3], - 2=>vec![2], - }, - btreemap! { - 3=>vec![0,1,2], - 2=>vec![3], - }, - ////////////// - btreemap! { - 3=>vec![3], - 1=>vec![1,2], - }, - btreemap! { - 3=>vec![3], - 1=>vec![0,2], - }, - btreemap! { - 3=>vec![3,1], - 1=>vec![0,2], - }, - ////////////// - btreemap! { - 3=>vec![3], - 0=>vec![1,2] - }, - btreemap! { - 3=>vec![3], - 0=>vec![0,2] - }, - ////////////// - btreemap! { - 2=>vec![3], - 1=>vec![0,1,2], - }, - btreemap! { - 2=>vec![3], - 1=>vec![1,2], - }, - btreemap! { - 2=>vec![3], - 1=>vec![0,2], - }, - ////////////// - btreemap! { - 2=>vec![3], - 0=>vec![1,2] - }, - btreemap! { - 2=>vec![3], - 0=>vec![0,2] - }, - ////////////// - btreemap! { - 1=>vec![3], - 0=>vec![1,2] - }, - btreemap! { - 1=>vec![3], - 0=>vec![0,2] - }, - btreemap! { - 1=>vec![3], - 0=>vec![0,1,2] - }, - btreemap! { - 1=>vec![2], - 0=>vec![1,3] - }, - btreemap! { - 1=>vec![2], - 0=>vec![0,3] - }, - btreemap! { - 1=>vec![2], - 0=>vec![0,1,3] - }, - btreemap! { - 1=>vec![1], - 0=>vec![2,3] - }, - btreemap! { - 1=>vec![1], - 0=>vec![0,2] - }, - btreemap! { - 1=>vec![1], - 0=>vec![0,2,3] - }, - btreemap! { - 1=>vec![0], - 0=>vec![2,3] - }, - btreemap! { - 1=>vec![0], - 0=>vec![1,3] - }, - btreemap! { - 1=>vec![0], - 0=>vec![1,2,3] - }, - btreemap! { - 1=>vec![0,1], - 0=>vec![2,3] - }, - btreemap! { - 1=>vec![1,2], - 0=>vec![0,3] - }, - btreemap! { - 1=>vec![0,2,3], - 0=>vec![1] - }, - btreemap! { - 1=>vec![1,2,3], - 0=>vec![0] - }, - btreemap! { - 1=>vec![1,0,3], - 0=>vec![2] - }, - btreemap! { - 1=>vec![1,2,0], - 0=>vec![3] - }, - ////////////// - btreemap! { - 3=>vec![3], - 2=>vec![2], - 1=>vec![1], - }, - btreemap! { - 3=>vec![3], - 2=>vec![1], - 1=>vec![2], - }, - btreemap! { - 3=>vec![3,0], - 2=>vec![2], - 1=>vec![1], - }, - btreemap! { - 3=>vec![3], - 2=>vec![2,0], - 1=>vec![1], - }, - btreemap! { - 3=>vec![3], - 2=>vec![2], - 1=>vec![1,0], - }, - ////------ - btreemap! { - 3=>vec![2], - 2=>vec![1], - 1=>vec![3], - }, - btreemap! { - 3=>vec![2], - 2=>vec![3], - 1=>vec![1], - }, - btreemap! { - 3=>vec![2,0], - 2=>vec![3], - 1=>vec![1], - }, - btreemap! { - 3=>vec![2], - 2=>vec![3,0], - 1=>vec![1], - }, - btreemap! { - 3=>vec![2], - 2=>vec![3], - 1=>vec![1,0], - }, - ////------++ - btreemap! { - 3=>vec![2], - 2=>vec![3], - 1=>vec![0], - }, - btreemap! { - 3=>vec![2], - 2=>vec![0], - 1=>vec![3], - }, - btreemap! { - 3=>vec![2,1], - 2=>vec![0], - 1=>vec![3], - }, - btreemap! { - 3=>vec![2], - 2=>vec![0,1], - 1=>vec![3], - }, - btreemap! { - 3=>vec![2], - 2=>vec![0], - 1=>vec![3,1], - }, - ////------ - btreemap! { - 3=>vec![1], - 2=>vec![2], - 1=>vec![3], - }, - btreemap! { - 3=>vec![1], - 2=>vec![3], - 1=>vec![2], - }, - btreemap! { - 3=>vec![1], - 2=>vec![0], - 1=>vec![2], - }, - btreemap! { - 3=>vec![1], - 2=>vec![2], - 1=>vec![0], - }, - btreemap! { - 3=>vec![1], - 2=>vec![0], - 1=>vec![3], - }, - btreemap! { - 3=>vec![1], - 2=>vec![3], - 1=>vec![0], - }, - btreemap! { - 3=>vec![1], - 2=>vec![0,2], - 1=>vec![3], - }, - btreemap! { - 3=>vec![1], - 2=>vec![0], - 1=>vec![3,2], - }, - ////------ - btreemap! { - 3=>vec![0], - 2=>vec![3], - 1=>vec![2], - }, - btreemap! { - 3=>vec![0], - 2=>vec![2], - 1=>vec![3], - }, - btreemap! { - 3=>vec![0], - 2=>vec![3], - 1=>vec![1], - }, - btreemap! { - 3=>vec![0], - 2=>vec![1], - 1=>vec![3], - }, - btreemap! { - 3=>vec![0], - 2=>vec![1,2], - 1=>vec![3], - }, - btreemap! { - 3=>vec![0], - 2=>vec![1], - 1=>vec![3,2], - }, - ////////////// - btreemap! { - 3=>vec![3], - 2=>vec![0], - 0=>vec![2], - }, - btreemap! { - 3=>vec![3], - 2=>vec![2], - 0=>vec![1], - }, - btreemap! { - 3=>vec![3], - 2=>vec![1], - 0=>vec![2] - }, - btreemap! { - 3=>vec![2], - 2=>vec![1], - 0=>vec![3] - }, - btreemap! { - 3=>vec![2], - 2=>vec![3], - 0=>vec![1] - }, - btreemap! { - 3=>vec![2], - 2=>vec![3], - 0=>vec![0], - }, - btreemap! { - 3=>vec![2], - 2=>vec![0], - 0=>vec![3], - }, - btreemap! { - 3=>vec![1], - 2=>vec![2], - 0=>vec![3], - }, - btreemap! { - 3=>vec![1], - 2=>vec![3], - 0=>vec![2], - }, - btreemap! { - 3=>vec![1], - 2=>vec![0], - 0=>vec![2], - }, - btreemap! { - 3=>vec![1], - 2=>vec![2], - 0=>vec![0], - }, - btreemap! { - 3=>vec![1], - 2=>vec![0], - 0=>vec![3], - }, - btreemap! { - 3=>vec![1], - 2=>vec![3], - 0=>vec![0], - }, - btreemap! { - 3=>vec![0], - 2=>vec![3], - 0=>vec![2], - }, - btreemap! { - 3=>vec![0], - 2=>vec![2], - 0=>vec![3], - }, - btreemap! { - 3=>vec![0], - 2=>vec![3], - 0=>vec![1], - }, - btreemap! { - 3=>vec![0], - 2=>vec![1], - 0=>vec![3], - }, - ////////////// - btreemap! { - 2=>vec![3], - 1=>vec![2], - 0=>vec![1], - }, - btreemap! { - 2=>vec![3], - 1=>vec![1], - 0=>vec![2] - }, - btreemap! { - 2=>vec![2], - 1=>vec![1], - 0=>vec![3] - }, - btreemap! { - 2=>vec![2], - 1=>vec![3], - 0=>vec![1] - }, - btreemap! { - 2=>vec![2], - 1=>vec![3], - 0=>vec![0], - }, - btreemap! { - 2=>vec![2], - 1=>vec![0], - 0=>vec![3], - }, - btreemap! { - 2=>vec![1], - 1=>vec![2], - 0=>vec![3], - }, - btreemap! { - 2=>vec![1], - 1=>vec![3], - 0=>vec![2], - }, - btreemap! { - 2=>vec![1], - 1=>vec![0], - 0=>vec![2], - }, - btreemap! { - 2=>vec![1], - 1=>vec![2], - 0=>vec![0], - }, - btreemap! { - 2=>vec![1], - 1=>vec![0], - 0=>vec![3], - }, - btreemap! { - 2=>vec![1], - 1=>vec![3], - 0=>vec![0], - }, - btreemap! { - 2=>vec![0], - 1=>vec![3], - 0=>vec![2], - }, - btreemap! { - 2=>vec![0], - 1=>vec![2], - 0=>vec![3], - }, - btreemap! { - 2=>vec![0], - 1=>vec![3], - 0=>vec![1], - }, - btreemap! { - 2=>vec![0], - 1=>vec![1], - 0=>vec![3], - }, - ////////////// - ]; - let total_allowed_entries = 4; - let number_of_places_win = 3; - let matrix = generate_ranked_players(number_of_places_win, total_allowed_entries); - let mut matrix_iter = matrix.iter(); - println!("matrix {:?}", matrix); - for outcome in expected_matrix { - println!("expected_outcome {:?}", outcome); - let result = matrix_iter.find(|possible_outcome| **possible_outcome == outcome); - println!("result {:?}", result); - assert_ne!(result, None); - assert_eq!(*(result.unwrap()), outcome); - } + fn can_generate_list_of_winners_large() { + let num_players = 25; + let permutations: Vec> = generate_winner_permutations(num_players); + assert_eq!(permutations.len(), 33_554_432); } } diff --git a/oracle/src/oracle.rs b/oracle/src/oracle.rs index ff73841..bac449e 100644 --- a/oracle/src/oracle.rs +++ b/oracle/src/oracle.rs @@ -16,7 +16,6 @@ use nostr::{key::Keys, nips::nip19::ToBech32}; use pem_rfc7468::{decode_vec, encode_string}; use std::{ cmp, - collections::BTreeMap, fs::{metadata, File}, io::{Read, Write}, path::Path, @@ -151,6 +150,17 @@ impl Oracle { } pub async fn create_event(&self, event: CreateEvent) -> Result { + if event.id.get_version_num() != 7 { + return Err(Error::BadEvent(anyhow!( + "event needs to provide a valid Uuidv7 for event id {}", + event.id + ))); + } + if event.total_allowed_entries > 25 { + return Err(Error::BadEvent(anyhow!( + "Max number of allowed entries the oracle can watch is 25" + ))); + } if let Some(coordinator) = event.coordinator.clone() { let message: CreateEventMessage = event.clone().into(); info!("create event: {:?}", message); @@ -516,37 +526,31 @@ impl Oracle { entry_indexies.sort_by_key(|entry| entry.id); entries.sort_by_key(|entry| cmp::Reverse(entry.score)); - let winning_scores: Vec = entries - .iter() - .take(event.number_of_places_win as usize) - .map(|entry| entry.score.unwrap_or_default()) // default means '0' was winning score - .collect(); + // we have set the number_of_places_win to 1 always (ranking can't be done in large groups efficiently at the moment) + let winning_score: i64 = entries[1].score.unwrap_or_default(); // default means '0' was winning score; - let winners: BTreeMap> = - get_winners(entry_indexies.clone(), winning_scores); + let winners: Vec = get_winners(entry_indexies.clone(), winning_score); let winner_bytes: Vec = get_winning_bytes(winners.clone()); if event.signing_date < OffsetDateTime::now_utc() { + info!( + "outcome_messages: {:?}", + event.event_annoucement.outcome_messages + ); + info!("winner_bytes: {:?}", winner_bytes); let outcome_index = event .event_annoucement .outcome_messages .iter() .position(|outcome| *outcome == winner_bytes); - - let winners_str = winners + let winning_entries = winners .iter() - .map(|(score, winning_entry_indexies)| { - let entries: String = winning_entry_indexies - .iter() - .filter_map(|entry_index| entry_indexies.get(entry_index.clone())) - .map(|entry| entry.id.to_string()) - .collect::>() - .join(","); - format!("({}, {})", score, entries) - }) + .filter_map(|entry_index| entry_indexies.get(*entry_index)) + .map(|entry| entry.id.to_string()) .collect::>() .join(","); + let winners_str = format!("({}, {})", winning_score, winning_entries); let Some(index) = outcome_index else { // Something went horribly wrong, use the info from this log line to track refunding users based on DLC expiry (we set to 1 week) @@ -608,39 +612,26 @@ impl Oracle { } } -pub fn get_winners( - entry_indexies: Vec, - winning_scores: Vec, -) -> BTreeMap> { - let mut winners: BTreeMap> = BTreeMap::new(); - for score in winning_scores.clone() { - winners.insert(score as usize, vec![]); - } +pub fn get_winners(entry_indexies: Vec, winning_score: i64) -> Vec { + let mut winners: Vec = Vec::new(); let entry_indexies_iter = entry_indexies.iter(); for (entry_index, entry) in entry_indexies_iter.enumerate() { let Some(score) = entry.score else { continue }; - if winning_scores.contains(&score) { - let Some(rank_winners) = winners.get_mut(&(score as usize)) else { - continue; - }; - rank_winners.push(entry_index); + if winning_score == score { + winners.push(entry_index); } } + winners.sort(); + winners } -pub fn get_winning_bytes(winners: BTreeMap>) -> Vec { +pub fn get_winning_bytes(winners: Vec) -> Vec { winners - .clone() .into_iter() - .flat_map(|(_, values)| { - values - .iter() - .flat_map(|val| val.to_be_bytes()) - .collect::>() - }) - .collect() + .flat_map(|num| num.to_be_bytes()) + .collect::>() } async fn add_only_forecast_data( diff --git a/oracle/tests/api/create_event.rs b/oracle/tests/api/create_event.rs index f40e09a..f5dab42 100644 --- a/oracle/tests/api/create_event.rs +++ b/oracle/tests/api/create_event.rs @@ -28,8 +28,7 @@ async fn can_create_oracle_event() { String::from("PAPG"), String::from("KWMC"), ], - total_allowed_entries: 100, - number_of_places_win: 3, + total_allowed_entries: 5, number_of_values_per_entry: 6, coordinator: None, }; @@ -56,10 +55,6 @@ async fn can_create_oracle_event() { new_event.total_allowed_entries as i64 ); assert_eq!(res.entry_ids.len(), 0); - assert_eq!( - res.number_of_places_win, - new_event.number_of_places_win as i64 - ); assert_eq!( res.number_of_values_per_entry, new_event.number_of_values_per_entry as i64 @@ -87,8 +82,7 @@ async fn can_create_and_get_oracle_event() { String::from("PAPG"), String::from("KWMC"), ], - total_allowed_entries: 100, - number_of_places_win: 3, + total_allowed_entries: 5, number_of_values_per_entry: 6, coordinator: None, }; @@ -149,10 +143,6 @@ async fn can_create_and_get_oracle_event() { new_event.total_allowed_entries as i64 ); assert_eq!(res.entry_ids.len(), 0); - assert_eq!( - res.number_of_places_win, - new_event.number_of_places_win as i64 - ); assert_eq!( res.number_of_values_per_entry, new_event.number_of_values_per_entry as i64 diff --git a/oracle/tests/api/create_event_entry.rs b/oracle/tests/api/create_event_entry.rs index 4f3ab55..664cb34 100644 --- a/oracle/tests/api/create_event_entry.rs +++ b/oracle/tests/api/create_event_entry.rs @@ -26,8 +26,7 @@ async fn can_create_entry_into_event() { String::from("PAPG"), String::from("KWMC"), ], - total_allowed_entries: 100, - number_of_places_win: 3, + total_allowed_entries: 5, number_of_values_per_entry: 6, coordinator: None, }; @@ -93,8 +92,7 @@ async fn can_create_and_get_event_entry() { String::from("PAPG"), String::from("KWMC"), ], - total_allowed_entries: 100, - number_of_places_win: 3, + total_allowed_entries: 10, number_of_values_per_entry: 6, coordinator: None, }; diff --git a/oracle/tests/api/etl_workflow.rs b/oracle/tests/api/etl_workflow.rs index f329e87..573a8fc 100644 --- a/oracle/tests/api/etl_workflow.rs +++ b/oracle/tests/api/etl_workflow.rs @@ -8,8 +8,8 @@ use axum::{ use hyper::{header, Method}; use log::info; use oracle::{ - oracle::{get_winners, get_winning_bytes}, - AddEventEntry, CreateEvent, Event, EventStatus, Forecast, Observation, WeatherChoices, + oracle::get_winning_bytes, AddEventEntry, CreateEvent, Event, EventStatus, Forecast, + Observation, WeatherChoices, }; use serde_json::from_slice; use time::{format_description::well_known::Rfc3339, OffsetDateTime}; @@ -67,10 +67,10 @@ async fn can_get_event_run_etl_and_see_it_signed() { String::from("KWMC"), ], total_allowed_entries: 4, - number_of_places_win: 1, number_of_values_per_entry: 6, coordinator: None, }; + info!("above create event"); let event = test_app.oracle.create_event(new_event_1).await.unwrap(); let entry_1 = AddEventEntry { @@ -254,283 +254,53 @@ async fn can_get_event_run_etl_and_see_it_signed() { 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 + let mut entries_scores_order = res.entries.clone(); + entries_scores_order.sort_by_key(|entry| cmp::Reverse(entry.score)); + info!("entries: {:?}", entries_scores_order); + // 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 mut winning_score_bytes = (4_i64).to_le_bytes().to_vec(); - winning_score_bytes.reverse(); - - let outcome_index = event - .event_annoucement - .outcome_messages + let entry_1_res = entries_scores_order .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); -} - -#[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, - coordinator: None, - }; - 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), - }, - ], - coordinator: None, - }; - 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, - }, - ], - coordinator: 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), - }, - ], - coordinator: None, - }; - 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), - }, - ], - coordinator: None, - }; - test_app - .oracle - .add_event_entry(entry_1.clone()) - .await + .find(|entry| entry.id == entry_1.id) .unwrap(); - test_app - .oracle - .add_event_entry(entry_2.clone()) - .await - .unwrap(); - test_app - .oracle - .add_event_entry(entry_3.clone()) - .await + assert_eq!(entry_1_res.score.unwrap(), 4); + let entry_2_res = entries_scores_order + .iter() + .find(|entry| entry.id == entry_2.id) .unwrap(); - test_app - .oracle - .add_event_entry(entry_4.clone()) - .await + assert_eq!(entry_2_res.score.unwrap(), 3); + let entry_3_res = entries_scores_order + .iter() + .find(|entry| entry.id == entry_3.id) .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()) + assert_eq!(entry_3_res.score.unwrap(), 4); + let entry_4_res = entries_scores_order + .iter() + .find(|entry| entry.id == entry_4.id) .unwrap(); + assert_eq!(entry_4_res.score.unwrap(), 1); - 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()) + let entry_outcome_order = res.entries.clone(); + entries_scores_order.sort_by_key(|entry| entry.id); + let entry_3_index = entry_outcome_order + .iter() + .position(|entry| entry.id == entry_3.id) .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()) + let entry_4_index = entry_outcome_order + .iter() + .position(|entry| entry.id == entry_4.id) .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.clone(); - 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 mut entry_indexies = res.entries.clone(); - entry_indexies.sort_by_key(|entry| entry.id); - - let winners = get_winners(entry_indexies, vec![4_i64]); - let mut winning_score_bytes = get_winning_bytes(winners.clone()); - winning_score_bytes.reverse(); + let mut winners = vec![entry_3_index, entry_4_index]; + winners.sort(); + let winning_bytes = get_winning_bytes(winners); + println!("winning_bytes in test: {:?}", winning_bytes); let outcome_index = event .event_annoucement .outcome_messages .iter() - .position(|outcome| *outcome == winning_score_bytes) + .position(|outcome| *outcome == winning_bytes) .unwrap(); let attested_outcome = res.event_annoucement.attestation_secret( diff --git a/oracle/tests/api/get_events.rs b/oracle/tests/api/get_events.rs index 98230ea..b8aaaea 100644 --- a/oracle/tests/api/get_events.rs +++ b/oracle/tests/api/get_events.rs @@ -27,8 +27,7 @@ async fn can_get_all_events() { String::from("PAPG"), String::from("KWMC"), ], - total_allowed_entries: 100, - number_of_places_win: 3, + total_allowed_entries: 5, number_of_values_per_entry: 6, coordinator: None, }; @@ -42,8 +41,7 @@ async fn can_get_all_events() { String::from("PAPG"), String::from("KJAN"), ], - total_allowed_entries: 100, - number_of_places_win: 3, + total_allowed_entries: 5, number_of_values_per_entry: 6, coordinator: None, }; @@ -57,8 +55,7 @@ async fn can_get_all_events() { String::from("KCRW"), String::from("KDED"), ], - total_allowed_entries: 100, - number_of_places_win: 3, + total_allowed_entries: 5, number_of_values_per_entry: 6, coordinator: None, }; @@ -107,10 +104,6 @@ async fn can_get_all_events() { cur_expect.total_allowed_entries as i64 ); assert_eq!(event_summary.total_entries, 0); - assert_eq!( - event_summary.number_of_places_win, - cur_expect.number_of_places_win as i64 - ); assert!(event_summary.weather.is_empty()); assert!(event_summary.attestation.is_none()); }