Skip to content

Commit

Permalink
Add /status page (ordinals#2819)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Dec 8, 2023
1 parent 9f6cb92 commit 46cb627
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 27 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ futures = "0.3.21"
hex = "0.4.3"
html-escaper = "0.2.0"
http = "0.2.6"
humantime = "2.1.0"
hyper = { version = "0.14.24", features = ["client", "http2"] }
indicatif = "0.17.1"
lazy_static = "1.4.0"
Expand Down
36 changes: 36 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use std::{process::Command, str};

fn git_branch() -> Option<String> {
str::from_utf8(
&Command::new("git")
.args(["rev-parse", "--abbrev-ref", "HEAD"])
.output()
.ok()?
.stdout,
)
.ok()
.map(|branch| branch.into())
}

fn git_commit() -> Option<String> {
str::from_utf8(
&Command::new("git")
.args(["rev-parse", "--verify", "HEAD"])
.output()
.ok()?
.stdout,
)
.ok()
.map(|branch| branch.into())
}

fn main() {
println!(
"cargo:rustc-env=GIT_BRANCH={}",
git_branch().unwrap_or_default()
);
println!(
"cargo:rustc-env=GIT_COMMIT={}",
git_commit().unwrap_or_default()
);
}
56 changes: 48 additions & 8 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use {
updater::Updater,
},
super::*,
crate::subcommand::find::FindRangeOutput,
crate::wallet::Wallet,
crate::{subcommand::find::FindRangeOutput, templates::StatusHtml},
bitcoin::block::Header,
bitcoincore_rpc::{json::GetBlockHeaderResult, Client},
chrono::SubsecRound,
Expand Down Expand Up @@ -181,6 +181,7 @@ pub(crate) struct Index {
index_sats: bool,
options: Options,
path: PathBuf,
started: DateTime<Utc>,
unrecoverably_reorged: AtomicBool,
}

Expand Down Expand Up @@ -342,10 +343,11 @@ impl Index {
first_inscription_height: options.first_inscription_height(),
genesis_block_coinbase_transaction,
height_limit: options.height_limit,
options: options.clone(),
index_runes,
index_sats,
options: options.clone(),
path,
started: Utc::now(),
unrecoverably_reorged: AtomicBool::new(false),
})
}
Expand Down Expand Up @@ -436,6 +438,48 @@ impl Index {
self.index_sats
}

pub(crate) fn status(&self) -> Result<StatusHtml> {
let rtx = self.database.begin_read()?;

let statistic_to_count = rtx.open_table(STATISTIC_TO_COUNT)?;

let statistic = |statistic: Statistic| -> Result<u64> {
Ok(
statistic_to_count
.get(statistic.key())?
.map(|guard| guard.value())
.unwrap_or_default(),
)
};

let height = rtx
.open_table(HEIGHT_TO_BLOCK_HASH)?
.range(0..)?
.next_back()
.transpose()?
.map(|(height, _hash)| height.value());

let next_height = height.map(|height| height + 1).unwrap_or(0);

let blessed_inscriptions = statistic(Statistic::BlessedInscriptions)?;
let cursed_inscriptions = statistic(Statistic::CursedInscriptions)?;

Ok(StatusHtml {
blessed_inscriptions,
cursed_inscriptions,
height,
inscriptions: blessed_inscriptions + cursed_inscriptions,
lost_sats: statistic(Statistic::LostSats)?,
minimum_rune_for_next_block: Rune::minimum_at_height(Height(next_height)),
rune_index: statistic(Statistic::IndexRunes)? != 0,
runes: statistic(Statistic::Runes)?,
sat_index: statistic(Statistic::IndexSats)? != 0,
started: self.started,
unrecoverably_reorged: self.unrecoverably_reorged.load(atomic::Ordering::Relaxed),
uptime: (Utc::now() - self.started).to_std()?,
})
}

