diff --git a/src/index.rs b/src/index.rs index f8cc7b6310..817003986b 100644 --- a/src/index.rs +++ b/src/index.rs @@ -51,6 +51,7 @@ define_table! { HEIGHT_TO_BLOCK_HASH, u64, &BlockHashValue } define_table! { HEIGHT_TO_LAST_SEQUENCE_NUMBER, u64, u64 } define_table! { INSCRIPTION_ID_TO_INSCRIPTION_ENTRY, &InscriptionIdValue, InscriptionEntryValue } define_table! { INSCRIPTION_ID_TO_SATPOINT, &InscriptionIdValue, &SatPointValue } +define_table! { INSCRIPTION_NUMBER_TO_INSCRIPTION_ID, i64, &InscriptionIdValue } define_table! { OUTPOINT_TO_SAT_RANGES, &OutPointValue, &[u8] } define_table! { OUTPOINT_TO_VALUE, &OutPointValue, u64} define_table! { SAT_TO_SATPOINT, u64, &SatPointValue } @@ -242,6 +243,7 @@ impl Index { tx.open_table(HEIGHT_TO_LAST_SEQUENCE_NUMBER)?; tx.open_table(INSCRIPTION_ID_TO_INSCRIPTION_ENTRY)?; tx.open_table(INSCRIPTION_ID_TO_SATPOINT)?; + tx.open_table(INSCRIPTION_NUMBER_TO_INSCRIPTION_ID)?; tx.open_table(OUTPOINT_TO_VALUE)?; tx.open_table(SAT_TO_SATPOINT)?; tx.open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ID)?; @@ -702,6 +704,20 @@ impl Index { ) } + pub(crate) fn get_inscription_id_by_inscription_number( + &self, + n: i64, + ) -> Result> { + Ok( + self + .database + .begin_read()? + .open_table(INSCRIPTION_NUMBER_TO_INSCRIPTION_ID)? + .get(&n)? + .map(|id| Entry::load(*id.value())), + ) + } + pub(crate) fn get_inscription_satpoint_by_id( &self, inscription_id: InscriptionId, @@ -3161,6 +3177,15 @@ mod tests { 0 ); + assert_eq!( + context + .index + .get_inscription_id_by_inscription_number(-3) + .unwrap() + .unwrap(), + fourth + ); + assert_eq!( context .index diff --git a/src/index/updater.rs b/src/index/updater.rs index 0a4a918995..2dd342a077 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -382,6 +382,8 @@ impl<'index> Updater<'_> { let mut inscription_id_to_inscription_entry = wtx.open_table(INSCRIPTION_ID_TO_INSCRIPTION_ENTRY)?; let mut inscription_id_to_satpoint = wtx.open_table(INSCRIPTION_ID_TO_SATPOINT)?; + let mut inscription_number_to_inscription_id = + wtx.open_table(INSCRIPTION_NUMBER_TO_INSCRIPTION_ID)?; let mut sat_to_inscription_id = wtx.open_multimap_table(SAT_TO_INSCRIPTION_ID)?; let mut inscription_id_to_children = wtx.open_multimap_table(INSCRIPTION_ID_TO_CHILDREN)?; let mut satpoint_to_inscription_id = wtx.open_multimap_table(SATPOINT_TO_INSCRIPTION_ID)?; @@ -416,6 +418,7 @@ impl<'index> Updater<'_> { value_receiver, &mut inscription_id_to_inscription_entry, lost_sats, + &mut inscription_number_to_inscription_id, cursed_inscription_count, blessed_inscription_count, &mut sequence_number_to_inscription_id, diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 2bd02387a8..6c9f4f7f93 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -32,6 +32,7 @@ pub(super) struct InscriptionUpdater<'a, 'db, 'tx> { pub(super) cursed_inscription_count: u64, pub(super) blessed_inscription_count: u64, pub(super) next_sequence_number: u64, + inscription_number_to_id: &'a mut Table<'db, 'tx, i64, &'static InscriptionIdValue>, sequence_number_to_id: &'a mut Table<'db, 'tx, u64, &'static InscriptionIdValue>, outpoint_to_value: &'a mut Table<'db, 'tx, &'static OutPointValue, u64>, reward: u64, @@ -56,6 +57,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { value_receiver: &'a mut Receiver, id_to_entry: &'a mut Table<'db, 'tx, &'static InscriptionIdValue, InscriptionEntryValue>, lost_sats: u64, + inscription_number_to_id: &'a mut Table<'db, 'tx, i64, &'static InscriptionIdValue>, cursed_inscription_count: u64, blessed_inscription_count: u64, sequence_number_to_id: &'a mut Table<'db, 'tx, u64, &'static InscriptionIdValue>, @@ -90,6 +92,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { blessed_inscription_count, next_sequence_number, sequence_number_to_id, + inscription_number_to_id, outpoint_to_value, reward: Height(height).subsidy(), sat_to_inscription_id, @@ -422,6 +425,10 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { number }; + self + .inscription_number_to_id + .insert(inscription_number, &inscription_id)?; + let sequence_number = self.next_sequence_number; self.next_sequence_number += 1; diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index df85e07657..7b837624bc 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -47,6 +47,23 @@ pub struct ServerConfig { pub is_json_api_enabled: bool, } +enum InscriptionQuery { + Id(InscriptionId), + Number(i64), +} + +impl FromStr for InscriptionQuery { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(if s.contains('i') { + InscriptionQuery::Id(s.parse()?) + } else { + InscriptionQuery::Number(s.parse()?) + }) + } +} + enum BlockQuery { Height(u64), Hash(BlockHash), @@ -175,7 +192,7 @@ impl Server { .route("/favicon.ico", get(Self::favicon)) .route("/feed.xml", get(Self::feed)) .route("/input/:block/:transaction/:input", get(Self::input)) - .route("/inscription/:inscription_id", get(Self::inscription)) + .route("/inscription/:inscription_query", get(Self::inscription)) .route("/inscriptions", get(Self::inscriptions)) .route( "/inscriptions/block/:height", @@ -975,9 +992,16 @@ impl Server { async fn inscription( Extension(page_config): Extension>, Extension(index): Extension>, - Path(inscription_id): Path, + Path(DeserializeFromStr(query)): Path>, accept_json: AcceptJson, ) -> ServerResult { + let inscription_id = match query { + InscriptionQuery::Id(id) => id, + InscriptionQuery::Number(inscription_number) => index + .get_inscription_id_by_inscription_number(inscription_number)? + .ok_or_not_found(|| format!("{inscription_number}"))?, + }; + let entry = index .get_inscription_entry(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; @@ -3025,4 +3049,56 @@ mod tests { [inscription_id], ); } + + #[test] + fn inscription_number_endpoint() { + let server = TestServer::new_with_regtest(); + server.mine_blocks(2); + + let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[ + (1, 0, 0, inscription("text/plain", "hello").to_witness()), + (2, 0, 0, inscription("text/plain", "cursed").to_witness()), + ], + outputs: 2, + ..Default::default() + }); + + let inscription_id = InscriptionId { txid, index: 0 }; + let cursed_inscription_id = InscriptionId { txid, index: 1 }; + + server.mine_blocks(1); + + server.assert_response_regex( + format!("/inscription/{inscription_id}"), + StatusCode::OK, + format!( + ".*

Inscription 0

.* +
+
id
+
{inscription_id}
.*" + ), + ); + server.assert_response_regex( + "/inscription/0", + StatusCode::OK, + format!( + ".*

Inscription 0

.* +
+
id
+
{inscription_id}
.*" + ), + ); + + server.assert_response_regex( + "/inscription/-1", + StatusCode::OK, + format!( + ".*

Inscription -1 \\(unstable\\)

.* +
+
id
+
{cursed_inscription_id}
.*" + ), + ) + } }