Use delphi for indexer price calculation.

This commit is contained in:
Jakob Notland 2026-06-09 12:18:45 +02:00
parent d8edfe15fb
commit 12f50429de
2 changed files with 37 additions and 17 deletions

View file

@ -12,6 +12,7 @@ use crate::db::blob::display_hex_to_blob;
use crate::db::cauldron::pool::{get_injections_between, get_pool_period_snapshot}; use crate::db::cauldron::pool::{get_injections_between, get_pool_period_snapshot};
use crate::db::oracle::get_closest; use crate::db::oracle::get_closest;
use crate::db::oracle::oracle_cash::get_oracle_cash_closest; use crate::db::oracle::oracle_cash::get_oracle_cash_closest;
use crate::db::oracle::v2::v2_bchusd_token_id;
use crate::rpc::apy::apyaggregator::APYAggregator; use crate::rpc::apy::apyaggregator::APYAggregator;
use crate::rpc::apy::poolperiod::split_at_injections; use crate::rpc::apy::poolperiod::split_at_injections;
use malachite::base::num::arithmetic::traits::FloorSqrt; use malachite::base::num::arithmetic::traits::FloorSqrt;
@ -58,13 +59,14 @@ pub const SATS_PER_BCH: i64 = 100_000_000;
/// On-chain Delphi oracle scale: prices are stored in cents /// On-chain Delphi oracle scale: prices are stored in cents
/// (e.g. $384.24 → 38424). Same unit as the oracles.cash feed. /// (e.g. $384.24 → 38424). Same unit as the oracles.cash feed.
pub const ORACLE_SCALE: i64 = 100; pub const ORACLE_SCALE: i64 = 100;
/// Timestamps before this value are looked up in the on-chain Delphi indexed table. /// Delphi v1 stopped publishing at this timestamp (2026-04-23 00:00:00 UTC).
/// Timestamps on or after are looked up in the oracle_cash table (oracles.cash feed).
/// Value: 2026-04-23 00:00:00 UTC (last reliable Delphi oracle update).
const ORACLE_CUTOFF_TS: i64 = 1776902400; const ORACLE_CUTOFF_TS: i64 = 1776902400;
/// Delphi v2 went live at this timestamp (2026-05-04 00:00:00 UTC).
const V2_DEPLOY_TS: i64 = 1777852800;
pub async fn usd_per_bch_at_or_before(oracle_pool: &SqlitePool, ts: i64) -> Decimal { pub async fn usd_per_bch_at_or_before(oracle_pool: &SqlitePool, ts: i64) -> Decimal {
if ts < ORACLE_CUTOFF_TS { if ts < ORACLE_CUTOFF_TS {
// Delphi v1: on-chain historical data
match get_closest(oracle_pool, &None, ts).await { match get_closest(oracle_pool, &None, ts).await {
Ok(Some(e)) => { Ok(Some(e)) => {
Decimal::from_i64(e.oracle_price).unwrap_or(Decimal::ZERO) Decimal::from_i64(e.oracle_price).unwrap_or(Decimal::ZERO)
@ -72,14 +74,24 @@ pub async fn usd_per_bch_at_or_before(oracle_pool: &SqlitePool, ts: i64) -> Deci
} }
_ => Decimal::ZERO, _ => Decimal::ZERO,
} }
} else { } else if ts < V2_DEPLOY_TS {
// oracles.cash prices are in cents; divide by 100 to get USD/BCH // oracles.cash: bridges the Apr 23 – May 4 gap
match get_oracle_cash_closest(oracle_pool, ts).await { match get_oracle_cash_closest(oracle_pool, ts).await {
Ok(Some(e)) => { Ok(Some(e)) => {
Decimal::from_i64(e.oracle_price).unwrap_or(Decimal::ZERO) / Decimal::from(100) Decimal::from_i64(e.oracle_price).unwrap_or(Decimal::ZERO) / Decimal::from(100)
} }
_ => Decimal::ZERO, _ => Decimal::ZERO,
} }
} else {
// Delphi v2: on-chain data from May 4 2026 onward
let token_id = Some(v2_bchusd_token_id().clone());
match get_closest(oracle_pool, &token_id, ts).await {
Ok(Some(e)) => {
Decimal::from_i64(e.oracle_price).unwrap_or(Decimal::ZERO)
/ Decimal::from_i64(ORACLE_SCALE).unwrap()
}
_ => Decimal::ZERO,
}
} }
} }

View file

