use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Family {
Stock,
Blake2b,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Pow {
Sha256d,
Blake2b,
Scrypt,
Verthash,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Fork {
pub height: u32,
pub hash: &'static str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Parent {
pub alias: &'static str,
pub network: Option<&'static str>,
pub label: &'static str,
pub family: Family,
pub pow: Pow,
pub mainnet: bool,
pub genesis: Option<&'static str>,
pub fork: Option<Fork>,
pub reserved: bool,
}
pub const COINBASE_MATURITY: u32 = 100;
pub const TXBT4_COINBASE_MATURITY: u32 = 6_705;
impl Parent {
pub const fn coinbase_maturity(&self) -> u32 {
if matches!(self.family, Family::Blake2b) && !self.mainnet {
TXBT4_COINBASE_MATURITY
} else {
COINBASE_MATURITY
}
}
pub const fn is_mature(&self, coin_height: u32, coinbase: bool, tip: u32) -> bool {
!coinbase || (tip >= coin_height && tip - coin_height + 1 >= self.coinbase_maturity())
}
}
pub const PARENTS: [Parent; 6] = [
Parent {
alias: "btc",
network: Some("btc:mainnet"),
label: "Bitcoin mainnet",
family: Family::Stock,
pow: Pow::Sha256d,
mainnet: true,
genesis: Some("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
fork: None,
reserved: false,
},
Parent {
alias: "tbtc4",
network: Some("btc:testnet4"),
label: "Bitcoin testnet4",
family: Family::Stock,
pow: Pow::Sha256d,
mainnet: false,
genesis: Some("00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043"),
fork: None,
reserved: false,
},
Parent {
alias: "xbt",
network: Some("btc:mainnet-blake2b"),
label: "BLAKE2b mainnet (Knots)",
family: Family::Blake2b,
pow: Pow::Blake2b,
mainnet: true,
genesis: Some("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
fork: Some(Fork {
height: 961_640,
hash: "0000000000000050c1e5f69672f459293be14f46e5a494e7a8c8541396f18eeb",
}),
reserved: false,
},
Parent {
alias: "txbt4",
network: Some("btc:testnet4-blake2b"),
label: "BLAKE2b testnet4 (Knots)",
family: Family::Blake2b,
pow: Pow::Blake2b,
mainnet: false,
genesis: Some("00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043"),
fork: Some(Fork {
height: 150_308,
hash: "000000000000b9d1b7e1bb0e77215ee92c6ef7ec8f4473e23908380649e779b6",
}),
reserved: false,
},
Parent {
alias: "ltc",
network: None,
label: "Litecoin mainnet",
family: Family::Stock,
pow: Pow::Scrypt,
mainnet: true,
genesis: None,
fork: None,
reserved: true,
},
Parent {
alias: "vtc",
network: None,
label: "Vertcoin mainnet",
family: Family::Stock,
pow: Pow::Verthash,
mainnet: true,
genesis: None,
fork: None,
reserved: true,
},
];
pub fn parent_alias(id: &str) -> Option<&'static str> {
PARENTS
.iter()
.find(|p| p.alias == id || p.network == Some(id))
.map(|p| p.alias)
}
pub fn resolve_parent(id: &str) -> Result<&'static Parent> {
let alias = parent_alias(id).ok_or_else(|| Error::UnknownParent(id.to_string()))?;
let p = PARENTS
.iter()
.find(|p| p.alias == alias)
.expect("alias came from the table");
if p.reserved {
return Err(Error::ReservedParent {
alias: p.alias,
label: p.label,
});
}
Ok(p)
}
pub fn is_blake2b(id: &str) -> Result<bool> {
Ok(resolve_parent(id)?.family == Family::Blake2b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn table() {
assert_eq!(
resolve_parent("txbt4").unwrap().network,
Some("btc:testnet4-blake2b")
);
assert_eq!(
resolve_parent("btc:testnet4-blake2b").unwrap().alias,
"txbt4"
);
assert_eq!(
resolve_parent("xbt").unwrap().network,
Some("btc:mainnet-blake2b")
);
assert_eq!(resolve_parent("btc:mainnet-blake2b").unwrap().alias, "xbt");
let by = |a: &str| PARENTS.iter().find(|p| p.alias == a).unwrap();
assert_eq!(by("xbt").genesis, by("btc").genesis);
assert_eq!(by("txbt4").genesis, by("tbtc4").genesis);
assert_eq!(by("txbt4").fork.unwrap().height, 150_308);
assert_eq!(by("xbt").fork.unwrap().height, 961_640);
assert!(is_blake2b("txbt4").unwrap() && is_blake2b("xbt").unwrap());
assert!(!is_blake2b("btc").unwrap() && !is_blake2b("tbtc4").unwrap());
assert_eq!(parent_alias("xbt4"), None);
assert!(matches!(
resolve_parent("ltc"),
Err(Error::ReservedParent { alias: "ltc", .. })
));
let e = resolve_parent("doge").unwrap_err().to_string();
assert!(e.contains("unknown parent \"doge\"") && e.contains("txbt4"));
assert!(resolve_parent("xbt").unwrap().mainnet && resolve_parent("btc").unwrap().mainnet);
assert!(
!resolve_parent("txbt4").unwrap().mainnet
&& !resolve_parent("btc:testnet4-blake2b").unwrap().mainnet
);
for name in ["__proto__", "constructor", "toString", ""] {
assert_eq!(parent_alias(name), None);
}
}
#[test]
fn parent_coinbase_maturity() {
let txbt4 = resolve_parent("txbt4").unwrap();
assert_eq!(txbt4.coinbase_maturity(), 6_705);
assert!(txbt4.is_mature(100, true, 6_804));
assert!(!txbt4.is_mature(100, true, 6_803));
assert!(!txbt4.is_mature(152_079, true, 152_201));
assert!(txbt4.is_mature(152_079, true, 158_783));
assert!(!txbt4.is_mature(152_079, true, 158_782));
assert!(txbt4.is_mature(152_079, false, 152_079));
assert!(!txbt4.is_mature(152_079, true, 152_000));
for alias in ["btc", "tbtc4", "xbt"] {
let p = resolve_parent(alias).unwrap();
assert_eq!(p.coinbase_maturity(), 100, "{alias}");
assert!(p.is_mature(100, true, 199) && !p.is_mature(100, true, 198));
}
assert_eq!(crate::rules::Params::default().coinbase_maturity, 100);
}
}