API call for latest transactions

This commit is contained in:
Dagur Valberg Johannsson 2024-09-25 11:56:55 +02:00
parent eeab81c761
commit 528cc79725
No known key found for this signature in database
GPG key ID: FD701804AEE88107
6 changed files with 125 additions and 3 deletions

View file

@ -1,3 +1,8 @@
// 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
use std::time;
use anyhow::{bail, Result};

View file

@ -1,3 +1,8 @@
// 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
use crate::{
bcmr::{parsedbcmr::parse_all_bcmr_identities, utilurl::get_url},
db::{

View file

@ -5,8 +5,8 @@
use std::time::{SystemTime, UNIX_EPOCH};
use anyhow::Result;
use bitcoin_hashes::hex::ToHex;
use bitcoincash::{BlockHash, Txid};
use bitcoin_hashes::hex::{FromHex, ToHex};
use bitcoincash::{BlockHash, TokenID, Txid};
use rusqlite::{params, Connection};
pub fn create_table(conn: &Connection) {
@ -49,3 +49,59 @@ pub fn insert_mempool_tx(conn: &rusqlite::Connection, txid: &Txid) -> Result<()>
conn.execute(sql, params![&txid.to_hex(), current_timestamp])?;
Ok(())
}
pub fn latest(
conn: &rusqlite::Connection,
limit: usize,
offset: usize,
token_id: Option<TokenID>,
) -> Result<Vec<(Txid, Option<BlockHash>, u64)>, rusqlite::Error> {
let (sql, params) = match token_id {
Some(tid) => (
"SELECT DISTINCT tx.txid, tx.blockhash, tx.mtp_timestamp, tx.first_seen_timestamp
FROM tx
JOIN utxo_funding ON tx.txid = utxo_funding.txid
WHERE utxo_funding.token_id = ?
ORDER BY COALESCE(tx.first_seen_timestamp, tx.mtp_timestamp) DESC
LIMIT ? OFFSET ?",
params![tid.to_hex(), limit, offset],
),
None => (
"SELECT tx.txid, tx.blockhash, tx.mtp_timestamp, tx.first_seen_timestamp
FROM tx
ORDER BY COALESCE(tx.first_seen_timestamp, tx.mtp_timestamp) DESC
LIMIT ? OFFSET ?",
params![limit, offset],
),
};
let mut stmt = conn.prepare(sql)?;
let tx_iter = stmt.query_map(params, |row| {
let txid_hex: String = row.get(0)?;
let blockhash_hex: Option<String> = row.get(1)?;
let mtp_timestamp: Option<u64> = row.get(2).ok(); // Handle potential NULL
let first_seen_timestamp: Option<u64> = row.get(3).ok(); // Handle potential NULL
// Use first_seen_timestamp if available, otherwise mtp_timestamp, or return an error if both are NULL
let timestamp = first_seen_timestamp.or(mtp_timestamp).ok_or_else(|| {
rusqlite::Error::InvalidColumnType(
2,
"timestamp".to_string(),
rusqlite::types::Type::Null,
)
})?;
Ok((
Txid::from_hex(&txid_hex).expect("Invalid Txid hex"),
blockhash_hex.map(|hex| BlockHash::from_hex(&hex).expect("Invalid BlockHash hex")),
timestamp,
))
})?;
let mut txs = Vec::with_capacity(limit);
for tx in tx_iter {
txs.push(tx?);
}
Ok(txs)
}

View file

@ -253,7 +253,8 @@ fn launch() -> _ {
rpc::contract::contract_count_token,
rpc::contract::contract_count_all,
rpc::contract::contract_volume,
rpc::user::unique_addresses
rpc::user::unique_addresses,
rpc::tx::tx_latest,
],
)
.mount(

View file

@ -22,6 +22,7 @@ pub mod pool;
pub mod price;
pub mod tokens;
pub mod tvl;
pub mod tx;
pub mod user;
pub type ResponseCache = Arc<Mutex<HashMap<String, (i64 /* timestamp */, Vec<serde_json::Value>)>>>;

54
src/rpc/tx.rs Normal file
View file

@ -0,0 +1,54 @@
// 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
use bitcoin_hashes::hex::FromHex;
use bitcoincash::TokenID;
use rocket::{get, http::Status, response::status::Custom, serde::json::Json, State};
use serde_json::json;
use serde_json::Value;
use crate::db::DB;
#[get("/tx/latest?<limit>&<offset>&<token>")]
pub fn tx_latest(
limit: Option<usize>,
offset: Option<usize>,
token: Option<&str>,
conn: &State<DB>,
) -> Result<Json<Value>, Custom<String>> {
let db = conn.cauldron_r.get().map_err(|_| {
Custom(
Status::InternalServerError,
"Failed to get DB connection".into(),
)
})?;
let limit = limit.unwrap_or(100).min(10000);
let offset = offset.unwrap_or(0);
let token = match token {
None => None,
Some(tokenhex) => match TokenID::from_hex(tokenhex) {
Ok(token) => Some(token),
Err(_) => return Err(Custom(Status::BadRequest, "Invalid token ID".into())),
},
};
let txs = crate::db::cauldron::tx::latest(&db, limit, offset, token)
.map_err(|e| Custom(Status::InternalServerError, format!("Error: {}", e)))?;
let txs_json: Vec<Value> = txs
.into_iter()
.map(|tx| {
json!({
"txid": tx.0.to_string(),
"blockhash": tx.1.map(|blockhex| blockhex.to_string()),
"timestamp_guess": tx.2
})
})
.collect();
Ok(Json(json!(txs_json)))
}