From a18a77912a9b7d5a698a64f0f94abe8247e58ddf Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 25 Nov 2022 10:58:09 -0800 Subject: [PATCH] Conditionally disable ordinal index dependent server features (#845) --- src/index.rs | 72 ++++++++------- src/index/updater.rs | 7 +- src/options.rs | 2 +- src/subcommand/server.rs | 103 ++++++++++++++++++---- src/subcommand/server/templates.rs | 71 +++++++++++++-- src/subcommand/server/templates/output.rs | 33 ++++++- templates/output.html | 4 +- templates/page.html | 2 + 8 files changed, 235 insertions(+), 59 deletions(-) diff --git a/src/index.rs b/src/index.rs index 2f4c48b6de..e5c0685f71 100644 --- a/src/index.rs +++ b/src/index.rs @@ -53,7 +53,6 @@ pub(crate) struct Index { genesis_block_coinbase_transaction: Transaction, genesis_block_coinbase_txid: Txid, height_limit: Option, - index_ordinals: bool, reorged: AtomicBool, rpc_url: String, } @@ -161,37 +160,43 @@ impl Index { let database = match unsafe { redb::Database::open(&database_path) } { Ok(database) => database, - Err(redb::Error::Io(error)) if error.kind() == io::ErrorKind::NotFound => unsafe { - Database::builder() - .set_write_strategy(if cfg!(test) { - WriteStrategy::Checksum - } else { - WriteStrategy::TwoPhase - }) - .create(&database_path)? - }, - Err(error) => return Err(error.into()), - }; + Err(redb::Error::Io(error)) if error.kind() == io::ErrorKind::NotFound => { + let database = unsafe { + Database::builder() + .set_write_strategy(if cfg!(test) { + WriteStrategy::Checksum + } else { + WriteStrategy::TwoPhase + }) + .create(&database_path)? + }; + let tx = database.begin_write()?; + + #[cfg(test)] + let tx = { + let mut tx = tx; + tx.set_durability(redb::Durability::None); + tx + }; + + tx.open_table(HEIGHT_TO_BLOCK_HASH)?; + tx.open_table(ORDINAL_TO_INSCRIPTION_TXID)?; + tx.open_table(ORDINAL_TO_SATPOINT)?; + tx.open_table(STATISTIC_TO_COUNT)?; + tx.open_table(TXID_TO_INSCRIPTION)?; + tx.open_table(WRITE_TRANSACTION_STARTING_BLOCK_COUNT_TO_TIMESTAMP)?; + + if options.index_ordinals { + tx.open_table(OUTPOINT_TO_ORDINAL_RANGES)?; + } - let tx = database.begin_write()?; + tx.commit()?; - #[cfg(test)] - let tx = { - let mut tx = tx; - tx.set_durability(redb::Durability::None); - tx + database + } + Err(error) => return Err(error.into()), }; - tx.open_table(HEIGHT_TO_BLOCK_HASH)?; - tx.open_table(ORDINAL_TO_INSCRIPTION_TXID)?; - tx.open_table(ORDINAL_TO_SATPOINT)?; - tx.open_table(OUTPOINT_TO_ORDINAL_RANGES)?; - tx.open_table(STATISTIC_TO_COUNT)?; - tx.open_table(TXID_TO_INSCRIPTION)?; - tx.open_table(WRITE_TRANSACTION_STARTING_BLOCK_COUNT_TO_TIMESTAMP)?; - - tx.commit()?; - let genesis_block_coinbase_transaction = options.chain.genesis_block().coinbase().unwrap().clone(); @@ -204,14 +209,21 @@ impl Index { database_path, genesis_block_coinbase_transaction, height_limit: options.height_limit, - index_ordinals: options.index_ordinals, reorged: AtomicBool::new(false), rpc_url, }) } + pub(crate) fn has_ordinal_index(&self) -> Result { + match self.begin_read()?.0.open_table(OUTPOINT_TO_ORDINAL_RANGES) { + Ok(_) => Ok(true), + Err(redb::Error::TableDoesNotExist(_)) => Ok(false), + Err(err) => Err(err.into()), + } + } + fn require_ordinal_index(&self, feature: &str) -> Result { - if !self.index_ordinals { + if !self.has_ordinal_index()? { bail!("{feature} requires `--index-ordinals` flag") } diff --git a/src/index/updater.rs b/src/index/updater.rs index 54d1759e50..a72f303f81 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -37,7 +37,7 @@ impl Updater { cache: HashMap::new(), chain: index.chain, height, - index_ordinals: index.index_ordinals, + index_ordinals: index.has_ordinal_index()?, ordinal_ranges_since_flush: 0, outputs_cached: 0, outputs_inserted_since_flush: 0, @@ -207,7 +207,6 @@ impl Updater { let mut height_to_block_hash = wtx.open_table(HEIGHT_TO_BLOCK_HASH)?; let mut ordinal_to_satpoint = wtx.open_table(ORDINAL_TO_SATPOINT)?; let mut ordinal_to_inscription_txid = wtx.open_table(ORDINAL_TO_INSCRIPTION_TXID)?; - let mut outpoint_to_ordinal_ranges = wtx.open_table(OUTPOINT_TO_ORDINAL_RANGES)?; let mut txid_to_inscription = wtx.open_table(TXID_TO_INSCRIPTION)?; let start = Instant::now(); @@ -233,6 +232,8 @@ impl Updater { } if self.index_ordinals { + let mut outpoint_to_ordinal_ranges = wtx.open_table(OUTPOINT_TO_ORDINAL_RANGES)?; + let mut coinbase_inputs = VecDeque::new(); let h = Height(self.height); @@ -396,7 +397,7 @@ impl Updater { self.outputs_cached ); - { + if self.index_ordinals { log::info!( "Flushing {} entries ({:.1}% resulting from {} insertions) from memory to database", self.cache.len(), diff --git a/src/options.rs b/src/options.rs index 059851b5ea..e823ba420f 100644 --- a/src/options.rs +++ b/src/options.rs @@ -15,7 +15,7 @@ pub(crate) struct Options { data_dir: Option, #[clap(long, help = "Limit index to blocks.")] pub(crate) height_limit: Option, - #[clap(long, help = "Index ordinal ranges")] + #[clap(long, help = "Index ordinal ranges.")] pub(crate) index_ordinals: bool, #[clap(long, help = "Connect to Bitcoin Core RPC at .")] rpc_url: Option, diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index ee32b622ff..42578535d0 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -320,7 +320,10 @@ impl Server { )) })?, } - .page(chain), + .page( + chain, + index.has_ordinal_index().map_err(ServerError::Internal)?, + ), ) } @@ -329,11 +332,6 @@ impl Server { Extension(index): Extension>, Path(outpoint): Path, ) -> ServerResult { - let list = index - .list(outpoint) - .map_err(ServerError::Internal)? - .ok_or_else(|| ServerError::NotFound(format!("output {outpoint} unknown")))?; - let output = index .transaction(outpoint.txid) .map_err(ServerError::Internal)? @@ -346,16 +344,29 @@ impl Server { Ok( OutputHtml { outpoint, - list, + list: if index.has_ordinal_index().map_err(ServerError::Internal)? { + Some( + index + .list(outpoint) + .map_err(ServerError::Internal)? + .ok_or_else(|| ServerError::NotFound(format!("output {outpoint} unknown")))?, + ) + } else { + None + }, chain, output, } - .page(chain), + .page( + chain, + index.has_ordinal_index().map_err(ServerError::Internal)?, + ), ) } async fn range( Extension(chain): Extension, + Extension(index): Extension>, Path((DeserializeFromStr(start), DeserializeFromStr(end))): Path<( DeserializeFromStr, DeserializeFromStr, @@ -366,7 +377,10 @@ impl Server { Ordering::Greater => Err(ServerError::BadRequest( "range start greater than range end".to_string(), )), - Ordering::Less => Ok(RangeHtml { start, end }.page(chain)), + Ordering::Less => Ok(RangeHtml { start, end }.page( + chain, + index.has_ordinal_index().map_err(ServerError::Internal)?, + )), } } @@ -386,7 +400,10 @@ impl Server { .blocks(100) .map_err(|err| ServerError::Internal(anyhow!("error getting blocks: {err}")))?, ) - .page(chain), + .page( + chain, + index.has_ordinal_index().map_err(ServerError::Internal)?, + ), ) } @@ -431,7 +448,12 @@ impl Server { } }; - Ok(BlockHtml::new(block, Height(height), Self::index_height(&index)?).page(chain)) + Ok( + BlockHtml::new(block, Height(height), Self::index_height(&index)?).page( + chain, + index.has_ordinal_index().map_err(ServerError::Internal)?, + ), + ) } async fn transaction( @@ -451,7 +473,10 @@ impl Server { .ok_or_else(|| ServerError::NotFound(format!("transaction {txid} unknown")))?, chain, ) - .page(chain), + .page( + chain, + index.has_ordinal_index().map_err(ServerError::Internal)?, + ), ) } @@ -569,7 +594,10 @@ impl Server { .nth(path.2) .ok_or_else(not_found)?; - Ok(InputHtml { path, input }.page(chain)) + Ok(InputHtml { path, input }.page( + chain, + index.has_ordinal_index().map_err(ServerError::Internal)?, + )) } async fn faq() -> Redirect { @@ -597,7 +625,10 @@ impl Server { })? .ok_or_else(|| ServerError::NotFound(format!("transaction {txid} has no inscription")))?, } - .page(chain), + .page( + chain, + index.has_ordinal_index().map_err(ServerError::Internal)?, + ), ) } } @@ -1064,8 +1095,9 @@ mod tests { "Invalid URL: error parsing TXID", ); } + #[test] - fn output() { + fn output_with_ordinal_index() { TestServer::new_with_args(&["--index-ordinals"]).assert_response_regex( "/output/4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0", StatusCode::OK, @@ -1081,6 +1113,24 @@ mod tests { ); } + #[test] + fn output_without_ordinal_index() { + TestServer::new().assert_response_regex( + "/output/4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0", + StatusCode::OK, + ".*Output 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0.*

Output 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0

+
+
value
5000000000
+
script pubkey
OP_PUSHBYTES_65 04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG
+
+ + + + +", + ); + } + #[test] fn unknown_output_returns_404() { TestServer::new_with_args(&["--index-ordinals"]).assert_response( @@ -1305,6 +1355,29 @@ next.*", ); } + #[test] + fn show_rare_txt_in_header_with_ordinal_index() { + TestServer::new_with_args(&["--index-ordinals"]).assert_response_regex( + "/", + StatusCode::OK, + ".* + Clock + rare.txt +
.*", + ); + } + + #[test] + fn dont_show_rare_txt_in_header_without_ordinal_index() { + TestServer::new().assert_response_regex( + "/", + StatusCode::OK, + ".* + Clock + .*", + ); + } + #[test] fn input() { TestServer::new().assert_response_regex( diff --git a/src/subcommand/server/templates.rs b/src/subcommand/server/templates.rs index 44e058ed2c..f58929120b 100644 --- a/src/subcommand/server/templates.rs +++ b/src/subcommand/server/templates.rs @@ -23,14 +23,20 @@ mod transaction; #[derive(Boilerplate)] pub(crate) struct PageHtml { - content: Box, chain: Chain, + content: Box, + has_ordinal_index: bool, } impl PageHtml { - pub(crate) fn new(content: T, chain: Chain) -> Self { + pub(crate) fn new( + content: T, + chain: Chain, + has_ordinal_index: bool, + ) -> Self { Self { content: Box::new(content), + has_ordinal_index, chain, } } @@ -39,11 +45,11 @@ impl PageHtml { pub(crate) trait Content: Display + 'static { fn title(&self) -> String; - fn page(self, chain: Chain) -> PageHtml + fn page(self, chain: Chain, has_ordinal_index: bool) -> PageHtml where Self: Sized, { - PageHtml::new(self, chain) + PageHtml::new(self, chain, has_ordinal_index) } } @@ -68,7 +74,57 @@ mod tests { } assert_regex_match!( - Foo.page(Chain::Mainnet).to_string(), + Foo.page(Chain::Mainnet, true).to_string(), + " + + + + + + Foo + + + + +
+ +
+
+

Foo

+
+ + +" + ); + } + + #[test] + fn page_no_ordinal_index() { + struct Foo; + + impl Display for Foo { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "

Foo

") + } + } + + impl Content for Foo { + fn title(&self) -> String { + "Foo".to_string() + } + } + + assert_regex_match!( + Foo.page(Chain::Mainnet, false).to_string(), " @@ -84,6 +140,7 @@ mod tests {