@ -3,9 +3,11 @@
// This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later. // 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 // A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
//! Background task that polls oracles.cash and keeps oracle_cash_price up-to-date. //! Background task that maintains oracle_cash_price for the historical gap period
//! This is a parallel feed alongside the on-chain Delphi oracle; both write to //! (Apr 23 – May 4 2026) between Delphi v1 and v2.
//! oracle.db but to separate tables. //!
//! Set `USE_ORACLES_CASH_LIVE_FEED = true` to re-enable continuous polling as a
//! live price fallback (e.g. if the on-chain v2 oracle becomes unavailable).
use std::{ use std::{
sync::{ sync::{
@ -28,14 +30,18 @@ const ORACLES_CASH_URL: &str = "https://oracles.generalprotocols.com";
const BCH_USD_ORACLE_PUBKEY: &str = const BCH_USD_ORACLE_PUBKEY: &str =
"02d09db08af1ff4e8453919cc866a4be427d7bfe18f2c05e5444c196fcf6fd2818"; "02d09db08af1ff4e8453919cc866a4be427d7bfe18f2c05e5444c196fcf6fd2818";
/// Set to true to continuously poll oracles.cash for live price updates.
/// False: only backfill the gap period; Delphi v2 is the live source.
const USE_ORACLES_CASH_LIVE_FEED: bool = false;
const POLL_INTERVAL: Duration = Duration::from_secs(300); // 5 minutes const POLL_INTERVAL: Duration = Duration::from_secs(300); // 5 minutes
const REQUEST_TIMEOUT: Duration = Duration::from_secs(20); const REQUEST_TIMEOUT: Duration = Duration::from_secs(20);
const BACKFILL_AGGREGATION: i64 = 3600; // 1-hour buckets const BACKFILL_AGGREGATION: i64 = 3600; // 1-hour buckets
/// Backfill oracle_cash data starting from this timestamp. /// Backfill starts from when Delphi v1 stopped (2026-04-23 00:00:00 UTC).
/// Everything before this point is served from the on-chain Delphi indexed table,
/// so oracle_cash only needs to cover from here onward.
/// Value: 2026-04-23 00:00:00 UTC (last reliable Delphi oracle update).
const ORACLE_CUTOFF_TS: i64 = 1776902400; const ORACLE_CUTOFF_TS: i64 = 1776902400;
/// Backfill ends when Delphi v2 went live (2026-05-04 00:00:00 UTC).
/// Only the gap between these two timestamps needs oracles.cash coverage.
const V2_DEPLOY_TS: i64 = 1777852800;
// ── API response types ──────────────────────────────────────────────────────── // ── API response types ────────────────────────────────────────────────────────
@ -109,14 +115,11 @@ async fn fetch_current_price(client: &reqwest::Client) -> anyhow::Result<(i64, i
)) ))
} }
/// Fetches hourly price history from ORACLE_CUTOFF_TS to now and inserts into the DB. /// Fetches hourly price history for the gap period [ORACLE_CUTOFF_TS, V2_DEPLOY_TS].
async fn backfill_history(client: &reqwest::Client, pool: &SqlitePool) -> anyhow::Result<()> { async fn backfill_history(client: &reqwest::Client, pool: &SqlitePool) -> anyhow::Result<()> {
let now = time_now();
let min_ts = ORACLE_CUTOFF_TS;
let url = format!( let url = format!(
"{}/api/v2/priceGraphPoints?publicKey={}&minMessageTimestamp={}&maxMessageTimestamp={}&aggregationPeriod={}", "{}/api/v2/priceGraphPoints?publicKey={}&minMessageTimestamp={}&maxMessageTimestamp={}&aggregationPeriod={}",
ORACLES_CASH_URL, BCH_USD_ORACLE_PUBKEY, min_ts, now, BACKFILL_AGGREGATION ORACLES_CASH_URL, BCH_USD_ORACLE_PUBKEY, ORACLE_CUTOFF_TS, V2_DEPLOY_TS, BACKFILL_AGGREGATION
); );
let resp: PriceGraphResponse = client let resp: PriceGraphResponse = client
@ -194,6 +197,11 @@ impl OracleCashFetcher {
} }
} }
if !USE_ORACLES_CASH_LIVE_FEED {
info!("oracle_cash: live feed disabled, gap backfill complete");
return;
}
loop { loop {
if !keep_running.load(Ordering::Relaxed) { if !keep_running.load(Ordering::Relaxed) {
info!("oracle_cash: exiting fetch task"); info!("oracle_cash: exiting fetch task");