Better
This commit is contained in:
parent
87a5187676
commit
852e389ed1
12 changed files with 110 additions and 285 deletions
|
|
@ -122,7 +122,6 @@ async fn insert_tx(
|
||||||
Some(ts),
|
Some(ts),
|
||||||
leg.deltas.0,
|
leg.deltas.0,
|
||||||
leg.deltas.1,
|
leg.deltas.1,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -47,20 +47,13 @@ const SQRT_SCALE: u64 = 1_000_000_000;
|
||||||
|
|
||||||
/// What happened between two consecutive pool states.
|
/// What happened between two consecutive pool states.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[repr(i64)]
|
|
||||||
pub enum StepKind {
|
pub enum StepKind {
|
||||||
/// Not a step we can read: a reserve is missing, zero, or unchanged.
|
/// Not a step we can read: a reserve is missing, zero, or unchanged.
|
||||||
Unreadable = 0,
|
Unreadable,
|
||||||
/// Both reserves moved the same way — a deposit or a withdrawal.
|
/// Both reserves moved the same way — a deposit or a withdrawal.
|
||||||
LiquidityChange = 1,
|
LiquidityChange,
|
||||||
/// The reserves moved in opposite directions — a swap.
|
/// The reserves moved in opposite directions — a swap.
|
||||||
Trade = 2,
|
Trade,
|
||||||
}
|
|
||||||
|
|
||||||
impl StepKind {
|
|
||||||
pub fn as_i64(self) -> i64 {
|
|
||||||
self as i64
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A pool's reserves at one point in time.
|
/// A pool's reserves at one point in time.
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use crate::db::cauldron::fees::{
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use bitcoincash::TokenID;
|
use bitcoincash::TokenID;
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, info, warn};
|
||||||
use malachite::Integer;
|
use malachite::Integer;
|
||||||
use riftenlabs_defi::{cauldron::ParsedContract, chainutil::OutPointHash};
|
use riftenlabs_defi::{cauldron::ParsedContract, chainutil::OutPointHash};
|
||||||
use rust_decimal::prelude::Zero;
|
use rust_decimal::prelude::Zero;
|
||||||
|
|
@ -59,13 +59,7 @@ pub async fn create_table(pool: &SqlitePool) {
|
||||||
sats BIGINT NOT NULL,
|
sats BIGINT NOT NULL,
|
||||||
token_amount BIGINT NOT NULL,
|
token_amount BIGINT NOT NULL,
|
||||||
sats_delta BIGINT NOT NULL,
|
sats_delta BIGINT NOT NULL,
|
||||||
token_delta BIGINT NOT NULL,
|
token_delta BIGINT NOT NULL
|
||||||
-- Fee earned by the step this entry closes, in micro-satoshis, and
|
|
||||||
-- what kind of step it was. See `db::cauldron::fees`. Nullable so
|
|
||||||
-- `ensure_fee_columns` can add them to an existing database and
|
|
||||||
-- `backfill_fees` can tell filled rows from unfilled ones.
|
|
||||||
fee_e6 INTEGER,
|
|
||||||
step_kind INTEGER
|
|
||||||
)",
|
)",
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
|
|
@ -104,142 +98,6 @@ pub async fn create_table(pool: &SqlitePool) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add the per-step fee columns to an existing database.
|
|
||||||
///
|
|
||||||
/// Safe to run on every start: `add_column_if_missing` is a no-op once they
|
|
||||||
/// exist. Deliberately adds no index — `SUM(fee_e6) ... WHERE pool = ? AND
|
|
||||||
/// effective_timestamp >= ?` is served by the existing
|
|
||||||
/// `idx_phe_pool_timestamp`, and `pool_history_entry` already carries ten
|
|
||||||
/// indexes with redundant pairs among them. Prune those before adding another.
|
|
||||||
pub async fn ensure_fee_columns(pool: &SqlitePool) {
|
|
||||||
use crate::db::cauldron::tokenlist::db_utils::add_column_if_missing;
|
|
||||||
add_column_if_missing(pool, "pool_history_entry", "fee_e6", "INTEGER")
|
|
||||||
.await
|
|
||||||
.expect("failed to add pool_history_entry.fee_e6");
|
|
||||||
add_column_if_missing(pool, "pool_history_entry", "step_kind", "INTEGER")
|
|
||||||
.await
|
|
||||||
.expect("failed to add pool_history_entry.step_kind");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fill `fee_e6`/`step_kind` for rows written before the columns existed.
|
|
||||||
///
|
|
||||||
/// Walks one pool at a time, in `sequence` order, since a step's fee is defined
|
|
||||||
/// against the entry before it. Each pool is its own short transaction, so the
|
|
||||||
/// write lock is never held long and memory stays bounded by the largest single
|
|
||||||
/// pool rather than by the table — the alternative, reading 3.7M rows and
|
|
||||||
/// updating them in one transaction, is minutes of held lock and hundreds of
|
|
||||||
/// megabytes resident.
|
|
||||||
///
|
|
||||||
/// Resumable by construction: only rows with `fee_e6 IS NULL` are considered, so
|
|
||||||
/// an interrupted run continues where it stopped. A completed backfill costs one
|
|
||||||
/// indexed count on subsequent starts and nothing else.
|
|
||||||
///
|
|
||||||
/// Errors are logged and skipped rather than fatal. A pool whose fees cannot be
|
|
||||||
/// computed is left NULL and retried next start; taking the indexer down over a
|
|
||||||
/// display figure would be the wrong trade.
|
|
||||||
pub async fn backfill_fees(pool: &SqlitePool) {
|
|
||||||
let pending: i64 =
|
|
||||||
match sqlx::query_scalar("SELECT COUNT(*) FROM pool_history_entry WHERE fee_e6 IS NULL")
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(n) => n,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Could not count rows needing fee backfill: {e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if pending == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pools: Vec<Vec<u8>> = match sqlx::query_scalar(
|
|
||||||
"SELECT DISTINCT pool FROM pool_history_entry WHERE fee_e6 IS NULL",
|
|
||||||
)
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(p) => p,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Could not list pools needing fee backfill: {e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
info!(
|
|
||||||
"Backfilling fees for {pending} entries across {} pools",
|
|
||||||
pools.len()
|
|
||||||
);
|
|
||||||
let started = std::time::Instant::now();
|
|
||||||
let mut written = 0i64;
|
|
||||||
|
|
||||||
for (done, pool_blob) in pools.iter().enumerate() {
|
|
||||||
match backfill_one_pool(pool, pool_blob).await {
|
|
||||||
Ok(n) => written += n,
|
|
||||||
Err(e) => error!("Fee backfill failed for one pool, will retry next start: {e}"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give the runtime a turn: this runs before the API binds, and a tight
|
|
||||||
// loop over tens of thousands of pools would otherwise monopolise it.
|
|
||||||
if done % 500 == 499 {
|
|
||||||
info!(" ... {done}/{} pools", pools.len());
|
|
||||||
tokio::task::yield_now().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!(
|
|
||||||
"Backfilled {written} pool history fees in {:.1}s",
|
|
||||||
started.elapsed().as_secs_f64()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// One pool's chain, in its own transaction.
|
|
||||||
async fn backfill_one_pool(pool: &SqlitePool, pool_blob: &[u8]) -> Result<i64> {
|
|
||||||
let rows = sqlx::query(
|
|
||||||
"SELECT utxo, sats, token_amount
|
|
||||||
FROM pool_history_entry
|
|
||||||
WHERE pool = ?1
|
|
||||||
ORDER BY sequence ASC",
|
|
||||||
)
|
|
||||||
.bind(pool_blob)
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut tx = pool.begin().await?;
|
|
||||||
let mut prev: Option<Reserves> = None;
|
|
||||||
let mut written = 0i64;
|
|
||||||
|
|
||||||
for row in rows {
|
|
||||||
let utxo: Vec<u8> = row.get(0);
|
|
||||||
let sats: i64 = row.get(1);
|
|
||||||
let token_amount: i64 = row.get(2);
|
|
||||||
|
|
||||||
let next = Reserves {
|
|
||||||
sats: sats.max(0) as u64,
|
|
||||||
tokens: token_amount.max(0) as u64,
|
|
||||||
};
|
|
||||||
let (fee_e6, kind) = match prev {
|
|
||||||
Some(prev) => (step_fee_e6(prev, next), classify_step(prev, next).as_i64()),
|
|
||||||
// A pool's first entry has no step behind it.
|
|
||||||
None => (0, StepKind::Unreadable.as_i64()),
|
|
||||||
};
|
|
||||||
|
|
||||||
sqlx::query("UPDATE pool_history_entry SET fee_e6 = ?, step_kind = ? WHERE utxo = ?")
|
|
||||||
.bind(fee_e6)
|
|
||||||
.bind(kind)
|
|
||||||
.bind(&utxo)
|
|
||||||
.execute(&mut *tx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
prev = Some(next);
|
|
||||||
written += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.commit().await?;
|
|
||||||
Ok(written)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_pool_by_utxo(
|
async fn get_pool_by_utxo(
|
||||||
conn: &mut SqliteConnection,
|
conn: &mut SqliteConnection,
|
||||||
utxo_hash: &OutPointHash,
|
utxo_hash: &OutPointHash,
|
||||||
|
|
@ -306,10 +164,6 @@ pub fn dummy_init_seq() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// One more parameter than clippy's default, as in `ohlcv.rs` and
|
|
||||||
// `moria_v11/ingest.rs`. Grouping the step arguments into a struct would touch
|
|
||||||
// every call site to no benefit — they are all passing literals.
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub async fn insert_pool_history_entry(
|
pub async fn insert_pool_history_entry(
|
||||||
conn: &mut SqliteConnection,
|
conn: &mut SqliteConnection,
|
||||||
pool: &OutPointHash,
|
pool: &OutPointHash,
|
||||||
|
|
@ -318,30 +172,14 @@ pub async fn insert_pool_history_entry(
|
||||||
first_seen_timestamp: Option<u64>,
|
first_seen_timestamp: Option<u64>,
|
||||||
sats_delta: i64,
|
sats_delta: i64,
|
||||||
token_delta: i64,
|
token_delta: i64,
|
||||||
// The pool's reserves in the entry this one succeeds, or `None` for a
|
|
||||||
// pool's first entry — which earns nothing, having no step behind it.
|
|
||||||
prev: Option<Reserves>,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let next_seq = NEXT_SEQUENCE.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
let next_seq = NEXT_SEQUENCE.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||||
assert!(next_seq >= 0);
|
assert!(next_seq >= 0);
|
||||||
|
|
||||||
// Computed here rather than by the caller so it cannot be forgotten at one
|
|
||||||
// of the two call sites, and so the fee is written in the same statement as
|
|
||||||
// the reserves it describes.
|
|
||||||
let (fee_e6, step_kind) = match (prev, cauldron.sats, cauldron.token_amount) {
|
|
||||||
(Some(prev), Some(sats), Some(token_amount)) if token_amount >= 0 => {
|
|
||||||
let next = Reserves {
|
|
||||||
sats,
|
|
||||||
tokens: token_amount as u64,
|
|
||||||
};
|
|
||||||
(step_fee_e6(prev, next), classify_step(prev, next).as_i64())
|
|
||||||
}
|
|
||||||
_ => (0, StepKind::Unreadable.as_i64()),
|
|
||||||
};
|
|
||||||
|
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"INSERT INTO pool_history_entry (utxo, pool, token_id, txid, tx_pos, mtp_timestamp, first_seen_timestamp, sequence, sats, token_amount, sats_delta, token_delta, fee_e6, step_kind)
|
"INSERT INTO pool_history_entry (utxo, pool, token_id, txid, tx_pos, mtp_timestamp, first_seen_timestamp, sequence, sats, token_amount, sats_delta, token_delta)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT(utxo) DO UPDATE SET
|
ON CONFLICT(utxo) DO UPDATE SET
|
||||||
pool = excluded.pool,
|
pool = excluded.pool,
|
||||||
token_id = excluded.token_id,
|
token_id = excluded.token_id,
|
||||||
|
|
@ -355,9 +193,7 @@ pub async fn insert_pool_history_entry(
|
||||||
-- history and breaking any cursor paging over it. The original value
|
-- history and breaking any cursor paging over it. The original value
|
||||||
-- is the order the state was first observed, and for a chain of
|
-- is the order the state was first observed, and for a chain of
|
||||||
-- spends a parent is always observed before its child.
|
-- spends a parent is always observed before its child.
|
||||||
sequence = pool_history_entry.sequence,
|
sequence = pool_history_entry.sequence",
|
||||||
fee_e6 = excluded.fee_e6,
|
|
||||||
step_kind = excluded.step_kind",
|
|
||||||
)
|
)
|
||||||
.bind(cauldron.new_utxo_hash.expect("utxo hash on new pool history entry").to_blob())
|
.bind(cauldron.new_utxo_hash.expect("utxo hash on new pool history entry").to_blob())
|
||||||
.bind(pool.to_blob())
|
.bind(pool.to_blob())
|
||||||
|
|
@ -371,8 +207,6 @@ pub async fn insert_pool_history_entry(
|
||||||
.bind(cauldron.token_amount)
|
.bind(cauldron.token_amount)
|
||||||
.bind(sats_delta)
|
.bind(sats_delta)
|
||||||
.bind(token_delta)
|
.bind(token_delta)
|
||||||
.bind(fee_e6)
|
|
||||||
.bind(step_kind)
|
|
||||||
.execute(&mut *conn)
|
.execute(&mut *conn)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow::anyhow!("failed to insert pool history entry. Original error: {:?}", e))?;
|
.map_err(|e| anyhow::anyhow!("failed to insert pool history entry. Original error: {:?}", e))?;
|
||||||
|
|
@ -432,7 +266,6 @@ pub async fn update_pool_history(
|
||||||
first_seen_timestamp,
|
first_seen_timestamp,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -456,10 +289,6 @@ pub async fn update_pool_history(
|
||||||
first_seen_timestamp,
|
first_seen_timestamp,
|
||||||
sats_delta,
|
sats_delta,
|
||||||
token_delta,
|
token_delta,
|
||||||
Some(Reserves {
|
|
||||||
sats: prev_entry.sats,
|
|
||||||
tokens: prev_entry.token_amount,
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
@ -904,59 +733,6 @@ async fn get_pool_history_entry(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Summed fees per pool since `start`, for a batch of pools.
|
|
||||||
///
|
|
||||||
/// One indexed pass instead of shipping every history row to the client and
|
|
||||||
/// having it re-derive the same figure. Pools with no rows in the window are
|
|
||||||
/// absent from the result rather than reported as zero — the caller cannot
|
|
||||||
/// otherwise tell "earned nothing" from "does not exist", and on a money figure
|
|
||||||
/// those must not look alike.
|
|
||||||
pub async fn db_pool_fees(
|
|
||||||
pool: &SqlitePool,
|
|
||||||
pool_ids: &[PoolID],
|
|
||||||
start_time: u64,
|
|
||||||
) -> Result<Vec<PoolFees>> {
|
|
||||||
let mut out = Vec::with_capacity(pool_ids.len());
|
|
||||||
|
|
||||||
// One statement per pool rather than an IN list: the parameter limit is
|
|
||||||
// finite, the index is (pool, effective_timestamp) so each is a range scan,
|
|
||||||
// and it keeps the mapping back to the requested id unambiguous.
|
|
||||||
for pool_id in pool_ids {
|
|
||||||
let row = sqlx::query(
|
|
||||||
"SELECT
|
|
||||||
COALESCE(SUM(fee_e6), 0),
|
|
||||||
COALESCE(SUM(step_kind = 2), 0),
|
|
||||||
COALESCE(SUM(step_kind = 1), 0),
|
|
||||||
COALESCE(SUM(step_kind = 0), 0),
|
|
||||||
COUNT(*)
|
|
||||||
FROM pool_history_entry
|
|
||||||
WHERE pool = ?1 AND effective_timestamp >= ?2",
|
|
||||||
)
|
|
||||||
.bind(pool_id.to_blob())
|
|
||||||
.bind(start_time as i64)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let rows: i64 = row.get(4);
|
|
||||||
if rows == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fee_e6: i64 = row.get(0);
|
|
||||||
out.push(PoolFees {
|
|
||||||
pool_id: pool_id.to_string(),
|
|
||||||
// A decimal string, not a float: this is money, and the consumer
|
|
||||||
// reads it into a BigNumber.
|
|
||||||
fee_sats: format!("{}.{:06}", fee_e6 / FEE_SCALE, (fee_e6 % FEE_SCALE).abs()),
|
|
||||||
trades: row.get(1),
|
|
||||||
liquidity_changes: row.get(2),
|
|
||||||
unreadable: row.get(3),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Where a page of history left off.
|
/// Where a page of history left off.
|
||||||
///
|
///
|
||||||
/// Ordering is by `sequence`, the order states were observed. For a pool that is
|
/// Ordering is by `sequence`, the order states were observed. For a pool that is
|
||||||
|
|
@ -980,6 +756,103 @@ pub struct HistoryCursor {
|
||||||
pub utxo: Vec<u8>,
|
pub utxo: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Summed fees per pool since `start`, for a batch of pools.
|
||||||
|
///
|
||||||
|
/// Walks each pool's reserves and values every step at the point it happened.
|
||||||
|
/// Deliberately computed rather than stored: a `fee_e6` column per row was
|
||||||
|
/// measured at only ~2x faster on the worst realistic query, which is cached
|
||||||
|
/// anyway, in exchange for 146 MB, a startup backfill, and a precision decision
|
||||||
|
/// frozen at write time. The win over the old design is doing this on the server
|
||||||
|
/// at all — clients used to download every pool's whole history to do it
|
||||||
|
/// themselves.
|
||||||
|
///
|
||||||
|
/// Pools with no rows in the window are absent from the result rather than
|
||||||
|
/// reported as zero: a caller cannot otherwise tell "earned nothing" from "does
|
||||||
|
/// not exist", and on a money figure those must not look alike.
|
||||||
|
pub async fn db_pool_fees(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
pool_ids: &[PoolID],
|
||||||
|
start_time: u64,
|
||||||
|
) -> Result<Vec<PoolFees>> {
|
||||||
|
let mut out = Vec::with_capacity(pool_ids.len());
|
||||||
|
|
||||||
|
for pool_id in pool_ids {
|
||||||
|
// The row before the window seeds the walk, so the step landing on the
|
||||||
|
// window's first entry is counted. That fee was earned at the moment of
|
||||||
|
// the later entry, which is inside the window.
|
||||||
|
let seed = sqlx::query(
|
||||||
|
"SELECT sats, token_amount
|
||||||
|
FROM pool_history_entry
|
||||||
|
WHERE pool = ?1 AND effective_timestamp < ?2
|
||||||
|
ORDER BY sequence DESC
|
||||||
|
LIMIT 1",
|
||||||
|
)
|
||||||
|
.bind(pool_id.to_blob())
|
||||||
|
.bind(start_time as i64)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let rows = sqlx::query(
|
||||||
|
"SELECT sats, token_amount
|
||||||
|
FROM pool_history_entry
|
||||||
|
WHERE pool = ?1 AND effective_timestamp >= ?2
|
||||||
|
ORDER BY sequence ASC",
|
||||||
|
)
|
||||||
|
.bind(pool_id.to_blob())
|
||||||
|
.bind(start_time as i64)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if rows.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reserves_of = |row: &sqlx::sqlite::SqliteRow| {
|
||||||
|
let sats: i64 = row.get(0);
|
||||||
|
let tokens: i64 = row.get(1);
|
||||||
|
Reserves {
|
||||||
|
sats: sats.max(0) as u64,
|
||||||
|
tokens: tokens.max(0) as u64,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut fee_e6: i64 = 0;
|
||||||
|
let mut trades = 0i64;
|
||||||
|
let mut liquidity_changes = 0i64;
|
||||||
|
let mut unreadable = 0i64;
|
||||||
|
let mut prev = seed.as_ref().map(reserves_of);
|
||||||
|
|
||||||
|
for row in &rows {
|
||||||
|
let next = reserves_of(row);
|
||||||
|
match prev {
|
||||||
|
Some(prev) => {
|
||||||
|
fee_e6 = fee_e6.saturating_add(step_fee_e6(prev, next));
|
||||||
|
match classify_step(prev, next) {
|
||||||
|
StepKind::Trade => trades += 1,
|
||||||
|
StepKind::LiquidityChange => liquidity_changes += 1,
|
||||||
|
StepKind::Unreadable => unreadable += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No step behind the first entry we can see.
|
||||||
|
None => unreadable += 1,
|
||||||
|
}
|
||||||
|
prev = Some(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push(PoolFees {
|
||||||
|
pool_id: pool_id.to_string(),
|
||||||
|
// A decimal string, not a float: this is money, and the consumer
|
||||||
|
// reads it into a BigNumber.
|
||||||
|
fee_sats: format!("{}.{:06}", fee_e6 / FEE_SCALE, (fee_e6 % FEE_SCALE).abs()),
|
||||||
|
trades,
|
||||||
|
liquidity_changes,
|
||||||
|
unreadable,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn db_pool_history(
|
pub async fn db_pool_history(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
pool_id: &PoolID,
|
pool_id: &PoolID,
|
||||||
|
|
@ -1212,7 +1085,6 @@ mod tests {
|
||||||
Some(t0 as u64),
|
Some(t0 as u64),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -1248,7 +1120,6 @@ mod tests {
|
||||||
Some(t1 as u64),
|
Some(t1 as u64),
|
||||||
100,
|
100,
|
||||||
-10,
|
-10,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -1303,7 +1174,6 @@ mod tests {
|
||||||
Some(t0 as u64),
|
Some(t0 as u64),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -1328,10 +1198,6 @@ mod tests {
|
||||||
Some(t1 as u64),
|
Some(t1 as u64),
|
||||||
(sats1 - sats0) as i64,
|
(sats1 - sats0) as i64,
|
||||||
tokens1 - tokens0,
|
tokens1 - tokens0,
|
||||||
Some(Reserves {
|
|
||||||
sats: sats0,
|
|
||||||
tokens: tokens0 as u64,
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -1386,7 +1252,6 @@ mod tests {
|
||||||
None,
|
None,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,7 @@ CREATE TABLE IF NOT EXISTS cached_token_metrics (
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn add_column_if_missing(
|
async fn add_column_if_missing(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
table: &str,
|
table: &str,
|
||||||
column: &str,
|
column: &str,
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,6 @@ mod tests {
|
||||||
Some(t0 as u64),
|
Some(t0 as u64),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -164,7 +163,6 @@ mod tests {
|
||||||
Some(t1 as u64),
|
Some(t1 as u64),
|
||||||
sats1 as i64,
|
sats1 as i64,
|
||||||
toks1,
|
toks1,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -178,7 +176,6 @@ mod tests {
|
||||||
Some(t1 as u64),
|
Some(t1 as u64),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -1342,7 +1339,6 @@ mod tests {
|
||||||
Some(t0 as u64),
|
Some(t0 as u64),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -1396,7 +1392,6 @@ mod tests {
|
||||||
Some(t1 as u64),
|
Some(t1 as u64),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,6 @@ pub async fn initialize_databases(network: &str, read_slots: ReadSlots) -> Resul
|
||||||
// Always-run migrations: safe on both new and existing cauldron.db
|
// Always-run migrations: safe on both new and existing cauldron.db
|
||||||
tokentoken_prepare_tables(&cauldron_db_write).await;
|
tokentoken_prepare_tables(&cauldron_db_write).await;
|
||||||
tokenbch_prepare_tables(&cauldron_db_write).await;
|
tokenbch_prepare_tables(&cauldron_db_write).await;
|
||||||
crate::db::cauldron::pool::ensure_fee_columns(&cauldron_db_write).await;
|
|
||||||
crate::db::cauldron::pool::backfill_fees(&cauldron_db_write).await;
|
|
||||||
|
|
||||||
// Initialize BCMR database
|
// Initialize BCMR database
|
||||||
let (db_exists, bcmr_db_write, bcmr_db_read) =
|
let (db_exists, bcmr_db_write, bcmr_db_read) =
|
||||||
|
|
|
||||||
|
|
@ -715,7 +715,6 @@ mod tests {
|
||||||
Some(thirty_days_ago as u64),
|
Some(thirty_days_ago as u64),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
insert_pool_history_entry(
|
insert_pool_history_entry(
|
||||||
|
|
@ -726,7 +725,6 @@ mod tests {
|
||||||
Some(current_timestamp as u64),
|
Some(current_timestamp as u64),
|
||||||
2000,
|
2000,
|
||||||
1000,
|
1000,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
@ -804,7 +802,6 @@ mod tests {
|
||||||
Some(thirty_days_ago as u64),
|
Some(thirty_days_ago as u64),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
insert_pool_history_entry(
|
insert_pool_history_entry(
|
||||||
|
|
@ -815,7 +812,6 @@ mod tests {
|
||||||
Some(current_timestamp as u64),
|
Some(current_timestamp as u64),
|
||||||
3000,
|
3000,
|
||||||
1500,
|
1500,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
@ -875,7 +871,6 @@ mod tests {
|
||||||
Some(thirty_days_ago as u64),
|
Some(thirty_days_ago as u64),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
@ -933,7 +928,6 @@ mod tests {
|
||||||
Some(thirty_days_ago as u64),
|
Some(thirty_days_ago as u64),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
insert_pool_history_entry(
|
insert_pool_history_entry(
|
||||||
|
|
@ -944,7 +938,6 @@ mod tests {
|
||||||
Some(current_timestamp as u64),
|
Some(current_timestamp as u64),
|
||||||
5000,
|
5000,
|
||||||
1500,
|
1500,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,6 @@ async fn setup_mock_db(pool: sqlx::SqlitePool) {
|
||||||
Some(TIME_1),
|
Some(TIME_1),
|
||||||
80_000,
|
80_000,
|
||||||
2_000,
|
2_000,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -145,7 +144,6 @@ async fn setup_mock_db(pool: sqlx::SqlitePool) {
|
||||||
Some(TIME_2),
|
Some(TIME_2),
|
||||||
120_000,
|
120_000,
|
||||||
2_000,
|
2_000,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -157,7 +155,6 @@ async fn setup_mock_db(pool: sqlx::SqlitePool) {
|
||||||
Some(TIME_3),
|
Some(TIME_3),
|
||||||
160_000,
|
160_000,
|
||||||
2_000,
|
2_000,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -169,7 +166,6 @@ async fn setup_mock_db(pool: sqlx::SqlitePool) {
|
||||||
Some(TIME_4),
|
Some(TIME_4),
|
||||||
200_000,
|
200_000,
|
||||||
2_000,
|
2_000,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -251,7 +247,6 @@ async fn insert_trade_at(
|
||||||
Some(ts),
|
Some(ts),
|
||||||
deltas.0,
|
deltas.0,
|
||||||
deltas.1,
|
deltas.1,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -431,7 +426,6 @@ async fn test_single_swap_multiple_pools() {
|
||||||
Some(time),
|
Some(time),
|
||||||
(100_000 * (i + 1)) as i64,
|
(100_000 * (i + 1)) as i64,
|
||||||
2_000i64,
|
2_000i64,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -534,7 +528,6 @@ async fn test_candle_prices_and_counts_volume_when_legs_cancel() {
|
||||||
Some(t0),
|
Some(t0),
|
||||||
10_000,
|
10_000,
|
||||||
200,
|
200,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -578,7 +571,6 @@ async fn test_candle_prices_and_counts_volume_when_legs_cancel() {
|
||||||
Some(t1),
|
Some(t1),
|
||||||
10_000,
|
10_000,
|
||||||
200,
|
200,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -591,7 +583,6 @@ async fn test_candle_prices_and_counts_volume_when_legs_cancel() {
|
||||||
Some(t1),
|
Some(t1),
|
||||||
-10_000,
|
-10_000,
|
||||||
-200,
|
-200,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -189,9 +189,9 @@ const FEES_MAX_IDS: usize = 200;
|
||||||
|
|
||||||
/// `/cauldron/pools/fees?ids=<a,b,c>&start=<unix>`
|
/// `/cauldron/pools/fees?ids=<a,b,c>&start=<unix>`
|
||||||
///
|
///
|
||||||
/// Fees earned per pool since `start`, summed from the per-step `fee_e6` written
|
/// Fees earned per pool since `start`. Exists so a wallet showing its total does
|
||||||
/// at index time. Exists so a wallet showing its total does not have to download
|
/// not have to download every pool's entire history and re-derive the figure in
|
||||||
/// every pool's entire history and re-derive the figure in the browser.
|
/// the browser — which cost one large uncached request per position.
|
||||||
///
|
///
|
||||||
/// Pools absent from the response had no history in the window; a caller
|
/// Pools absent from the response had no history in the window; a caller
|
||||||
/// distinguishing "earned nothing" from "no such pool" should treat absence as
|
/// distinguishing "earned nothing" from "no such pool" should treat absence as
|
||||||
|
|
|
||||||
|
|
@ -656,7 +656,6 @@ mod tests {
|
||||||
Some(1727963350),
|
Some(1727963350),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -977,7 +976,6 @@ mod tests {
|
||||||
Some(ts_created),
|
Some(ts_created),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -1374,7 +1372,6 @@ mod tests {
|
||||||
Some(TIME_1),
|
Some(TIME_1),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -1582,7 +1579,7 @@ mod tests {
|
||||||
&OutPointHash::all_zeros(),
|
&OutPointHash::all_zeros(),
|
||||||
);
|
);
|
||||||
insert_new_pool(&mut conn, &ca).await.unwrap();
|
insert_new_pool(&mut conn, &ca).await.unwrap();
|
||||||
insert_pool_history_entry(&mut conn, &utxo_a, &ca, Some(BASE), Some(BASE), 0, 0, None)
|
insert_pool_history_entry(&mut conn, &utxo_a, &ca, Some(BASE), Some(BASE), 0, 0)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
@ -1620,7 +1617,6 @@ mod tests {
|
||||||
Some(BASE + 200),
|
Some(BASE + 200),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -346,7 +346,6 @@ pub mod tests {
|
||||||
Some(1000),
|
Some(1000),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -358,7 +357,6 @@ pub mod tests {
|
||||||
Some(2000),
|
Some(2000),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -370,7 +368,6 @@ pub mod tests {
|
||||||
Some(3000),
|
Some(3000),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,6 @@ mod tests {
|
||||||
Some(current_time),
|
Some(current_time),
|
||||||
500, // sats_delta for trading activity
|
500, // sats_delta for trading activity
|
||||||
50, // token_delta for trading activity
|
50, // token_delta for trading activity
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -270,7 +269,6 @@ mod tests {
|
||||||
Some(current_time),
|
Some(current_time),
|
||||||
500, // sats_delta for trading activity
|
500, // sats_delta for trading activity
|
||||||
50, // token_delta for trading activity
|
50, // token_delta for trading activity
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue