From e0c928fd0fc6f7b171a22beb619f003a93cc9cc6 Mon Sep 17 00:00:00 2001 From: Andrew McKenzie Date: Thu, 4 Apr 2024 11:24:04 +0100 Subject: [PATCH] track last beacon ts for schedule and recip separately. ensure only one beacon attempt per beaconing window --- .../migrations/15_last_beacon_reciprocity.sql | 8 + iot_verifier/src/last_beacon.rs | 13 +- iot_verifier/src/last_beacon_reciprocity.rs | 53 ++ iot_verifier/src/lib.rs | 1 + iot_verifier/src/poc.rs | 40 +- iot_verifier/src/runner.rs | 7 +- iot_verifier/tests/common/mod.rs | 4 +- iot_verifier/tests/runner_tests.rs | 531 ++++++++++++++++-- 8 files changed, 570 insertions(+), 87 deletions(-) create mode 100644 iot_verifier/migrations/15_last_beacon_reciprocity.sql create mode 100644 iot_verifier/src/last_beacon_reciprocity.rs diff --git a/iot_verifier/migrations/15_last_beacon_reciprocity.sql b/iot_verifier/migrations/15_last_beacon_reciprocity.sql new file mode 100644 index 000000000..8d5c347c2 --- /dev/null +++ b/iot_verifier/migrations/15_last_beacon_reciprocity.sql @@ -0,0 +1,8 @@ +create table last_beacon_recip ( + id bytea primary key not null, + timestamp timestamptz not null +); +-- seed beacon_recip with timestamps from last_beacon +insert into last_beacon_recip (id, timestamp) +select id, timestamp from last_beacon +where timestamp > now() - interval '7 day'; diff --git a/iot_verifier/src/last_beacon.rs b/iot_verifier/src/last_beacon.rs index 97aeadcfd..ba5476415 100644 --- a/iot_verifier/src/last_beacon.rs +++ b/iot_verifier/src/last_beacon.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, Utc}; use helium_crypto::PublicKeyBinary; use serde::{Deserialize, Serialize}; -use sqlx::{postgres::PgRow, FromRow, Row}; +use sqlx::{postgres::PgRow, FromRow, Postgres, Row, Transaction}; #[derive(Deserialize, Serialize, Debug)] pub struct LastBeacon { @@ -86,14 +86,11 @@ impl LastBeacon { Ok(height) } - pub async fn update_last_timestamp<'c, E>( - executor: E, + pub async fn update_last_timestamp( + txn: &mut Transaction<'_, Postgres>, id: &PublicKeyBinary, timestamp: DateTime, - ) -> anyhow::Result<()> - where - E: sqlx::Executor<'c, Database = sqlx::Postgres>, - { + ) -> anyhow::Result<()> { let _ = sqlx::query( r#" insert into last_beacon (id, timestamp) @@ -104,7 +101,7 @@ impl LastBeacon { ) .bind(id.as_ref()) .bind(timestamp) - .execute(executor) + .execute(txn) .await?; Ok(()) } diff --git a/iot_verifier/src/last_beacon_reciprocity.rs b/iot_verifier/src/last_beacon_reciprocity.rs new file mode 100644 index 000000000..279d093b8 --- /dev/null +++ b/iot_verifier/src/last_beacon_reciprocity.rs @@ -0,0 +1,53 @@ +use chrono::{DateTime, Utc}; +use helium_crypto::PublicKeyBinary; +use serde::{Deserialize, Serialize}; +use sqlx::{postgres::PgRow, FromRow, Postgres, Row, Transaction}; + +#[derive(Deserialize, Serialize, Debug)] +pub struct LastBeaconReciprocity { + pub id: PublicKeyBinary, + pub timestamp: DateTime, +} + +impl FromRow<'_, PgRow> for LastBeaconReciprocity { + fn from_row(row: &PgRow) -> sqlx::Result { + Ok(Self { + id: row.get::, &str>("id").into(), + timestamp: row.get::, &str>("timestamp"), + }) + } +} + +impl LastBeaconReciprocity { + pub async fn get<'c, E>(executor: E, id: &PublicKeyBinary) -> anyhow::Result> + where + E: sqlx::Executor<'c, Database = Postgres>, + { + Ok(sqlx::query_as::<_, LastBeaconReciprocity>( + r#" select * from last_beacon_recip where id = $1;"#, + ) + .bind(id.as_ref()) + .fetch_optional(executor) + .await?) + } + + pub async fn update_last_timestamp( + txn: &mut Transaction<'_, Postgres>, + id: &PublicKeyBinary, + timestamp: DateTime, + ) -> anyhow::Result<()> { + let _ = sqlx::query( + r#" + insert into last_beacon_recip (id, timestamp) + values ($1, $2) + on conflict (id) do update set + timestamp = EXCLUDED.timestamp + "#, + ) + .bind(id.as_ref()) + .bind(timestamp) + .execute(txn) + .await?; + Ok(()) + } +} diff --git a/iot_verifier/src/lib.rs b/iot_verifier/src/lib.rs index f82541673..98e7e1440 100644 --- a/iot_verifier/src/lib.rs +++ b/iot_verifier/src/lib.rs @@ -4,6 +4,7 @@ pub mod gateway_cache; pub mod gateway_updater; pub mod hex_density; pub mod last_beacon; +pub mod last_beacon_reciprocity; pub mod last_witness; pub mod loader; pub mod meta; diff --git a/iot_verifier/src/poc.rs b/iot_verifier/src/poc.rs index 2a4392e16..62c498eed 100644 --- a/iot_verifier/src/poc.rs +++ b/iot_verifier/src/poc.rs @@ -3,6 +3,7 @@ use crate::{ gateway_cache::{GatewayCache, GatewayCacheError}, hex_density::HexDensityMap, last_beacon::LastBeacon, + last_beacon_reciprocity::LastBeaconReciprocity, last_witness::LastWitness, region_cache::RegionCache, witness_updater::WitnessUpdater, @@ -148,7 +149,7 @@ impl Poc { }; // we have beaconer info, proceed to verifications let last_beacon = LastBeacon::get(&self.pool, &beaconer_pub_key).await?; - match do_beacon_verifications( + let result = match do_beacon_verifications( deny_list, self.entropy_start, self.entropy_end, @@ -164,24 +165,35 @@ impl Poc { .get(beaconer_metadata.location) .await .unwrap_or(*DEFAULT_TX_SCALE); - // update 'last beacon' timestamp if the beacon has passed regular validations - // but only if there has been at least one witness report - if !self.witness_reports.is_empty() { - LastBeacon::update_last_timestamp( - &self.pool, - &beaconer_pub_key, - self.beacon_report.received_timestamp, - ) - .await? - } - Ok(VerifyBeaconResult::valid(beaconer_info, tx_scale)) + VerifyBeaconResult::valid(beaconer_info, tx_scale) } - Err(invalid_response) => Ok(VerifyBeaconResult::invalid( + Err(invalid_response) => VerifyBeaconResult::invalid( invalid_response.reason, invalid_response.details, beaconer_info, - )), + ), + }; + let mut txn = self.pool.begin().await?; + // update 'last beacon' timestamp irrespective of whether the beacon is valid or not + LastBeacon::update_last_timestamp( + &mut txn, + &beaconer_pub_key, + self.beacon_report.received_timestamp, + ) + .await?; + // update 'last beacon reciprocity' timestamp if the beacon has passed regular validations + // and has at least one witness report + if result.result == VerificationStatus::Valid && !self.witness_reports.is_empty() { + LastBeaconReciprocity::update_last_timestamp( + &mut txn, + &beaconer_pub_key, + self.beacon_report.received_timestamp, + ) + .await? } + txn.commit().await?; + + Ok(result) } pub async fn verify_witnesses( diff --git a/iot_verifier/src/runner.rs b/iot_verifier/src/runner.rs index 946695c6c..4e0656ec7 100644 --- a/iot_verifier/src/runner.rs +++ b/iot_verifier/src/runner.rs @@ -1,7 +1,7 @@ use crate::{ gateway_cache::GatewayCache, hex_density::HexDensityMap, - last_beacon::LastBeacon, + last_beacon_reciprocity::LastBeaconReciprocity, poc::{Poc, VerifyBeaconResult}, poc_report::Report, region_cache::RegionCache, @@ -567,8 +567,9 @@ where &self, report: &IotVerifiedWitnessReport, ) -> anyhow::Result { - let last_beacon = LastBeacon::get(&self.pool, &report.report.pub_key).await?; - Ok(last_beacon.map_or(false, |lw| { + let last_beacon_recip = + LastBeaconReciprocity::get(&self.pool, &report.report.pub_key).await?; + Ok(last_beacon_recip.map_or(false, |lw| { report.received_timestamp - lw.timestamp < *RECIPROCITY_WINDOW })) } diff --git a/iot_verifier/tests/common/mod.rs b/iot_verifier/tests/common/mod.rs index c257c752d..c21a6d759 100644 --- a/iot_verifier/tests/common/mod.rs +++ b/iot_verifier/tests/common/mod.rs @@ -21,7 +21,7 @@ use iot_config::{ }; use iot_verifier::{ entropy::Entropy, - last_beacon::LastBeacon, + last_beacon_reciprocity::LastBeaconReciprocity, last_witness::LastWitness, poc_report::{InsertBindings, IotStatus, Report, ReportType}, }; @@ -311,7 +311,7 @@ pub async fn inject_last_beacon( gateway: PublicKeyBinary, ts: DateTime, ) -> anyhow::Result<()> { - LastBeacon::update_last_timestamp(&mut *txn, &gateway, ts).await + LastBeaconReciprocity::update_last_timestamp(&mut *txn, &gateway, ts).await } #[allow(dead_code)] diff --git a/iot_verifier/tests/runner_tests.rs b/iot_verifier/tests/runner_tests.rs index 6ebbaef48..cd0fca038 100644 --- a/iot_verifier/tests/runner_tests.rs +++ b/iot_verifier/tests/runner_tests.rs @@ -193,6 +193,64 @@ async fn valid_beacon_and_witness(pool: PgPool) -> anyhow::Result<()> { PublicKeyBinary::from(valid_witness.pub_key.clone()), PublicKeyBinary::from_str(common::WITNESS1).unwrap() ); + // assert the witness reports status + assert_eq!( + valid_witness_report.status, + VerificationStatus::Valid as i32 + ); + Ok(()) +} + +#[sqlx::test] +async fn confirm_valid_reports_unmodified(pool: PgPool) -> anyhow::Result<()> { + let mut ctx = TestContext::setup(pool.clone(), *BEACON_INTERVAL).await?; + let now = ctx.entropy_ts; + + // test with a valid beacon and a valid witness + // confirm the s3 reports are passed through unmodified + let beacon_to_inject = common::create_valid_beacon_report(common::BEACONER1, ctx.entropy_ts); + let witness_to_inject = common::create_valid_witness_report(common::WITNESS1, ctx.entropy_ts); + common::inject_beacon_report(pool.clone(), beacon_to_inject.clone()).await?; + common::inject_witness_report(pool.clone(), witness_to_inject.clone()).await?; + + // inject last beacons and witness reports into the DB + // avoid the reports declared invalid due to reciprocity check + // when setting the last time consider the beacon interval setup + let mut txn = pool.begin().await?; + common::inject_last_beacon( + &mut txn, + beacon_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_witness( + &mut txn, + beacon_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_beacon( + &mut txn, + witness_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_witness( + &mut txn, + witness_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + txn.commit().await?; + + ctx.runner.handle_db_tick().await?; + + let valid_poc = ctx.valid_pocs.receive_valid_poc().await; + assert_eq!(1, valid_poc.selected_witnesses.len()); + assert_eq!(0, valid_poc.unselected_witnesses.len()); + let valid_beacon = valid_poc.beacon_report.unwrap().report.clone().unwrap(); + let valid_witness_report = valid_poc.selected_witnesses[0].clone(); + let valid_witness = valid_witness_report.report.unwrap(); // assert the beacon and witness reports outputted to filestore // are unmodified from those submitted assert_eq!( @@ -203,11 +261,424 @@ async fn valid_beacon_and_witness(pool: PgPool) -> anyhow::Result<()> { valid_witness, LoraWitnessReportReqV1::from(witness_to_inject.clone()) ); + Ok(()) +} + +#[sqlx::test] +async fn confirm_invalid_reports_unmodified(pool: PgPool) -> anyhow::Result<()> { + let mut ctx = TestContext::setup(pool.clone(), *BEACON_INTERVAL).await?; + let now = Utc::now(); + + // test with an invalid beacon and a valid witness + // confirm the s3 reports are passed through unmodified + let beacon_to_inject = + common::create_valid_beacon_report(common::UNKNOWN_GATEWAY1, ctx.entropy_ts); + let witness_to_inject = common::create_valid_witness_report(common::WITNESS1, ctx.entropy_ts); + common::inject_beacon_report(pool.clone(), beacon_to_inject.clone()).await?; + common::inject_witness_report(pool.clone(), witness_to_inject.clone()).await?; + + let mut txn = pool.begin().await?; + common::inject_last_beacon( + &mut txn, + witness_to_inject.report.pub_key.clone(), + now - ChronoDuration::hours(1), + ) + .await?; + common::inject_last_witness( + &mut txn, + witness_to_inject.report.pub_key.clone(), + now - ChronoDuration::hours(1), + ) + .await?; + txn.commit().await?; + + ctx.runner.handle_db_tick().await?; + + let invalid_beacon_report = ctx.invalid_beacons.receive_invalid_beacon().await; + let invalid_witness_report = ctx.invalid_witnesses.receive_invalid_witness().await; + let invalid_beacon = invalid_beacon_report.report.clone().unwrap(); + let invalid_witness = invalid_witness_report.clone().report.unwrap(); + // assert the beacon and witness reports outputted to filestore + // are unmodified from those submitted + assert_eq!( + invalid_beacon, + LoraBeaconReportReqV1::from(beacon_to_inject.clone()) + ); + assert_eq!( + invalid_witness, + LoraWitnessReportReqV1::from(witness_to_inject.clone()) + ); + + Ok(()) +} + +#[sqlx::test] +async fn confirm_valid_beacon_invalid_witness_reports_unmodified( + pool: PgPool, +) -> anyhow::Result<()> { + let mut ctx = TestContext::setup(pool.clone(), *BEACON_INTERVAL).await?; + let now = ctx.entropy_ts; + + // test with an valid beacon and an invalid witness + // confirm the s3 reports are passed through unmodified + let beacon_to_inject = common::create_valid_beacon_report(common::BEACONER3, ctx.entropy_ts); + let witness_to_inject = + common::create_valid_witness_report(common::NO_METADATA_GATEWAY1, ctx.entropy_ts); + common::inject_beacon_report(pool.clone(), beacon_to_inject.clone()).await?; + common::inject_witness_report(pool.clone(), witness_to_inject.clone()).await?; + + let mut txn = pool.begin().await?; + common::inject_last_beacon( + &mut txn, + beacon_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_witness( + &mut txn, + beacon_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_beacon( + &mut txn, + witness_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_witness( + &mut txn, + witness_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + txn.commit().await?; + + ctx.runner.handle_db_tick().await?; + + let valid_poc = ctx.valid_pocs.receive_valid_poc().await; + assert_eq!(0, valid_poc.selected_witnesses.len()); + assert_eq!(1, valid_poc.unselected_witnesses.len()); + let valid_beacon = valid_poc.beacon_report.unwrap().report.clone().unwrap(); + let invalid_witness_report = valid_poc.unselected_witnesses[0].clone(); + let invalid_witness = invalid_witness_report.report.clone().unwrap(); + // assert the beacon and witness reports outputted to filestore + // are unmodified from those submitted + assert_eq!( + valid_beacon, + LoraBeaconReportReqV1::from(beacon_to_inject.clone()) + ); + assert_eq!( + invalid_witness, + LoraWitnessReportReqV1::from(witness_to_inject.clone()) + ); + Ok(()) +} + +#[sqlx::test] +async fn valid_beacon_irregular_schedule_with_witness(pool: PgPool) -> anyhow::Result<()> { + let mut ctx = TestContext::setup(pool.clone(), *BEACON_INTERVAL).await?; + let now = ctx.entropy_ts; + + // submit a valid beacon and a valid witness + // then test with a second beacon from same beaconer + // this will fail as it is too soon after the first + // only one beacon per window is allowed + let beacon_to_inject = common::create_valid_beacon_report(common::BEACONER1, ctx.entropy_ts); + let witness_to_inject = common::create_valid_witness_report(common::WITNESS1, ctx.entropy_ts); + common::inject_beacon_report(pool.clone(), beacon_to_inject.clone()).await?; + common::inject_witness_report(pool.clone(), witness_to_inject.clone()).await?; + + // inject last beacons and witness reports into the DB + // avoid the reports declared invalid due to reciprocity check + // when setting the last time consider the beacon interval setup + let mut txn = pool.begin().await?; + common::inject_last_beacon( + &mut txn, + beacon_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_witness( + &mut txn, + beacon_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_beacon( + &mut txn, + witness_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_witness( + &mut txn, + witness_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + txn.commit().await?; + + ctx.runner.handle_db_tick().await?; + + let valid_poc = ctx.valid_pocs.receive_valid_poc().await; + assert_eq!(1, valid_poc.selected_witnesses.len()); + assert_eq!(0, valid_poc.unselected_witnesses.len()); + let valid_beacon = valid_poc.beacon_report.unwrap().report.clone().unwrap(); + let valid_witness_report = valid_poc.selected_witnesses[0].clone(); + let valid_witness = valid_witness_report.report.unwrap(); + // assert the pubkeys in the outputted reports + // match those which we injected + assert_eq!( + PublicKeyBinary::from(valid_beacon.pub_key.clone()), + PublicKeyBinary::from_str(common::BEACONER1).unwrap() + ); + assert_eq!( + PublicKeyBinary::from(valid_witness.pub_key.clone()), + PublicKeyBinary::from_str(common::WITNESS1).unwrap() + ); // assert the witness reports status assert_eq!( valid_witness_report.status, VerificationStatus::Valid as i32 ); + + // submit the second beacon report + let beacon_to_inject = common::create_valid_beacon_report(common::BEACONER1, ctx.entropy_ts); + let witness_to_inject = common::create_valid_witness_report(common::WITNESS1, ctx.entropy_ts); + common::inject_beacon_report(pool.clone(), beacon_to_inject.clone()).await?; + common::inject_witness_report(pool.clone(), witness_to_inject.clone()).await?; + ctx.runner.handle_db_tick().await?; + + let invalid_beacon_report = ctx.invalid_beacons.receive_invalid_beacon().await; + let invalid_witness_report = ctx.invalid_witnesses.receive_invalid_witness().await; + let invalid_beacon = invalid_beacon_report.report.clone().unwrap(); + let invalid_witness = invalid_witness_report.clone().report.unwrap(); + // assert the pubkeys in the outputted reports + // match those which we injected + assert_eq!( + PublicKeyBinary::from(invalid_beacon.pub_key.clone()), + PublicKeyBinary::from_str(common::BEACONER1).unwrap() + ); + assert_eq!( + PublicKeyBinary::from(invalid_witness.pub_key.clone()), + PublicKeyBinary::from_str(common::WITNESS1).unwrap() + ); + // assert the invalid details + assert_eq!( + InvalidReason::IrregularInterval as i32, + invalid_beacon_report.reason + ); + assert_eq!( + InvalidReason::IrregularInterval as i32, + invalid_witness_report.reason + ); + assert_eq!( + InvalidParticipantSide::Beaconer as i32, + invalid_witness_report.participant_side + ); + Ok(()) +} + +#[sqlx::test] +async fn valid_beacon_irregular_schedule_no_witness(pool: PgPool) -> anyhow::Result<()> { + let mut ctx = TestContext::setup(pool.clone(), *BEACON_INTERVAL).await?; + let now = ctx.entropy_ts; + + // submit a valid beacon and no witnesses + // then test with a second beacon from same beaconer + // this will fail as it is too soon after the first + // only one beacon per window is allowed + let beacon_to_inject = common::create_valid_beacon_report(common::BEACONER1, ctx.entropy_ts); + common::inject_beacon_report(pool.clone(), beacon_to_inject.clone()).await?; + + // inject last beacons reports into the DB + // avoid the reports declared invalid due to reciprocity check + // when setting the last time consider the beacon interval setup + let mut txn = pool.begin().await?; + common::inject_last_beacon( + &mut txn, + beacon_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_witness( + &mut txn, + beacon_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + txn.commit().await?; + + ctx.runner.handle_db_tick().await?; + + let valid_poc = ctx.valid_pocs.receive_valid_poc().await; + assert_eq!(0, valid_poc.selected_witnesses.len()); + assert_eq!(0, valid_poc.unselected_witnesses.len()); + let valid_beacon = valid_poc.beacon_report.unwrap().report.clone().unwrap(); + // assert the pubkeys in the outputted reports + // match those which we injected + assert_eq!( + PublicKeyBinary::from(valid_beacon.pub_key.clone()), + PublicKeyBinary::from_str(common::BEACONER1).unwrap() + ); + // assert the beacon report outputted to filestore + // are unmodified from those submitted + assert_eq!( + valid_beacon, + LoraBeaconReportReqV1::from(beacon_to_inject.clone()) + ); + + // submit the second beacon report + let beacon_to_inject = common::create_valid_beacon_report(common::BEACONER1, ctx.entropy_ts); + let witness_to_inject = common::create_valid_witness_report(common::WITNESS1, ctx.entropy_ts); + common::inject_beacon_report(pool.clone(), beacon_to_inject.clone()).await?; + common::inject_witness_report(pool.clone(), witness_to_inject.clone()).await?; + ctx.runner.handle_db_tick().await?; + + let invalid_beacon_report = ctx.invalid_beacons.receive_invalid_beacon().await; + let invalid_witness_report = ctx.invalid_witnesses.receive_invalid_witness().await; + let invalid_beacon = invalid_beacon_report.report.clone().unwrap(); + let invalid_witness = invalid_witness_report.clone().report.unwrap(); + // assert the pubkeys in the outputted reports + // match those which we injected + assert_eq!( + PublicKeyBinary::from(invalid_beacon.pub_key.clone()), + PublicKeyBinary::from_str(common::BEACONER1).unwrap() + ); + assert_eq!( + PublicKeyBinary::from(invalid_witness.pub_key.clone()), + PublicKeyBinary::from_str(common::WITNESS1).unwrap() + ); + // assert the invalid details + assert_eq!( + InvalidReason::IrregularInterval as i32, + invalid_beacon_report.reason + ); + assert_eq!( + InvalidReason::IrregularInterval as i32, + invalid_witness_report.reason + ); + assert_eq!( + InvalidParticipantSide::Beaconer as i32, + invalid_witness_report.participant_side + ); + Ok(()) +} + +#[sqlx::test] +async fn invalid_beacon_irregular_schedule_with_witness(pool: PgPool) -> anyhow::Result<()> { + let mut ctx = TestContext::setup(pool.clone(), *BEACON_INTERVAL).await?; + let now = ctx.entropy_ts; + + // submit an invalid beacon and a valid witness + // beacon is invalid due to expired entropy + // then test with a second beacon from same beaconer + // this will fail as it is too soon after the first + // only one beacon per window is allowed + // even if the first beacon is invalid + let beacon_to_inject = common::create_valid_beacon_report( + common::BEACONER1, + ctx.entropy_ts - Duration::from_secs(100), + ); + let witness_to_inject = common::create_valid_witness_report(common::WITNESS1, ctx.entropy_ts); + common::inject_beacon_report(pool.clone(), beacon_to_inject.clone()).await?; + common::inject_witness_report(pool.clone(), witness_to_inject.clone()).await?; + + // inject last beacons and witness reports into the DB + // avoid the reports declared invalid due to reciprocity check + // when setting the last time consider the beacon interval setup + let mut txn = pool.begin().await?; + common::inject_last_beacon( + &mut txn, + beacon_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_witness( + &mut txn, + beacon_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_beacon( + &mut txn, + witness_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + common::inject_last_witness( + &mut txn, + witness_to_inject.report.pub_key.clone(), + now - (*BEACON_INTERVAL + ChronoDuration::hours(2)), + ) + .await?; + txn.commit().await?; + + ctx.runner.handle_db_tick().await?; + + let invalid_beacon_report = ctx.invalid_beacons.receive_invalid_beacon().await; + let invalid_witness_report = ctx.invalid_witnesses.receive_invalid_witness().await; + let invalid_beacon = invalid_beacon_report.report.clone().unwrap(); + let invalid_witness = invalid_witness_report.clone().report.unwrap(); + // assert the pubkeys in the outputted reports + // match those which we injected + assert_eq!( + PublicKeyBinary::from(invalid_beacon.pub_key.clone()), + PublicKeyBinary::from_str(common::BEACONER1).unwrap() + ); + assert_eq!( + PublicKeyBinary::from(invalid_witness.pub_key.clone()), + PublicKeyBinary::from_str(common::WITNESS1).unwrap() + ); + // assert the invalid details + assert_eq!( + InvalidReason::EntropyExpired as i32, + invalid_beacon_report.reason + ); + assert_eq!( + InvalidReason::EntropyExpired as i32, + invalid_witness_report.reason + ); + assert_eq!( + InvalidParticipantSide::Beaconer as i32, + invalid_witness_report.participant_side + ); + + // submit the second beacon report + let beacon_to_inject = common::create_valid_beacon_report(common::BEACONER1, ctx.entropy_ts); + let witness_to_inject = common::create_valid_witness_report(common::WITNESS1, ctx.entropy_ts); + common::inject_beacon_report(pool.clone(), beacon_to_inject.clone()).await?; + common::inject_witness_report(pool.clone(), witness_to_inject.clone()).await?; + ctx.runner.handle_db_tick().await?; + + let invalid_beacon_report = ctx.invalid_beacons.receive_invalid_beacon().await; + let invalid_witness_report = ctx.invalid_witnesses.receive_invalid_witness().await; + let invalid_beacon = invalid_beacon_report.report.clone().unwrap(); + let invalid_witness = invalid_witness_report.clone().report.unwrap(); + // assert the pubkeys in the outputted reports + // match those which we injected + assert_eq!( + PublicKeyBinary::from(invalid_beacon.pub_key.clone()), + PublicKeyBinary::from_str(common::BEACONER1).unwrap() + ); + assert_eq!( + PublicKeyBinary::from(invalid_witness.pub_key.clone()), + PublicKeyBinary::from_str(common::WITNESS1).unwrap() + ); + // assert the invalid details + assert_eq!( + InvalidReason::IrregularInterval as i32, + invalid_beacon_report.reason + ); + assert_eq!( + InvalidReason::IrregularInterval as i32, + invalid_witness_report.reason + ); + assert_eq!( + InvalidParticipantSide::Beaconer as i32, + invalid_witness_report.participant_side + ); Ok(()) } @@ -250,7 +721,6 @@ async fn valid_beacon_gateway_not_found(pool: PgPool) -> anyhow::Result<()> { assert_eq!(1, valid_poc.unselected_witnesses.len()); let valid_beacon = valid_poc.beacon_report.unwrap().report.clone().unwrap(); let invalid_witness_report = valid_poc.unselected_witnesses[0].clone(); - let invalid_witness = invalid_witness_report.report.clone().unwrap(); // assert the pubkeys in the outputted reports // match those which we injected assert_eq!( @@ -270,16 +740,6 @@ async fn valid_beacon_gateway_not_found(pool: PgPool) -> anyhow::Result<()> { InvalidParticipantSide::Witness as i32, invalid_witness_report.participant_side ); - // assert the beacon and witness reports outputted to filestore - // are unmodified from those submitted - assert_eq!( - valid_beacon, - LoraBeaconReportReqV1::from(beacon_to_inject.clone()) - ); - assert_eq!( - invalid_witness, - LoraWitnessReportReqV1::from(witness_to_inject.clone()) - ); // assert the witness reports status assert_eq!( invalid_witness_report.status, @@ -336,7 +796,6 @@ async fn invalid_witness_no_metadata(pool: PgPool) -> anyhow::Result<()> { assert_eq!(1, valid_poc.unselected_witnesses.len()); let valid_beacon = valid_poc.beacon_report.unwrap().report.clone().unwrap(); let invalid_witness_report = valid_poc.unselected_witnesses[0].clone(); - let invalid_witness = invalid_witness_report.report.clone().unwrap(); // assert the pubkeys in the outputted reports // match those which we injected assert_eq!( @@ -361,16 +820,6 @@ async fn invalid_witness_no_metadata(pool: PgPool) -> anyhow::Result<()> { InvalidParticipantSide::Witness as i32, invalid_witness_report.participant_side ); - // assert the beacon and witness reports outputted to filestore - // are unmodified from those submitted - assert_eq!( - valid_beacon, - LoraBeaconReportReqV1::from(beacon_to_inject.clone()) - ); - assert_eq!( - invalid_witness, - LoraWitnessReportReqV1::from(witness_to_inject.clone()) - ); Ok(()) } @@ -433,16 +882,6 @@ async fn invalid_beacon_no_gateway_found(pool: PgPool) -> anyhow::Result<()> { InvalidParticipantSide::Beaconer as i32, invalid_witness_report.participant_side ); - // assert the beacon and witness reports outputted to filestore - // are unmodified from those submitted - assert_eq!( - invalid_beacon, - LoraBeaconReportReqV1::from(beacon_to_inject.clone()) - ); - assert_eq!( - invalid_witness, - LoraWitnessReportReqV1::from(witness_to_inject.clone()) - ); Ok(()) } @@ -460,7 +899,6 @@ async fn invalid_beacon_gateway_not_found_no_witnesses(pool: PgPool) -> anyhow:: ctx.runner.handle_db_tick().await?; let invalid_beacon_report = ctx.invalid_beacons.receive_invalid_beacon().await; - let invalid_beacon = invalid_beacon_report.report.clone().unwrap(); // assert the pubkeys in the outputted reports // match those which we injected assert_eq!( @@ -472,12 +910,6 @@ async fn invalid_beacon_gateway_not_found_no_witnesses(pool: PgPool) -> anyhow:: InvalidReason::GatewayNotFound as i32, invalid_beacon_report.reason ); - // assert the beacon report outputted to filestore - // is unmodified from those submitted - assert_eq!( - invalid_beacon, - LoraBeaconReportReqV1::from(beacon_to_inject.clone()) - ); Ok(()) } @@ -570,16 +1002,6 @@ async fn valid_beacon_and_witness_no_beacon_reciprocity(pool: PgPool) -> anyhow: InvalidParticipantSide::Beaconer as i32, invalid_witness.participant_side ); - // assert the beacon and witness reports outputted to filestore - // are unmodified from those submitted - assert_eq!( - invalid_beacon.report.unwrap(), - LoraBeaconReportReqV1::from(beacon_to_inject.clone()) - ); - assert_eq!( - invalid_witness.report.unwrap(), - LoraWitnessReportReqV1::from(witness_to_inject.clone()) - ); Ok(()) } @@ -620,7 +1042,6 @@ async fn valid_beacon_and_witness_no_witness_reciprocity(pool: PgPool) -> anyhow assert_eq!(1, valid_poc.unselected_witnesses.len()); let valid_beacon = valid_poc.beacon_report.unwrap().report.clone().unwrap(); let invalid_witness_report = valid_poc.unselected_witnesses[0].clone(); - let invalid_witness = invalid_witness_report.report.clone().unwrap(); // assert the pubkeys in the outputted reports // match those which we injected assert_eq!( @@ -645,16 +1066,6 @@ async fn valid_beacon_and_witness_no_witness_reciprocity(pool: PgPool) -> anyhow InvalidParticipantSide::Witness as i32, invalid_witness_report.participant_side ); - // assert the beacon and witness reports outputted to filestore - // are unmodified from those submitted - assert_eq!( - valid_beacon, - LoraBeaconReportReqV1::from(beacon_to_inject.clone()) - ); - assert_eq!( - invalid_witness, - LoraWitnessReportReqV1::from(witness_to_inject.clone()) - ); Ok(()) }