riftenlabs-indexer/src/db/cauldron/user.rs
Dagur Valberg Johannsson 96a960a6bd
Improve mempool removal speed
Remove multiple in one query and trust in `ON DELETE CASCADE` to do its
job.
2024-10-25 14:32:04 +02:00

107 lines
3.2 KiB
Rust

// Copyright (C) 2024 Riften Labs AS
//
// This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later.
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
// Keep track of individual user activity
use std::collections::HashSet;
use anyhow::Result;
use bitcoin_hashes::{hex::ToHex, Hash};
use bitcoincash::{PubkeyHash, Transaction};
use riftenlabs_defi::cauldron::ParsedContract;
use rusqlite::{params, Connection};
pub fn create_table(conn: &Connection) {
conn.execute(
"CREATE TABLE user_action (
spent_utxo_hash TEXT NOT NULL,
user TEXT NOT NULL,
address_type TEXT NOT NULL,
FOREIGN KEY(spent_utxo_hash) REFERENCES utxo_spending(spent_utxo_hash) ON DELETE CASCADE,
PRIMARY KEY (spent_utxo_hash, user)
)",
[],
)
.unwrap();
}
pub fn insert_user_action(
con: &Connection,
cauldrons: &Vec<ParsedContract>,
tx: &Transaction,
replace: bool,
) -> Result<()> {
let mut statement = con.prepare(&format!(
"INSERT OR {} INTO user_action (spent_utxo_hash, user, address_type) VALUES (?, ?, ?)",
if replace { "REPLACE" } else { "IGNORE" }
))?;
let p2pkh_output_hashes: HashSet<PubkeyHash> = tx
.output
.iter()
.filter_map(|output| {
if output.script_pubkey.is_p2pkh() {
Some(output.script_pubkey[3..23].to_vec())
} else {
None
}
})
.map(|pubkey| {
PubkeyHash::from_inner(
pubkey
.try_into()
.expect("failed to convert into pubkeyhash"),
)
})
.collect();
for c in cauldrons {
for user in &p2pkh_output_hashes {
statement.execute(params![c.spent_utxo_hash.to_hex(), user.to_hex(), "p2pkh"])?;
}
}
Ok(())
}
pub fn get_unique_per_month_accumilating(conn: &Connection) -> Result<Vec<(String, usize)>> {
let sql = "WITH first_user_action AS (
-- Step 1: Get the first month when each user was active
SELECT
ua.user,
MIN(strftime('%Y-%m', datetime(tx.mtp_timestamp, 'unixepoch'))) AS first_action_month
FROM user_action ua
JOIN utxo_spending us ON ua.spent_utxo_hash = us.spent_utxo_hash
JOIN tx ON us.txid = tx.txid
GROUP BY ua.user
),
-- Step 2: Accumulate unique users by month
cumulative_unique_users AS (
SELECT
f.first_action_month AS month_year,
COUNT(DISTINCT f.user) AS new_unique_users
FROM first_user_action f
GROUP BY f.first_action_month
ORDER BY f.first_action_month
)
-- Step 3: Running total of unique users
SELECT
month_year,
SUM(new_unique_users) OVER (ORDER BY month_year) AS accumulated_unique_users
FROM cumulative_unique_users;
";
let mut stmt = conn.prepare(sql)?;
let mut rows = stmt.query([])?;
let mut results = Vec::new();
while let Some(row) = rows.next()? {
let month_year: String = row.get(0)?;
let accumulated_unique_users: usize = row.get(1)?;
results.push((month_year, accumulated_unique_users));
}
Ok(results)
}