pub(crate) fn info(&self) -> Result<Info> {
fn insert_table_info<K: RedbKey + 'static, V: RedbValue + 'static>(
tables: &mut BTreeMap<String, TableInfo>,
Expand Down Expand Up @@ -709,10 +753,6 @@ impl Index {
Ok(())
}

pub(crate) fn is_unrecoverably_reorged(&self) -> bool {
self.unrecoverably_reorged.load(atomic::Ordering::Relaxed)
}

fn begin_read(&self) -> Result<rtx::Rtx> {
Ok(rtx::Rtx(self.database.begin_read()?))
}
Expand All @@ -728,7 +768,7 @@ impl Index {
let value = statistic_to_count
.get(&(statistic.key()))?
.map(|x| x.value())
.unwrap_or(0)
.unwrap_or_default()
+ n;
statistic_to_count.insert(&statistic.key(), &value)?;
Ok(())
Expand All @@ -745,7 +785,7 @@ impl Index {
.get(&statistic.key())
.unwrap()
.map(|x| x.value())
.unwrap_or(0)
.unwrap_or_default()
}

pub(crate) fn block_count(&self) -> Result<u32> {
Expand Down
78 changes: 61 additions & 17 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use {
PreviewAudioHtml, PreviewCodeHtml, PreviewFontHtml, PreviewImageHtml, PreviewMarkdownHtml,
PreviewModelHtml, PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml, PreviewVideoHtml,
RangeHtml, RareTxt, RuneHtml, RunesHtml, SatHtml, SatInscriptionJson, SatInscriptionsJson,
SatJson, TransactionHtml,
SatJson, StatusHtml, TransactionHtml,
},
},
axum::{
Expand Down Expand Up @@ -750,18 +750,11 @@ impl Server {
Ok(Json(hex::encode(metadata)))
}

async fn status(Extension(index): Extension<Arc<Index>>) -> (StatusCode, &'static str) {
if index.is_unrecoverably_reorged() {
(
StatusCode::OK,
"unrecoverable reorg detected, please rebuild the database.",
)
} else {
(
StatusCode::OK,
StatusCode::OK.canonical_reason().unwrap_or_default(),
)
}
async fn status(
Extension(page_config): Extension<Arc<PageConfig>>,
Extension(index): Extension<Arc<Index>>,
) -> ServerResult<PageHtml<StatusHtml>> {
Ok(index.status()?.page(page_config))
}

async fn search_by_query(
Expand Down Expand Up @@ -973,7 +966,7 @@ impl Server {
Extension(page_config): Extension<Arc<PageConfig>>,
Extension(index): Extension<Arc<Index>>,
Path(path): Path<(u32, usize, usize)>,
) -> Result<PageHtml<InputHtml>, ServerError> {
) -> ServerResult<PageHtml<InputHtml>> {
let not_found = || format!("input /{}/{}/{}", path.0, path.1, path.2);

let block = index
Expand Down Expand Up @@ -2458,7 +2451,50 @@ mod tests {

#[test]
fn status() {
TestServer::new().assert_response("/status", StatusCode::OK, "OK");
let test_server = TestServer::new();

test_server.assert_response_regex(
"/status",
StatusCode::OK,
".*<h1>Status</h1>
<dl>
<dt>height</dt>
<dd>0</dd>
<dt>inscriptions</dt>
<dd>0</dd>
<dt>blessed inscriptions</dt>
<dd>0</dd>
<dt>cursed inscriptions</dt>
<dd>0</dd>
<dt>runes</dt>
<dd>0</dd>
<dt>lost sats</dt>
<dd>.*</dd>
<dt>started</dt>
<dd>.*</dd>
<dt>uptime</dt>
<dd>.*</dd>
<dt>minimum rune for next block</dt>
<dd>AAAAAAAAAAAAA</dd>
<dt>version</dt>
<dd>.*</dd>
<dt>unrecoverably reorged</dt>
<dd>false</dd>
<dt>sat index</dt>
<dd>false</dd>
<dt>rune index</dt>
<dd>false</dd>
<dt>git branch</dt>
<dd>.*</dd>
<dt>git commit</dt>
<dd>
<a href=https://github.com/ordinals/ord/commit/[[:xdigit:]]{40}>
[[:xdigit:]]{40}
</a>
</dd>
</dl>
.*",
);
}

#[test]
Expand Down Expand Up @@ -2994,15 +3030,23 @@ mod tests {

test_server.mine_blocks(21);

test_server.assert_response("/status", StatusCode::OK, "OK");
test_server.assert_response_regex(
"/status",
StatusCode::OK,
".*<dt>unrecoverably reorged</dt>\n <dd>false</dd>.*",
);

for _ in 0..15 {
test_server.bitcoin_rpc_server.invalidate_tip();
}

test_server.bitcoin_rpc_server.mine_blocks(21);

test_server.assert_response_regex("/status", StatusCode::OK, "unrecoverable reorg detected.*");
test_server.assert_response_regex(
"/status",
StatusCode::OK,
".*<dt>unrecoverably reorged</dt>\n <dd>true</dd>.*",
);
}

#[test]
Expand Down
2 changes: 2 additions & 0 deletions src/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub(crate) use {
rune::RuneHtml,
runes::RunesHtml,
sat::{SatHtml, SatInscriptionJson, SatInscriptionsJson, SatJson},
status::StatusHtml,
transaction::TransactionHtml,
};

Expand All @@ -46,6 +47,7 @@ mod rare;
mod rune;
mod runes;
pub mod sat;
mod status;
mod transaction;

#[derive(Boilerplate)]
Expand Down
23 changes: 23 additions & 0 deletions src/templates/status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::*;

#[derive(Boilerplate)]
pub(crate) struct StatusHtml {
pub(crate) blessed_inscriptions: u64,
pub(crate) cursed_inscriptions: u64,
pub(crate) height: Option<u32>,
pub(crate) inscriptions: u64,
pub(crate) lost_sats: u64,
pub(crate) minimum_rune_for_next_block: Rune,
pub(crate) rune_index: bool,
pub(crate) runes: u64,
pub(crate) sat_index: bool,
pub(crate) started: DateTime<Utc>,
pub(crate) unrecoverably_reorged: bool,
pub(crate) uptime: Duration,
}

impl PageContent for StatusHtml {
fn title(&self) -> String {
"Status".into()
}
}
43 changes: 43 additions & 0 deletions templates/status.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<h1>Status</h1>
<dl>
%% if let Some(height) = self.height {
<dt>height</dt>
<dd>{{ height }}</dd>
%% }
<dt>inscriptions</dt>
<dd>{{ self.inscriptions }}</dd>
<dt>blessed inscriptions</dt>
<dd>{{ self.blessed_inscriptions }}</dd>
<dt>cursed inscriptions</dt>
<dd>{{ self.cursed_inscriptions }}</dd>
<dt>runes</dt>
<dd>{{ self.runes }}</dd>
<dt>lost sats</dt>
<dd>{{ self.lost_sats }}</dd>
<dt>started</dt>
<dd>{{ self.started }}</dd>
<dt>uptime</dt>
<dd>{{ humantime::format_duration(self.uptime) }}</dd>
<dt>minimum rune for next block</dt>
<dd>{{ self.minimum_rune_for_next_block }}</dd>
<dt>version</dt>
<dd>{{ env!("CARGO_PKG_VERSION") }}</dd>
<dt>unrecoverably reorged</dt>
<dd>{{ self.unrecoverably_reorged }}</dd>
<dt>sat index</dt>
<dd>{{ self.sat_index }}</dd>
<dt>rune index</dt>
<dd>{{ self.rune_index }}</dd>
%% if !env!("GIT_BRANCH").is_empty() {
<dt>git branch</dt>
<dd>{{ env!("GIT_BRANCH") }}</dd>
%% }
%% if !env!("GIT_COMMIT").is_empty() {
<dt>git commit</dt>
<dd>
<a href=https://github.com/ordinals/ord/commit/{{ env!("GIT_COMMIT") }}>
{{ env!("GIT_COMMIT") }}
</a>
</dd>
%% }
</dl>
1 change: 0 additions & 1 deletion tests/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ fn preview() {
for attempt in 0.. {
if let Ok(response) = reqwest::blocking::get(format!("http://127.0.0.1:{port}/status")) {
if response.status() == 200 {
assert_eq!(response.text().unwrap(), "OK");
break;
}
}
Expand Down
1 change: 0 additions & 1 deletion tests/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ fn run() {
for attempt in 0.. {
if let Ok(response) = reqwest::blocking::get(format!("http://localhost:{port}/status")) {
if response.status() == 200 {
assert_eq!(response.text().unwrap(), "OK");
break;
}
}
Expand Down

0 comments on commit 46cb627

Please sign in to comment.