-
-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
build: Remove
cargo vendor
, mount CARGO_HOME from host instead
- Loading branch information
Showing
4 changed files
with
303 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,297 @@ | ||
use std::time::{Duration as StdDuration, UNIX_EPOCH}; | ||
|
||
use chrono::Duration; | ||
|
||
use bitcoin::{blockdata::constants, Amount}; | ||
use bitcoincore_rpc::RpcApi; | ||
|
||
use crate::bitcoincore_ext::RpcApiExt; | ||
use crate::util::BoolThen; | ||
use crate::{Query, Result}; | ||
|
||
const DIFFCHANGE_INTERVAL: u64 = constants::DIFFCHANGE_INTERVAL as u64; | ||
const TARGET_BLOCK_SPACING: u64 = constants::TARGET_BLOCK_SPACING as u64; | ||
const INITIAL_REWARD: u64 = 50 * constants::COIN_VALUE; | ||
const HALVING_INTERVAL: u64 = 210_000; | ||
//const MAX_BLOCK_WEIGHT: f64 = constants::MAX_BLOCK_WEIGHT as f64; | ||
|
||
// XXX remove % full? | ||
// XXX network.run_from_another_thread(network.interface.session.send_request("server.banner",[getmpk()])) | ||
// FIXME use widetext directly where possible | ||
|
||
pub fn get_welcome_banner(query: &Query, include_supply: bool) -> Result<String> { | ||
let rpc = query.rpc(); | ||
|
||
let net_info = rpc.get_network_info()?; | ||
let chain_info = rpc.get_blockchain_info()?; | ||
let mempool_info = rpc.get_mempool_info()?; | ||
let net_totals = rpc.get_net_totals()?; | ||
let peers = rpc.get_peer_info()?; | ||
let hash_rate_7d = rpc.get_network_hash_ps(1008)?; | ||
let uptime = dur_from_secs(rpc.uptime()?); | ||
let tip_stats = rpc.get_block_stats(&rpc.get_best_block_hash()?)?; | ||
|
||
let est_fee = |target| { | ||
query | ||
.estimate_fee(target) | ||
.ok() | ||
.flatten() | ||
.map_or("ₙ.ₐ.".into(), |rate| format!("{:.1}", rate)) | ||
}; | ||
let est_20m = est_fee(2u16); | ||
let est_3h = est_fee(18u16); | ||
let est_1d = est_fee(144u16); | ||
|
||
//let store_stats = query.store_stats(); | ||
|
||
let mut chain_name = chain_info.chain; | ||
if chain_name == "main" || chain_name == "test" { | ||
chain_name = format!("{}net", chain_name) | ||
}; | ||
|
||
let height = chain_info.headers; | ||
|
||
// 24 hour average bandwidth usage | ||
let num_days = uptime.num_seconds() as f64 / 86400f64; | ||
let bandwidth_up = (net_totals.total_bytes_sent as f64 / num_days) as u64; | ||
let bandwidth_down = (net_totals.total_bytes_recv as f64 / num_days) as u64; | ||
|
||
// Time until the next difficulty adjustment | ||
let retarget_blocks = DIFFCHANGE_INTERVAL - (height % DIFFCHANGE_INTERVAL); | ||
let retarget_dur = dur_from_secs(retarget_blocks * TARGET_BLOCK_SPACING); | ||
|
||
// Current reward era and time until next halving | ||
let reward_era = height / HALVING_INTERVAL; | ||
let block_reward = Amount::from_sat(INITIAL_REWARD / 2u64.pow(reward_era as u32)); | ||
let halving_blocks = HALVING_INTERVAL - (height % HALVING_INTERVAL); | ||
let halving_dur = dur_from_secs(halving_blocks * TARGET_BLOCK_SPACING); | ||
|
||
let tip_ago = match (UNIX_EPOCH + StdDuration::from_secs(tip_stats.time as u64)).elapsed() { | ||
Ok(elapsed) => format!("{} ago", format_dur(&Duration::from_std(elapsed).unwrap())), | ||
Err(_) => format!("just now"), // account for blocks with a timestamp slightly in the future | ||
}; | ||
|
||
//let tip_full = tip_stats.total_weight as f64 / MAX_BLOCK_WEIGHT * 100f64; | ||
|
||
// BTC/kb -> sat/vB | ||
let mempool_min_fee = mempool_info.mempool_min_fee.as_sat() as f64 / 1000f64; | ||
|
||
let has_inbound = peers.iter().any(|p| p.inbound); | ||
|
||
let modes = [ | ||
if chain_info.pruned { | ||
"✂️ ᴘʀᴜɴᴇᴅ" | ||
} else { | ||
"🗄️ ᴀʀᴄʜɪᴠᴀʟ" | ||
}, | ||
if net_info.local_relay { | ||
"🗣️ ᴍᴇᴍᴘᴏᴏʟ ʀᴇʟᴀʏ" | ||
} else { | ||
"🖁 ʙʟᴏᴄᴋꜱᴏɴʟʏ" | ||
}, | ||
if has_inbound { | ||
"👂 ʟɪꜱᴛᴇɴꜱ" | ||
} else { | ||
"🙉 ɴᴏʟɪꜱᴛᴇɴ" | ||
}, | ||
]; | ||
|
||
let ver_lines = big_numbers(crate::BWT_VERSION); | ||
|
||
let (supply_frag, utxo_size_frag) = include_supply | ||
.and_then(|| { | ||
info!("running gettxoutsetinfo to audit the bitcoin supply, this may take a few minutes..."); | ||
Some(rpc.get_tx_out_set_info()) | ||
}) | ||
.transpose()? | ||
.map_or(("".into(), "".into()), |utxo_info| { | ||
let total_supply = utxo_info.total_amount.to_string(); | ||
let supply_frag = format!( | ||
r#" | ||
✔️ ᴠᴇʀɪꜰɪᴇᴅ ✔️ | ||
ᴄɪʀᴄᴜʟᴀᴛɪɴɢ {total_supply} | ||
ꜱᴜᴘᴘʟʏ {total_supply_line} | ||
"#, | ||
total_supply = to_smallcaps(&total_supply), | ||
total_supply_line = "‾".repeat(total_supply.len()), | ||
); | ||
let utxo_size_frag = format!("\n 𝚄𝚃𝚇𝙾 𝚂𝙸𝚉𝙴: 🗃️ {utxo_size}", utxo_size = to_smallcaps(&format_bytes(utxo_info.disk_size))); | ||
(supply_frag, utxo_size_frag) | ||
}); | ||
|
||
|
||
// ᴛʀᴀᴄᴋɪɴɢ: 🔍 {stat_tx_count} ᴛxꜱ & ᴀᴄᴛɪᴠᴇ ᴀᴅᴅʀᴇꜱꜱᴇꜱ | ||
Ok(format!( | ||
r#" | ||
██████ ██ ██ ████████ | ||
██ ██ ██ ██ ██ | ||
██████ ██ █ ██ ██ {ver_line1} | ||
██ ██ ██ ███ ██ ██ █ █ {ver_line2} | ||
██████ ███ ███ ██ ▀▄▀ {ver_line3} | ||
{client_name} | ||
{modes} | ||
𝙽𝙴𝚃𝚆𝙾𝚁𝙺: 🌐 {chain_name} | ||
𝙲𝙾𝙽𝙽𝙴𝙲𝚃𝙴𝙳: 🖧 {connected_peers} ᴘᴇᴇʀꜱ | ||
𝚄𝙿𝚃𝙸𝙼𝙴: ⏱️ {uptime} | ||
𝙱𝙰𝙽𝙳𝚆𝙸𝙳𝚃𝙷: 📡 {bandwidth_up} 🔼 {bandwidth_down} 🔽 (24ʜ ᴀᴠɢ) | ||
𝙲𝙷𝙰𝙸𝙽 𝚂𝙸𝚉𝙴: 💾 {chain_size}{utxo_size_frag} | ||
𝙷𝙰𝚂𝙷𝚁𝙰𝚃𝙴: ⛏️ {hash_rate} (7ᴅ ᴀᴠɢ) | ||
𝙳𝙸𝙵𝙵𝙸𝙲𝚄𝙻𝚃𝚈: 🏋️ {difficulty} (ʀᴇ-🎯 ɪɴ {retarget_dur} ⏳) | ||
𝚁𝙴𝚆𝙰𝚁𝙳 𝙴𝚁𝙰: 🎁 {block_reward:.2} ʙᴛᴄ (½ ɪɴ {halving_dur} ⏳) | ||
𝙻𝙰𝚂𝚃 𝙱𝙻𝙾𝙲𝙺: ⛓️ {tip_height} / {tip_ago} / {tip_size} / {tip_n_tx} | ||
ꜰᴇᴇʀᴀᴛᴇ {tip_fee_per10}-{tip_fee_per90} ꜱᴀᴛ/ᴠʙ / ᴀᴠɢ {tip_fee_avg} ꜱᴀᴛ/ᴠʙ / ᴛᴏᴛᴀʟ {tip_fee_total:.3} ʙᴛᴄ | ||
𝙼𝙴𝙼𝙿𝙾𝙾𝙻: 💭 {mempool_size} / {mempool_n_tx} / ᴍɪɴ {mempool_min_fee} ꜱᴀᴛ/ᴠʙ | ||
𝙵𝙴𝙴𝚂 𝙴𝚂𝚃: 🏷️ 20 ᴍɪɴᴜᴛᴇꜱ: {est_20m} / 3 ʜᴏᴜʀꜱ: {est_3h} / 1 ᴅᴀʏ: {est_1d} (ꜱᴀᴛ/ᴠʙ) | ||
{supply_frag} | ||
𝚂𝚄𝙿𝙿𝙾𝚁𝚃 𝙳𝙴𝚅𝙴𝙻𝙾𝙿𝙼𝙴𝙽𝚃: bc1qmuagsjvq0lh3admnafk0qnlql0vvxv08au9l2d / https://btcpay.shesek.info | ||
"#, | ||
client_name = to_widetext(&net_info.subversion), | ||
chain_name = to_smallcaps(&chain_name), | ||
connected_peers = net_info.connections, | ||
uptime = to_smallcaps(&format_dur(&uptime).to_uppercase()), | ||
bandwidth_up = to_smallcaps(&format_bytes(bandwidth_up)), | ||
bandwidth_down = to_smallcaps(&format_bytes(bandwidth_down)), | ||
chain_size = to_smallcaps(&format_bytes(chain_info.size_on_disk)), | ||
utxo_size_frag = utxo_size_frag, | ||
hash_rate = to_smallcaps(&format_metric(hash_rate_7d, " ", "H/s")), | ||
difficulty = to_smallcaps(&format_metric(chain_info.difficulty as f64, " ", "")), | ||
retarget_dur = to_smallcaps(&format_dur(&retarget_dur).to_uppercase()), | ||
halving_dur = to_smallcaps(&format_dur(&halving_dur).to_uppercase()), | ||
block_reward = block_reward.as_btc(), | ||
tip_height = tip_stats.height, | ||
tip_ago = to_smallcaps(&tip_ago), | ||
tip_size = to_smallcaps(&format_bytes(tip_stats.total_size as u64)), | ||
tip_n_tx = to_smallcaps(&format_metric(tip_stats.txs as f64, "", " txs")), | ||
tip_fee_per10 = tip_stats.feerate_percentiles.0, | ||
tip_fee_per90 = tip_stats.feerate_percentiles.4, | ||
tip_fee_avg = tip_stats.avg_fee_rate, | ||
tip_fee_total = tip_stats.total_fee.as_btc(), | ||
//tip_full = tip_full, / {tip_full:.1}% ꜰᴜʟʟ | ||
mempool_size = to_smallcaps(&format_bytes(mempool_info.bytes)), | ||
mempool_n_tx = to_smallcaps(&format_metric(mempool_info.size as f64, "", " txs")), | ||
mempool_min_fee = mempool_min_fee, | ||
est_20m = est_20m, | ||
est_3h = est_3h, | ||
est_1d = est_1d, | ||
modes = modes.join(" "), | ||
ver_line1 = ver_lines.0, | ||
ver_line2 = ver_lines.1, | ||
ver_line3 = ver_lines.2, | ||
supply_frag = supply_frag, | ||
)) | ||
} | ||
|
||
fn dur_from_secs(seconds: u64) -> Duration { | ||
Duration::from_std(StdDuration::from_secs(seconds)).unwrap() | ||
} | ||
|
||
fn format_dur(dur: &Duration) -> String { | ||
let days = dur.num_days(); | ||
if days > 90 { | ||
return format!("{} months", days / 30); | ||
} | ||
if days > 21 { | ||
return format!("{} weeks", days / 7); | ||
} | ||
if days > 3 { | ||
return format!("{} days", days); | ||
} | ||
let hours = dur.num_hours(); | ||
if hours > 3 { | ||
return format!("{} hours", hours); | ||
} | ||
let minutes = dur.num_minutes(); | ||
if minutes > 3 { | ||
return format!("{} minutes", minutes); | ||
} | ||
format!("{} seconds", dur.num_seconds()) | ||
} | ||
|
||
fn format_bytes(bytes: u64) -> String { | ||
format_metric(bytes as f64, " ", "B") | ||
} | ||
|
||
fn format_metric(num: f64, space: &str, suf: &str) -> String { | ||
if num >= 1000000000000000000f64 { | ||
format!("{:.1}{}E{}", num / 1000000000000000000f64, space, suf) | ||
} else if num >= 1000000000000000f64 { | ||
format!("{:.1}{}P{}", num / 1000000000000000f64, space, suf) | ||
} else if num >= 1000000000000f64 { | ||
format!("{:.1}{}T{}", num / 1000000000000f64, space, suf) | ||
} else if num >= 1000000000f64 { | ||
format!("{:.1}{}G{}", num / 1000000000f64, space, suf) | ||
} else if num >= 1000000f64 { | ||
format!("{:.1}{}M{}", num / 1000000f64, space, suf) | ||
} else if num >= 1000f64 { | ||
format!("{:.1}{}K{}", num / 1000f64, space, suf) | ||
} else { | ||
format!("{:.1}{}{}", num, space, suf) | ||
} | ||
} | ||
|
||
lazy_static! { | ||
static ref SMALLCAPS_ALPHABET: Vec<char> = | ||
"ᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘQʀꜱᴛᴜᴠᴡxʏᴢᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘQʀꜱᴛᴜᴠᴡxʏᴢ01234567890./:".chars().collect::<Vec<_>>(); | ||
static ref WIDETEXT_ALPHABET: Vec<char> = | ||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890./:" | ||
.chars() | ||
.collect::<Vec<_>>(); | ||
} | ||
|
||
fn convert_alphabet(s: &str, alphabet: &[char]) -> String { | ||
s.chars() | ||
.map(|c| match c { | ||
'a'..='z' => alphabet[c as usize - 97], | ||
'A'..='Z' => alphabet[c as usize - 65 + 26], | ||
'0'..='9' => alphabet[c as usize - 48 + 26 * 2], | ||
'.' => alphabet[63], | ||
'/' => alphabet[64], | ||
':' => alphabet[65], | ||
c => c, | ||
}) | ||
.collect() | ||
} | ||
|
||
fn to_smallcaps(s: &str) -> String { | ||
convert_alphabet(s, &SMALLCAPS_ALPHABET[..]) | ||
} | ||
fn to_widetext(s: &str) -> String { | ||
convert_alphabet(s, &WIDETEXT_ALPHABET[..]) | ||
} | ||
|
||
fn big_numbers(s: &str) -> (String, String, String) { | ||
let mut lines = ("".to_string(), "".to_string(), "".to_string()); | ||
for c in s.chars() { | ||
let char_lines = match c { | ||
'0' => ("█▀▀█", "█ █", "█▄▄█"), | ||
'1' => ("▄█ ", " █ ", "▄█▄"), | ||
'2' => ("█▀█", " ▄▀", "█▄▄"), | ||
'3' => ("█▀▀█", " ▀▄", "█▄▄█"), | ||
'4' => (" █▀█ ", "█▄▄█▄", " █ "), | ||
'5' => ("█▀▀", "▀▀▄", "▄▄▀"), | ||
'6' => ("▄▀▀▄", "█▄▄ ", "▀▄▄▀"), | ||
'7' => ("▀▀▀█", " █ ", " ▐▌ "), | ||
'8' => ("▄▀▀▄", "▄▀▀▄", "▀▄▄▀"), | ||
'9' => ("▄▀▀▄", "▀▄▄█", " ▄▄▀"), | ||
'.' => (" ", " ", "█"), | ||
_ => continue, | ||
}; | ||
lines.0.push_str(char_lines.0); | ||
lines.1.push_str(char_lines.1); | ||
lines.2.push_str(char_lines.2); | ||
lines.0.push_str(" "); | ||
lines.1.push_str(" "); | ||
lines.2.push_str(" "); | ||
} | ||
lines | ||
} |