diff --git a/src/index.rs b/src/index.rs index 302cbad4d7..4171ee34bb 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1436,6 +1436,37 @@ impl Index { .transpose() } + pub fn get_children_at_index_by_sequence_number( + &self, + sequence_number: u32, + index: isize, + ) -> Result> { + let rtx = self.database.begin_read()?; + + let sequence_number_to_children = rtx.open_multimap_table(SEQUENCE_NUMBER_TO_CHILDREN)?; + let sequence_number_to_inscription_entry = + rtx.open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY)?; + if index < 0 { + sequence_number_to_children + .get(sequence_number)? + .nth_back((index + 1).abs_diff(0)) + } else { + sequence_number_to_children + .get(sequence_number)? + .nth(index.abs_diff(0)) + } + .map(|result| { + result + .and_then(|sequence_number| { + let sequence_number = sequence_number.value(); + sequence_number_to_inscription_entry + .get(sequence_number) + .map(|entry| InscriptionEntry::load(entry.unwrap().value()).id) + }) + .map_err(|err| anyhow!(err.to_string())) + }) + .transpose() + } #[cfg(test)] pub(crate) fn get_inscription_id_by_inscription_number( &self, diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 38e501f4ed..3d63e5f02c 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -249,6 +249,10 @@ impl Server { "/r/children/:inscription_id/inscriptions", get(Self::child_inscriptions_recursive), ) + .route( + "/r/children/:inscription_id/at/:index", + get(Self::child_inscription_at_index), + ) .route( "/r/children/:inscription_id/inscriptions/:page", get(Self::child_inscriptions_recursive_paginated), @@ -2043,6 +2047,41 @@ impl Server { }) } + async fn child_inscription_at_index( + Extension(index): Extension>, + Path((parent, inscription_index)): Path<(InscriptionId, isize)>, + ) -> ServerResult { + task::block_in_place(|| { + let entry = index + .get_inscription_entry(parent)? + .ok_or_not_found(|| format!("inscription {parent}"))?; + + let id = index + .get_children_at_index_by_sequence_number(entry.sequence_number, inscription_index)? + .ok_or_not_found(|| format!("child {inscription_index}"))?; + + let satpoint = index + .get_inscription_satpoint_by_id(id) + .ok() + .flatten() + .unwrap(); + + Ok( + Json(api::ChildInscriptionRecursive { + charms: Charm::charms(entry.charms), + fee: entry.fee, + height: entry.height, + id, + number: entry.inscription_number, + output: satpoint.outpoint, + sat: entry.sat, + satpoint, + timestamp: timestamp(entry.timestamp.into()).timestamp(), + }) + .into_response(), + ) + }) + } async fn inscriptions( Extension(server_config): Extension>, Extension(index): Extension>, @@ -6497,6 +6536,66 @@ next assert_eq!(children_json.page, 1); } + #[test] + fn children_recursive_indexed_endpoint() { + let server = TestServer::builder().chain(Chain::Regtest).build(); + server.mine_blocks(1); + + let parent_txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + ..default() + }); + + let parent_inscription_id = InscriptionId { + txid: parent_txid, + index: 0, + }; + + server.assert_response( + format!("/r/children/{parent_inscription_id}/at/-1"), + StatusCode::NOT_FOUND, + &format!("inscription {parent_inscription_id} not found"), + ); + + server.mine_blocks(1); + + let mut builder = script::Builder::new(); + for _ in 0..70 { + builder = Inscription { + content_type: Some("text/plain".into()), + body: Some("hello".into()), + parents: vec![parent_inscription_id.value()], + unrecognized_even_field: false, + ..default() + } + .append_reveal_script_to_builder(builder); + } + + let witness = Witness::from_slice(&[builder.into_bytes(), Vec::new()]); + + let txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(2, 0, 0, witness), (2, 1, 0, Default::default())], + ..default() + }); + + server.mine_blocks(1); + let latest_child_inscription_id = InscriptionId { txid, index: 69 }; + let child_json = server.get_json::(format!( + "/r/children/{parent_inscription_id}/at/-1" + )); + assert_eq!(child_json.id, latest_child_inscription_id); + let zeroth_child_inscription_id = InscriptionId { txid, index: 0 }; + let zeroth_child = server.get_json::(format!( + "/r/children/{parent_inscription_id}/at/0" + )); + assert_eq!(zeroth_child.id, zeroth_child_inscription_id); + let first_child_inscription_id = InscriptionId { txid, index: 1 }; + let first_child = server.get_json::(format!( + "/r/children/{parent_inscription_id}/at/1" + )); + assert_eq!(first_child.id, first_child_inscription_id); + } + #[test] fn parents_recursive_endpoint() { let server = TestServer::builder().chain(Chain::Regtest).build();