From 5ae3b1f26ee9f4db5115367e0f4be1e17f9cb72b Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Tue, 27 Jun 2023 19:47:04 +0530 Subject: [PATCH] feat: store pow performance statistics against statistics --- db/db-core/src/lib.rs | 24 +++++++ db/db-core/src/tests.rs | 8 +++ .../20230627132800_mcaptcha_pow_analytics.sql | 13 ++++ db/db-sqlx-maria/sqlx-data.json | 61 +++++++++++++++++ db/db-sqlx-maria/src/lib.rs | 66 +++++++++++++++++++ .../20230627133023_mcaptcha_pow_analytics.sql | 8 +++ db/db-sqlx-postgres/sqlx-data.json | 47 +++++++++++++ db/db-sqlx-postgres/src/lib.rs | 66 +++++++++++++++++++ 8 files changed, 293 insertions(+) create mode 100644 db/db-sqlx-maria/migrations/20230627132800_mcaptcha_pow_analytics.sql create mode 100644 db/db-sqlx-postgres/migrations/20230627133023_mcaptcha_pow_analytics.sql diff --git a/db/db-core/src/lib.rs b/db/db-core/src/lib.rs index 8c33d95f..b30e5e1a 100644 --- a/db/db-core/src/lib.rs +++ b/db/db-core/src/lib.rs @@ -250,6 +250,30 @@ pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase { /// fetch PoWConfig confirms async fn fetch_confirm(&self, user: &str, key: &str) -> DBResult>; + + /// record PoW timing + async fn analysis_save( + &self, + captcha_id: &str, + d: &PerformanceAnalytics, + ) -> DBResult<()>; + + /// fetch PoW analytics + async fn analytics_fetch( + &self, + captcha_id: &str, + ) -> DBResult>; +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] +/// Proof-of-Work CAPTCHA performance analytics +pub struct PerformanceAnalytics { + /// time taken to generate proof + pub time: u32, + /// difficulty factor for which the proof was generated + pub difficulty_factor: u32, + /// worker/client type: wasm, javascript, python, etc. + pub worker_type: String, } #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] diff --git a/db/db-core/src/tests.rs b/db/db-core/src/tests.rs index 2ca6786b..a2d19f03 100644 --- a/db/db-core/src/tests.rs +++ b/db/db-core/src/tests.rs @@ -260,6 +260,14 @@ pub async fn database_works<'a, T: MCDatabase>( db.record_solve(c.key).await.unwrap(); db.record_confirm(c.key).await.unwrap(); + let analytics = PerformanceAnalytics { + time: 0, + difficulty_factor: 0, + worker_type: "wasm".into(), + }; + db.analysis_save(c.key, &analytics).await.unwrap(); + assert_eq!(db.analytics_fetch(c.key).await.unwrap(), vec![analytics]); + assert_eq!(db.fetch_solve(p.username, c.key).await.unwrap().len(), 1); assert_eq!( db.fetch_config_fetched(p.username, c.key) diff --git a/db/db-sqlx-maria/migrations/20230627132800_mcaptcha_pow_analytics.sql b/db/db-sqlx-maria/migrations/20230627132800_mcaptcha_pow_analytics.sql new file mode 100644 index 00000000..2cc2678e --- /dev/null +++ b/db/db-sqlx-maria/migrations/20230627132800_mcaptcha_pow_analytics.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS mcaptcha_pow_analytics ( + ID INT auto_increment, + PRIMARY KEY(ID), + config_id INTEGER NOT NULL, + time INTEGER NOT NULL, + difficulty_factor INTEGER NOT NULL, + worker_type VARCHAR(100) NOT NULL UNIQUE, + CONSTRAINT `fk_mcaptcha_config_id_pow_analytics` + FOREIGN KEY (config_id) + REFERENCES mcaptcha_config (config_id) + ON DELETE CASCADE + ON UPDATE CASCADE +); diff --git a/db/db-sqlx-maria/sqlx-data.json b/db/db-sqlx-maria/sqlx-data.json index 35887e6f..9a6934cd 100644 --- a/db/db-sqlx-maria/sqlx-data.json +++ b/db/db-sqlx-maria/sqlx-data.json @@ -277,6 +277,57 @@ }, "query": "DELETE FROM mcaptcha_levels \n WHERE config_id = (\n SELECT config_id FROM mcaptcha_config where captcha_key= (?) \n AND user_id = (\n SELECT ID from mcaptcha_users WHERE name = ?\n )\n )" }, + "7fde24630da96b8c4fd9d6120a54b4fae9db4f3c1c1bc15ebb2c0e94831ff9af": { + "describe": { + "columns": [ + { + "name": "time", + "ordinal": 0, + "type_info": { + "char_set": 63, + "flags": { + "bits": 4097 + }, + "max_size": 11, + "type": "Long" + } + }, + { + "name": "difficulty_factor", + "ordinal": 1, + "type_info": { + "char_set": 63, + "flags": { + "bits": 4097 + }, + "max_size": 11, + "type": "Long" + } + }, + { + "name": "worker_type", + "ordinal": 2, + "type_info": { + "char_set": 224, + "flags": { + "bits": 4101 + }, + "max_size": 400, + "type": "VarString" + } + } + ], + "nullable": [ + false, + false, + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "SELECT time, difficulty_factor, worker_type FROM mcaptcha_pow_analytics\n WHERE \n config_id = (\n SELECT \n config_id FROM mcaptcha_config \n WHERE \n captcha_key = ?\n )\n ORDER BY ID" + }, "89386c46668a2653a54687e65958af5cf7a8da268339a1f5a379ede47b3c6d2a": { "describe": { "columns": [], @@ -873,6 +924,16 @@ }, "query": "SELECT name, password FROM mcaptcha_users WHERE email = ?" }, + "f987c4568ab28271d87af47f473b18cf41130a483333e81d5f50199758cbb98b": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 4 + } + }, + "query": "INSERT INTO mcaptcha_pow_analytics \n (config_id, time, difficulty_factor, worker_type)\n VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?, ?, ?)" + }, "fc717ff0827ccfaa1cc61a71cc7f71c348ebb03d35895c54b011c03121ad2385": { "describe": { "columns": [], diff --git a/db/db-sqlx-maria/src/lib.rs b/db/db-sqlx-maria/src/lib.rs index ed6e8cf5..b57befde 100644 --- a/db/db-sqlx-maria/src/lib.rs +++ b/db/db-sqlx-maria/src/lib.rs @@ -895,6 +895,72 @@ impl MCDatabase for Database { Ok(Date::dates_to_unix(records)) } + + /// record PoW timing + async fn analysis_save( + &self, + captcha_id: &str, + d: &PerformanceAnalytics, + ) -> DBResult<()> { + let _ = sqlx::query!( + "INSERT INTO mcaptcha_pow_analytics + (config_id, time, difficulty_factor, worker_type) + VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?, ?, ?)", + captcha_id, + d.time as i32, + d.difficulty_factor as i32, + &d.worker_type, + ) + .execute(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + Ok(()) + } + + /// fetch PoW analytics + async fn analytics_fetch( + &self, + captcha_id: &str, + ) -> DBResult> { + struct P { + time: i32, + difficulty_factor: i32, + worker_type: String, + } + + impl From

for PerformanceAnalytics { + fn from(v: P) -> Self { + Self { + time: v.time as u32, + difficulty_factor: v.difficulty_factor as u32, + worker_type: v.worker_type, + } + } + } + + let mut c = sqlx::query_as!( + P, + "SELECT time, difficulty_factor, worker_type FROM mcaptcha_pow_analytics + WHERE + config_id = ( + SELECT + config_id FROM mcaptcha_config + WHERE + captcha_key = ? + ) + ORDER BY ID", + &captcha_id, + ) + .fetch_all(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + let mut res = Vec::with_capacity(c.len()); + for i in c.drain(0..) { + res.push(i.into()) + } + + Ok(res) + } } #[derive(Clone)] diff --git a/db/db-sqlx-postgres/migrations/20230627133023_mcaptcha_pow_analytics.sql b/db/db-sqlx-postgres/migrations/20230627133023_mcaptcha_pow_analytics.sql new file mode 100644 index 00000000..b979aed1 --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20230627133023_mcaptcha_pow_analytics.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS mcaptcha_pow_analytics ( + config_id INTEGER references mcaptcha_config(config_id) ON DELETE CASCADE, + time INTEGER NOT NULL, + difficulty_factor INTEGER NOT NULL, + worker_type VARCHAR(100) NOT NULL UNIQUE, + ID SERIAL PRIMARY KEY NOT NULL + +); diff --git a/db/db-sqlx-postgres/sqlx-data.json b/db/db-sqlx-postgres/sqlx-data.json index 3b4cb791..917c5c78 100644 --- a/db/db-sqlx-postgres/sqlx-data.json +++ b/db/db-sqlx-postgres/sqlx-data.json @@ -493,6 +493,21 @@ }, "query": "SELECT EXISTS (SELECT 1 from mcaptcha_users WHERE name = $1)" }, + "af47990880a92c63d1cf5192203899c72621479dc6bb47859fb4498264b78033": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text", + "Int4", + "Int4", + "Varchar" + ] + } + }, + "query": "INSERT INTO mcaptcha_pow_analytics \n (config_id, time, difficulty_factor, worker_type)\n VALUES ((SELECT config_id FROM mcaptcha_config WHERE key = $1), $2, $3, $4)" + }, "b97d810814fbeb2df19f47bcfa381bc6fb7ac6832d040b377cf4fca2ca896cfb": { "describe": { "columns": [], @@ -611,6 +626,38 @@ }, "query": "DELETE FROM mcaptcha_users WHERE name = ($1)" }, + "d4ca28f0d895ef832d5cc1bc56c17e94c33efdfe63e10f29bbe54b6841a43320": { + "describe": { + "columns": [ + { + "name": "time", + "ordinal": 0, + "type_info": "Int4" + }, + { + "name": "difficulty_factor", + "ordinal": 1, + "type_info": "Int4" + }, + { + "name": "worker_type", + "ordinal": 2, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + false, + false + ], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "SELECT time, difficulty_factor, worker_type FROM mcaptcha_pow_analytics\n WHERE \n config_id = (\n SELECT \n config_id FROM mcaptcha_config \n WHERE \n key = $1\n )\n ORDER BY ID" + }, "d7dd6cd6a7626e79c62377b2d59115067c5851ec044911ff8833779a08bbb8f7": { "describe": { "columns": [], diff --git a/db/db-sqlx-postgres/src/lib.rs b/db/db-sqlx-postgres/src/lib.rs index 752026ca..103a6e06 100644 --- a/db/db-sqlx-postgres/src/lib.rs +++ b/db/db-sqlx-postgres/src/lib.rs @@ -901,6 +901,72 @@ impl MCDatabase for Database { Ok(Date::dates_to_unix(records)) } + + /// record PoW timing + async fn analysis_save( + &self, + captcha_id: &str, + d: &PerformanceAnalytics, + ) -> DBResult<()> { + let _ = sqlx::query!( + "INSERT INTO mcaptcha_pow_analytics + (config_id, time, difficulty_factor, worker_type) + VALUES ((SELECT config_id FROM mcaptcha_config WHERE key = $1), $2, $3, $4)", + captcha_id, + d.time as i32, + d.difficulty_factor as i32, + &d.worker_type, + ) + .execute(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + Ok(()) + } + + /// fetch PoW analytics + async fn analytics_fetch( + &self, + captcha_id: &str, + ) -> DBResult> { + struct P { + time: i32, + difficulty_factor: i32, + worker_type: String, + } + + impl From

for PerformanceAnalytics { + fn from(v: P) -> Self { + Self { + time: v.time as u32, + difficulty_factor: v.difficulty_factor as u32, + worker_type: v.worker_type, + } + } + } + + let mut c = sqlx::query_as!( + P, + "SELECT time, difficulty_factor, worker_type FROM mcaptcha_pow_analytics + WHERE + config_id = ( + SELECT + config_id FROM mcaptcha_config + WHERE + key = $1 + ) + ORDER BY ID", + &captcha_id, + ) + .fetch_all(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + let mut res = Vec::with_capacity(c.len()); + for i in c.drain(0..) { + res.push(i.into()) + } + + Ok(res) + } } #[derive(Clone)]