SHOWING: First 60
fn clock_updates() {
let test_server = TestServer::new();
test_server.assert_response_regex("/clock", StatusCode::OK, ".*<text.*>0</text>.*");
test_server.mine_blocks(1);
test_server.assert_response_regex("/clock", StatusCode::OK, ".*<text.*>1</text>.*");
}
#[test]
fn block_by_hash() {
let test_server = TestServer::new();
test_server.mine_blocks(1);
let transaction = TransactionTemplate {
inputs: &[(1, 0, 0, Default::default())],
fee: 0,
..Default::default()
};
test_server.bitcoin_rpc_server.broadcast_tx(transaction);
let block_hash = test_server.mine_blocks(1)[0].block_hash();
test_server.assert_response_regex(
format!("/block/{block_hash}"),
StatusCode::OK,
".*<h1>Block 2</h1>.*",
);
}
#[test]
fn block_by_height() {
let test_server = TestServer::new();
test_server.assert_response_regex("/block/0", StatusCode::OK, ".*<h1>Block 0</h1>.*");
}
#[test]
fn transaction() {
let test_server = TestServer::new();
#44,987,773text options,
)?)))
.directory(if cfg!(test) {
LETS_ENCRYPT_STAGING_DIRECTORY
} else {
LETS_ENCRYPT_PRODUCTION_DIRECTORY
});
let mut state = config.state();
let acceptor = state.axum_acceptor(Arc::new(
rustls::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_cert_resolver(state.resolver()),
));
tokio::spawn(async move {
while let Some(result) = state.next().await {
match result {
Ok(ok) => log::info!("ACME event: {:?}", ok),
Err(err) => log::error!("ACME error: {:?}", err),
}
}
});
Ok(acceptor)
}
fn index_height(index: &Index) -> ServerResult<Height> {
index.block_height()?.ok_or_not_found(|| "genesis block")
}
async fn clock(Extension(index): Extension<Arc<Index>>) -> ServerResult<Response> {
Ok(
(
[(
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static("default-src 'unsafe-inline'"),
#44,987,743text "baz",
])
.unwrap_err()
.to_string(),
".*--cbor-metadata.*cannot be used with.*--json-metadata.*"
);
}
#[test]
fn batch_is_loaded_from_yaml_file() {
let parent = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi0"
.parse::<InscriptionId>()
.unwrap();
let tempdir = TempDir::new().unwrap();
let inscription_path = tempdir.path().join("tulip.txt");
fs::write(&inscription_path, "tulips are pretty").unwrap();
let brc20_path = tempdir.path().join("token.json");
let batch_path = tempdir.path().join("batch.yaml");
fs::write(
&batch_path,
format!(
"mode: separate-outputs
parent: {parent}
inscriptions:
- file: {}
metadata:
title: Lorem Ipsum
description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tristique, massa nec condimentum venenatis, ante massa tempor velit, et accumsan ipsum ligula a massa. Nunc quis orci ante.
- file: {}
metaprotocol: brc-20
",
inscription_path.display(),
#44,987,732text let acme_cache = if let Some(acme_cache) = acme_cache {
acme_cache.clone()
} else {
options.data_dir()?.join("acme-cache")
};
Ok(acme_cache)
}
fn acme_domains(&self) -> Result<Vec<String>> {
if !self.acme_domain.is_empty() {
Ok(self.acme_domain.clone())
} else {
Ok(vec![System::new()
.host_name()
.ok_or(anyhow!("no hostname found"))?])
}
}
fn http_port(&self) -> Option<u16> {
if self.http || self.http_port.is_some() || (self.https_port.is_none() && !self.https) {
Some(self.http_port.unwrap_or(80))
} else {
None
}
}
fn https_port(&self) -> Option<u16> {
if self.https || self.https_port.is_some() {
Some(self.https_port.unwrap_or(443))
} else {
None
}
}
fn acceptor(&self, options: &Options) -> Result<AxumAcceptor> {
let config = AcmeConfig::new(self.acme_domains()?)
.contact(&self.acme_contact)
.cache_option(Some(DirCache::new(Self::acme_cache(
self.acme_cache.as_ref(),#44,987,722text .route(
"/r/sat/:sat_number/:page",
get(Self::sat_inscriptions_paginated),
)
.route(
"/r/sat/:sat_number/at/:index",
get(Self::sat_inscription_at_index),
)
.route("/range/:start/:end", get(Self::range))
.route("/rare.txt", get(Self::rare_txt))
.route("/rune/:rune", get(Self::rune))
.route("/runes", get(Self::runes))
.route("/sat/:sat", get(Self::sat))
.route("/search", get(Self::search_by_query))
.route("/search/*query", get(Self::search_by_path))
.route("/static/*path", get(Self::static_asset))
.route("/status", get(Self::status))
.route("/tx/:txid", get(Self::transaction))
.layer(Extension(index))
.layer(Extension(page_config))
.layer(Extension(Arc::new(config)))
.layer(SetResponseHeaderLayer::if_not_present(
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static("default-src 'self'"),
))
.layer(SetResponseHeaderLayer::overriding(
#44,987,707text )],
ClockSvg::new(Self::index_height(&index)?),
)
.into_response(),
)
}
async fn sat(
Extension(page_config): Extension<Arc<PageConfig>>,
Extension(index): Extension<Arc<Index>>,
Path(DeserializeFromStr(sat)): Path<DeserializeFromStr<Sat>>,
accept_json: AcceptJson,
) -> ServerResult<Response> {
let inscriptions = index.get_inscription_ids_by_sat(sat)?;
let satpoint = index.rare_sat_satpoint(sat)?.or_else(|| {
inscriptions.first().and_then(|&first_inscription_id| {
index
.get_inscription_satpoint_by_id(first_inscription_id)
.ok()
.flatten()
})
});
let blocktime = index.block_time(sat.height())?;
Ok(if accept_json.0 {
Json(SatJson {
number: sat.0,
decimal: sat.decimal().to_string(),
degree: sat.degree().to_string(),
name: sat.name(),
block: sat.height().0,
cycle: sat.cycle(),
epoch: sat.epoch().0,
period: sat.period(),
#44,987,704text
test_server.mine_blocks(21);
test_server.assert_response("/status", StatusCode::OK, "OK");
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]
fn rare_with_sat_index() {
TestServer::new_with_sat_index().assert_response(
"/rare.txt",
StatusCode::OK,
"sat\tsatpoint
0\t4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0:0
",
);
}
#[test]
fn rare_without_sat_index() {
TestServer::new().assert_response(
"/rare.txt",
StatusCode::OK,
"sat\tsatpoint
",
);
}
#[test]
fn show_rare_txt_in_header_with_sat_index() {
TestServer::new_with_sat_index().assert_response_regex(
"/",
StatusCode::OK,
".*
<a href=/clock title=clock>.*</a>
<a href=/rare.txt title=rare>.*</a>.*",
);
}
#[test]
fn rare_sat_location() {
#44,987,675text offset: sat.third(),
rarity: sat.rarity(),
percentile: sat.percentile(),
satpoint,
timestamp: blocktime.timestamp().timestamp(),
inscriptions,
})
.into_response()
} else {
SatHtml {
sat,
satpoint,
blocktime,
inscriptions,
}
.page(page_config)
.into_response()
})
}
async fn ordinal(Path(sat): Path<String>) -> Redirect {
Redirect::to(&format!("/sat/{sat}"))
}
async fn output(
Extension(page_config): Extension<Arc<PageConfig>>,
Extension(index): Extension<Arc<Index>>,
Path(outpoint): Path<OutPoint>,
accept_json: AcceptJson,
) -> ServerResult<Response> {
let list = index.list(outpoint)?;
let output = if outpoint == OutPoint::null() || outpoint == unbound_outpoint() {
let mut value = 0;
if let Some(List::Unspent(ranges)) = &list {
for (start, end) in ranges {
value += end - start;
}
}
TxOut {
value,
#44,987,673text script_pubkey: ScriptBuf::new(),
}
} else {
index
.get_transaction(outpoint.txid)?
.ok_or_not_found(|| format!("output {outpoint}"))?
.output
.into_iter()
.nth(outpoint.vout as usize)
.ok_or_not_found(|| format!("output {outpoint}"))?
};
let inscriptions = index.get_inscriptions_on_output(outpoint)?;
let runes = index.get_rune_balances_for_outpoint(outpoint)?;
Ok(if accept_json.0 {
Json(OutputJson::new(
outpoint,
list,
page_config.chain,
output,
inscriptions,
runes
.into_iter()
.map(|(rune, pile)| (rune, pile.amount))
.collect(),
))
.into_response()
} else {
OutputHtml {
outpoint,
inscriptions,
list,
chain: page_config.chain,
output,
runes,
}
.page(page_config)
.into_response()
})
}
async fn range(
Extension(page_config): Extension<Arc<PageConfig>>,
#44,987,672textuse {super::*, crate::wallet::Wallet, std::collections::BTreeSet};
#[derive(Serialize, Deserialize)]
pub struct CardinalUtxo {
pub output: OutPoint,
pub amount: u64,
}
pub(crate) fn run(options: Options) -> SubcommandResult {
let index = Index::open(&options)?;
index.update()?;
let unspent_outputs = index.get_unspent_outputs(Wallet::load(&options)?)?;
let inscribed_utxos = index
.get_inscriptions(&unspent_outputs)?
.keys()
.map(|satpoint| satpoint.outpoint)
.collect::<BTreeSet<OutPoint>>();
let cardinal_utxos = unspent_outputs
.iter()
.filter_map(|(output, amount)| {
if inscribed_utxos.contains(output) {
None
} else {
Some(CardinalUtxo {
output: *output,
amount: amount.to_sat(),
})
}
})
.collect::<Vec<CardinalUtxo>>();
Ok(Box::new(cardinal_utxos))
}
#44,987,665text brc20_path.display()
),
)
.unwrap();
let mut metadata = Mapping::new();
metadata.insert(
Value::String("title".to_string()),
Value::String("Lorem Ipsum".to_string()),
);
metadata.insert(Value::String("description".to_string()), Value::String("Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tristique, massa nec condimentum venenatis, ante massa tempor velit, et accumsan ipsum ligula a massa. Nunc quis orci ante.".to_string()));
assert_eq!(
Batchfile::load(&batch_path).unwrap(),
Batchfile {
inscriptions: vec![
BatchEntry {
file: inscription_path,
metadata: Some(Value::Mapping(metadata)),
..Default::default()
},
BatchEntry {
file: brc20_path,
metaprotocol: Some("brc-20".to_string()),
..Default::default()
}
],
parent: Some(parent),
..Default::default()
}
);
}
#[test]
fn batch_with_unknown_field_throws_error() {
#44,987,664textdmt-mint.nat#44,764,490tapdmt-mint.nat#44,764,460tapdmt-mint.nat#44,764,458tapdmt-mint.nat#44,764,429tapdmt-mint.nat#44,764,418tapdmt-mint.nat#44,764,417tapdmt-mint.nat#44,764,394tapdmt-mint.nat#44,764,388tapdmt-mint.nat#44,764,326tapdmt-mint.nat#44,764,231tap