2024-09-25 11:56:55 +02:00
|
|
|
// 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
|
|
|
|
|
|
2024-09-10 22:37:44 +02:00
|
|
|
use crate::{
|
|
|
|
|
bcmr::{parsedbcmr::parse_all_bcmr_identities, utilurl::get_url},
|
|
|
|
|
db::{
|
|
|
|
|
bcmr::{delete_entries_for_well_known, insert_well_known_bcmr},
|
|
|
|
|
DBPool,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
use anyhow::Result;
|
|
|
|
|
use log::{info, warn};
|
|
|
|
|
use serde_json::Value;
|
|
|
|
|
use std::{
|
|
|
|
|
sync::{atomic::AtomicBool, Arc},
|
|
|
|
|
thread::{self, JoinHandle},
|
|
|
|
|
time::{Duration, Instant},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// List of 'trust me bro' sources of truth
|
|
|
|
|
const WELL_KNOWN: [(&str, &str); 1] = [(
|
|
|
|
|
"otr",
|
|
|
|
|
"https://otr.cash/.well-known/bitcoin-cash-metadata-registry.json",
|
|
|
|
|
)];
|
|
|
|
|
|
|
|
|
|
pub struct WellKnownDownloader {
|
|
|
|
|
db: DBPool,
|
|
|
|
|
keep_running: Arc<AtomicBool>,
|
|
|
|
|
download_thread: Option<JoinHandle<()>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const LOOP_SLEEP_TIME: Duration = Duration::from_secs(2);
|
|
|
|
|
const UPDATE_INTERVAL: Duration = Duration::from_secs(3600 * 24); // once per day
|
|
|
|
|
const DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(120);
|
|
|
|
|
const MAX_BCMR_SIZE: usize = 1024 * 1024 * 500; // 500 MB
|
|
|
|
|
|
|
|
|
|
impl WellKnownDownloader {
|
|
|
|
|
pub fn new(db: DBPool) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
db,
|
|
|
|
|
keep_running: Arc::new(AtomicBool::new(true)),
|
|
|
|
|
download_thread: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn start(&mut self) -> Result<()> {
|
|
|
|
|
let db_cpy = self.db.clone();
|
|
|
|
|
let keep_running_cpy = self.keep_running.clone();
|
|
|
|
|
self.download_thread = Some(
|
|
|
|
|
thread::Builder::new()
|
|
|
|
|
.name("wellknown downloader".to_string())
|
|
|
|
|
.spawn(move || {
|
|
|
|
|
let mut last_update: Option<Instant> = None;
|
|
|
|
|
loop {
|
|
|
|
|
thread::sleep(LOOP_SLEEP_TIME);
|
|
|
|
|
if !keep_running_cpy.load(std::sync::atomic::Ordering::Relaxed) {
|
|
|
|
|
info!("Exiting bcmr well known thread");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(last_time) = last_update {
|
|
|
|
|
if Instant::now().duration_since(last_time) < UPDATE_INTERVAL {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
last_update = Some(Instant::now());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (source, url) in WELL_KNOWN {
|
|
|
|
|
info!("bcmr: Fetching well known '{}' token list", source);
|
|
|
|
|
|
|
|
|
|
let json_str: String = match get_url(url, DOWNLOAD_TIMEOUT, MAX_BCMR_SIZE) {
|
|
|
|
|
Ok((json_str, _)) => json_str,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
info!("bcmr: Failed to download well known url {}: {}", url, e);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let json: Value = match serde_json::from_str(&json_str) {
|
|
|
|
|
Ok(j) => j,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
info!("bcmr: Failed to parse well known url {}: {}", url, e);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let bcmr_entries = match parse_all_bcmr_identities(&json, true, source) {
|
|
|
|
|
Ok(entries) => entries,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
info!("bcmr: Failed to parse bcmr from well known url {}: {}", url, e);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut tx = match db_cpy.get() {
|
|
|
|
|
Ok(conn) => conn,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
warn!("bcmr: Failed to get DB connection: {}", e);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let tx = match tx.transaction() {
|
|
|
|
|
Ok(tx) => tx,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
warn!("bcmr: Failed to initiate DB transaction: {}", e);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Err(e) = delete_entries_for_well_known(&tx, source) {
|
|
|
|
|
warn!("bcmr: Failed to delete entries for well known source {}: {}", source, e);
|
|
|
|
|
}
|
|
|
|
|
for bcmr in bcmr_entries {
|
|
|
|
|
if let Err(e) = insert_well_known_bcmr(&tx, source, &bcmr) {
|
|
|
|
|
warn!("bcmr: Failed to insert an entry from well known source {}: {}", source, e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Err(e) = tx.commit() {
|
|
|
|
|
warn!("bcmr: Failed to update DB entries for {}: {}", source, e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}})
|
|
|
|
|
.expect("failed to start bcmr wellknown thread"),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for WellKnownDownloader {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
self.keep_running
|
|
|
|
|
.store(false, std::sync::atomic::Ordering::SeqCst);
|
|
|
|
|
if let Some(thread) = self.download_thread.take() {
|
|
|
|
|
// Wake the thread in case it is sleeping
|
|
|
|
|
thread.thread().unpark();
|
|
|
|
|
|
|
|
|
|
match thread.join() {
|
|
|
|
|
Ok(_) => info!("bcmr well knownthread done"),
|
|
|
|
|
Err(e) => warn!("Failed to join bcmr wellknown thread: {:?}", e),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|