Merge branch 'well-known-bug' into 'master'
bcmr: Look for well-known on bare domains Closes #24 See merge request riftenlabs/riftenlabs-indexer!83
This commit is contained in:
commit
4fe4492620
1 changed files with 122 additions and 24 deletions
|
|
@ -36,6 +36,45 @@ const MAX_BCMR_SIZE: usize = 1024 * 1024 * 100; // 100 MB
|
||||||
|
|
||||||
const MAX_PARALLEL_DOWNLOADS: usize = 5;
|
const MAX_PARALLEL_DOWNLOADS: usize = 5;
|
||||||
|
|
||||||
|
const WELL_KNOWN_PATH: &str = "/.well-known/bitcoin-cash-metadata-registry.json";
|
||||||
|
|
||||||
|
/// Resolve an on-chain BCMR URI into one or more candidate URLs to try in order.
|
||||||
|
///
|
||||||
|
/// Handles three cases:
|
||||||
|
/// - `ipfs://<cid>` → a single random IPFS gateway URL.
|
||||||
|
/// - Bare domain (authority only, no path) → BCMR v2 well-known URI first, then
|
||||||
|
/// the bare root URL as a fallback for bug-compatibility with tokens that
|
||||||
|
/// served the registry directly at `/`.
|
||||||
|
/// - Anything with a path → used as-is (with `https://` prepended if missing).
|
||||||
|
fn resolve_uri_candidates(uri: &str) -> Vec<String> {
|
||||||
|
if let Some(stripped) = uri.strip_prefix("ipfs://") {
|
||||||
|
return vec![format!(
|
||||||
|
"{}{}",
|
||||||
|
IPFS_GATEWAYS.choose(&mut thread_rng()).unwrap(),
|
||||||
|
stripped
|
||||||
|
)];
|
||||||
|
}
|
||||||
|
|
||||||
|
let (scheme, rest) = match uri.split_once("://") {
|
||||||
|
Some((s, r)) => (s, r),
|
||||||
|
None => ("https", uri),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (authority, path) = match rest.split_once('/') {
|
||||||
|
Some((a, p)) => (a, format!("/{p}")),
|
||||||
|
None => (rest, String::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if path.is_empty() || path == "/" {
|
||||||
|
vec![
|
||||||
|
format!("{scheme}://{authority}{WELL_KNOWN_PATH}"),
|
||||||
|
format!("{scheme}://{authority}/"),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![format!("{scheme}://{authority}{path}")]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct BCMRDownloader {
|
pub struct BCMRDownloader {
|
||||||
db: SqlitePool,
|
db: SqlitePool,
|
||||||
keep_running: Arc<AtomicBool>,
|
keep_running: Arc<AtomicBool>,
|
||||||
|
|
@ -67,31 +106,21 @@ async fn fetch_bcmr(
|
||||||
let mut errors: Vec<String> = Vec::new();
|
let mut errors: Vec<String> = Vec::new();
|
||||||
|
|
||||||
for url in urls {
|
for url in urls {
|
||||||
let url = if let Some(stripped) = url.strip_prefix("ipfs://") {
|
for resolved in resolve_uri_candidates(url) {
|
||||||
format!(
|
let (contents, actual_hash) =
|
||||||
"{}{}",
|
match get_url(client, &resolved, DOWNLOAD_TIMEOUT, MAX_BCMR_SIZE).await {
|
||||||
IPFS_GATEWAYS.choose(&mut thread_rng()).unwrap(),
|
Ok(c) => c,
|
||||||
stripped
|
Err(e) => {
|
||||||
)
|
errors.push(e.to_string());
|
||||||
} else if !url.starts_with("https://") {
|
continue;
|
||||||
format!("https://{url}")
|
}
|
||||||
} else {
|
};
|
||||||
url.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (contents, actual_hash) =
|
if actual_hash == expected_hash {
|
||||||
match get_url(client, &url, DOWNLOAD_TIMEOUT, MAX_BCMR_SIZE).await {
|
return (Some((contents, actual_hash)), String::default(), false);
|
||||||
Ok(c) => c,
|
} else {
|
||||||
Err(e) => {
|
candidate = Some((contents, actual_hash))
|
||||||
errors.push(e.to_string());
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if actual_hash == expected_hash {
|
|
||||||
return (Some((contents, actual_hash)), String::default(), false);
|
|
||||||
} else {
|
|
||||||
candidate = Some((contents, actual_hash))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,3 +301,72 @@ impl Drop for BCMRDownloader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bare_domain_resolves_to_well_known_then_root() {
|
||||||
|
let candidates = resolve_uri_candidates("chipnet-16.paryonusd.com");
|
||||||
|
assert_eq!(
|
||||||
|
candidates,
|
||||||
|
vec![
|
||||||
|
"https://chipnet-16.paryonusd.com/.well-known/bitcoin-cash-metadata-registry.json"
|
||||||
|
.to_string(),
|
||||||
|
"https://chipnet-16.paryonusd.com/".to_string(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bare_domain_with_scheme_and_trailing_slash() {
|
||||||
|
let candidates = resolve_uri_candidates("https://example.com/");
|
||||||
|
assert_eq!(
|
||||||
|
candidates,
|
||||||
|
vec![
|
||||||
|
"https://example.com/.well-known/bitcoin-cash-metadata-registry.json".to_string(),
|
||||||
|
"https://example.com/".to_string(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn url_with_path_is_used_as_is() {
|
||||||
|
let candidates = resolve_uri_candidates("https://example.com/path/registry.json");
|
||||||
|
assert_eq!(
|
||||||
|
candidates,
|
||||||
|
vec!["https://example.com/path/registry.json".to_string()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schemeless_url_with_path_gets_https() {
|
||||||
|
let candidates = resolve_uri_candidates("example.com/registry.json");
|
||||||
|
assert_eq!(
|
||||||
|
candidates,
|
||||||
|
vec!["https://example.com/registry.json".to_string()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ipfs_uri_uses_a_gateway() {
|
||||||
|
let candidates = resolve_uri_candidates("ipfs://QmAbc123");
|
||||||
|
assert_eq!(candidates.len(), 1);
|
||||||
|
assert!(candidates[0].ends_with("/QmAbc123"));
|
||||||
|
assert!(IPFS_GATEWAYS.iter().any(|gw| candidates[0].starts_with(gw)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn domain_with_port_is_treated_as_bare() {
|
||||||
|
let candidates = resolve_uri_candidates("example.com:8443");
|
||||||
|
assert_eq!(
|
||||||
|
candidates,
|
||||||
|
vec![
|
||||||
|
"https://example.com:8443/.well-known/bitcoin-cash-metadata-registry.json"
|
||||||
|
.to_string(),
|
||||||
|
"https://example.com:8443/".to_string(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue