diff --git a/docs/src/guides/api.md b/docs/src/guides/api.md
index f5dda88ce4..2424013194 100644
--- a/docs/src/guides/api.md
+++ b/docs/src/guides/api.md
@@ -5127,6 +5127,4 @@ curl -s -H "Accept: application/json" \
## Recursive Endpoints
-See [Recursion](../inscriptions/recursion.md) for an explanation of these.
-
-{{#include ../inscriptions/recursion.md:35:3483}}
+See [Recursion](../inscriptions/recursion.md).
diff --git a/docs/src/inscriptions/recursion.md b/docs/src/inscriptions/recursion.md
index abcad670bf..b7e44a3307 100644
--- a/docs/src/inscriptions/recursion.md
+++ b/docs/src/inscriptions/recursion.md
@@ -3277,6 +3277,128 @@ curl -s \
```
+
+
+ GET
+ /r/parents/<INSCRIPTION_ID>/inscriptions
+
+
+### Description
+
+Details of the first 100 parent inscriptions.
+
+### Example
+
+```bash
+curl -s -H "Accept: application/json" \
+ http://0.0.0.0:80/r/parents/4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019i0/inscriptions
+```
+
+```json
+{
+ "parents": [
+ {
+ "charms": [],
+ "fee": 21730,
+ "height": 775167,
+ "id": "92c409fb749b1005fe9a1482d3a74a8e73936a72644f4979df8184aba473841di0",
+ "number": 4573,
+ "output": "4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019:13",
+ "sat": null,
+ "satpoint": "4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019:13:0",
+ "timestamp": 1675607405
+ },
+ {
+ "charms": [],
+ "fee": 14977,
+ "height": 775167,
+ "id": "c689cbcb8e31858c5e1476d04af4e7e7cedd1fb4fb9cae5bb62036936a08282di0",
+ "number": 4576,
+ "output": "4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019:14",
+ "sat": null,
+ "satpoint": "4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019:14:0",
+ "timestamp": 1675607405
+ },
+ {
+ "charms": [],
+ "fee": 12533,
+ "height": 775167,
+ "id": "982d15f6b3510307ef845f1cb3352b27e2b048616b7c0642367ebc05bbd36d3ai0",
+ "number": 4578,
+ "output": "4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019:12",
+ "sat": null,
+ "satpoint": "4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019:12:0",
+ "timestamp": 1675607405
+ }
+ ...
+ ],
+ "more": true,
+ "page": 0
+}
+```
+
+
+
+
+ GET
+ /r/parents/<INSCRIPTION_ID>/inscriptions/<PAGE>
+
+
+### Description
+
+Details of the set of 100 parent inscriptions on <PAGE>.
+
+### Example
+
+```bash
+curl -s -H "Accept: application/json" \
+ http://0.0.0.0:80/r/parents/4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019i0/inscriptions/1
+```
+
+```json
+{
+ "parents": [
+ {
+ "charms": [],
+ "fee": 65049,
+ "height": 775443,
+ "id": "972994a55c338e8458bfd156642f4aa56bdab54c68658d6b64d932fedef3c81fi0",
+ "number": 10804,
+ "output": "4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019:102",
+ "sat": null,
+ "satpoint": "4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019:102:0",
+ "timestamp": 1675780989
+ },
+ {
+ "charms": [],
+ "fee": 60111,
+ "height": 775443,
+ "id": "dbc21f2d3323df24a378fef3bdbe4e79c4947ce7da54968affcdefa7eda80d21i0",
+ "number": 10805,
+ "output": "4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019:110",
+ "sat": null,
+ "satpoint": "4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019:110:0",
+ "timestamp": 1675780989
+ },
+ {
+ "charms": [],
+ "fee": 49881,
+ "height": 775443,
+ "id": "97870f7cf65992a66d0413a7e6773190e686f185500f78c30f989f2d1f1ba922i0",
+ "number": 10806,
+ "output": "4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019:101",
+ "sat": null,
+ "satpoint": "4a86d375a70a4ecc7ffcd910e05f5e0771ae6a50133543f1bf6b5651adbf0019:101:0",
+ "timestamp": 1675780989
+ }
+ ...
+ ],
+ "more": false,
+ "page": 1
+}
+```
+
+
GET
diff --git a/src/api.rs b/src/api.rs
index 9675f5f3d2..2bcc6a5067 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -84,7 +84,14 @@ pub struct Children {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct ChildInscriptions {
- pub children: Vec,
+ pub children: Vec,
+ pub more: bool,
+ pub page: usize,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct ParentInscriptions {
+ pub parents: Vec,
pub more: bool,
pub page: usize,
}
@@ -132,7 +139,7 @@ pub struct InscriptionRecursive {
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct ChildInscriptionRecursive {
+pub struct RelativeInscriptionRecursive {
pub charms: Vec,
pub fee: u64,
pub height: u32,
diff --git a/src/index.rs b/src/index.rs
index 24737e63c6..641ce9085b 100644
--- a/src/index.rs
+++ b/src/index.rs
@@ -1308,17 +1308,17 @@ impl Index {
pub fn get_parents_by_sequence_number_paginated(
&self,
parent_sequence_numbers: Vec,
+ page_size: usize,
page_index: usize,
) -> Result<(Vec, bool)> {
- const PAGE_SIZE: usize = 100;
let rtx = self.database.begin_read()?;
let sequence_number_to_entry = rtx.open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY)?;
let mut parents = parent_sequence_numbers
.iter()
- .skip(page_index * PAGE_SIZE)
- .take(PAGE_SIZE.saturating_add(1))
+ .skip(page_index * page_size)
+ .take(page_size.saturating_add(1))
.map(|sequence_number| {
sequence_number_to_entry
.get(sequence_number)
@@ -1327,7 +1327,7 @@ impl Index {
})
.collect::>>()?;
- let more_parents = parents.len() > PAGE_SIZE;
+ let more_parents = parents.len() > page_size;
if more_parents {
parents.pop();
diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs
index c44ac491dd..35c97c9fb6 100644
--- a/src/subcommand/server.rs
+++ b/src/subcommand/server.rs
@@ -264,6 +264,14 @@ impl Server {
"/r/parents/{inscription_id}/{page}",
get(r::parents_paginated),
)
+ .route(
+ "/r/parents/{inscription_id}/inscriptions",
+ get(r::parent_inscriptions),
+ )
+ .route(
+ "/r/parents/{inscription_id}/inscriptions/{page}",
+ get(r::parent_inscriptions_paginated),
+ )
.route("/r/sat/{sat_number}", get(r::sat))
.route("/r/sat/{sat_number}/at/{index}", get(r::sat_at_index))
.route("/r/sat/{sat_number}/{page}", get(r::sat_paginated))
@@ -1825,7 +1833,8 @@ impl Server {
.get_inscription_entry(id)?
.ok_or_not_found(|| format!("inscription {id}"))?;
- let (parents, more) = index.get_parents_by_sequence_number_paginated(child.parents, page)?;
+ let (parents, more) =
+ index.get_parents_by_sequence_number_paginated(child.parents, 100, page)?;
let prev_page = page.checked_sub(1);
@@ -6445,6 +6454,129 @@ next
assert_eq!(child_inscriptions_json.page, 1);
}
+ #[test]
+ fn parent_inscriptions_recursive_endpoint() {
+ let server = TestServer::builder().chain(Chain::Regtest).build();
+ server.mine_blocks(1);
+
+ let mut builder = script::Builder::new();
+ for _ in 0..111 {
+ builder = Inscription {
+ content_type: Some("text/plain".into()),
+ body: Some("hello".into()),
+ unrecognized_even_field: false,
+ ..default()
+ }
+ .append_reveal_script_to_builder(builder);
+ }
+
+ let witness = Witness::from_slice(&[builder.into_bytes(), Vec::new()]);
+
+ let parents_txid = server.core.broadcast_tx(TransactionTemplate {
+ inputs: &[(1, 0, 0, witness)],
+ ..default()
+ });
+
+ server.mine_blocks(1);
+
+ let mut builder = script::Builder::new();
+ builder = Inscription {
+ content_type: Some("text/plain".into()),
+ body: Some("hello".into()),
+ parents: (0..111)
+ .map(|i| {
+ InscriptionId {
+ txid: parents_txid,
+ index: i,
+ }
+ .value()
+ })
+ .collect(),
+ unrecognized_even_field: false,
+ ..default()
+ }
+ .append_reveal_script_to_builder(builder);
+
+ let witness = Witness::from_slice(&[builder.into_bytes(), Vec::new()]);
+
+ let child_txid = server.core.broadcast_tx(TransactionTemplate {
+ inputs: &[(2, 0, 0, witness), (2, 1, 0, Default::default())],
+ ..default()
+ });
+
+ let child_inscription_id = InscriptionId {
+ txid: child_txid,
+ index: 0,
+ };
+
+ server.assert_response(
+ format!("/r/parents/{child_inscription_id}/inscriptions"),
+ StatusCode::NOT_FOUND,
+ &format!("inscription {child_inscription_id} not found"),
+ );
+
+ server.mine_blocks(1);
+
+ let first_parent_inscription_id = InscriptionId {
+ txid: parents_txid,
+ index: 0,
+ };
+ let hundredth_parent_inscription_id = InscriptionId {
+ txid: parents_txid,
+ index: 99,
+ };
+ let hundred_first_parent_inscription_id = InscriptionId {
+ txid: parents_txid,
+ index: 100,
+ };
+ let hundred_eleventh_parent_inscription_id = InscriptionId {
+ txid: parents_txid,
+ index: 110,
+ };
+
+ let parent_inscriptions_json = server.get_json::(format!(
+ "/r/parents/{child_inscription_id}/inscriptions"
+ ));
+
+ assert_eq!(parent_inscriptions_json.parents.len(), 100);
+
+ assert_eq!(
+ parent_inscriptions_json.parents[0].id,
+ first_parent_inscription_id
+ );
+ assert_eq!(parent_inscriptions_json.parents[0].number, 0); // parents are #0 and -1 to -110, child is #1
+
+ assert_eq!(
+ parent_inscriptions_json.parents[99].id,
+ hundredth_parent_inscription_id
+ );
+ assert_eq!(parent_inscriptions_json.parents[99].number, -99); // all but 1st parent are cursed
+
+ assert!(parent_inscriptions_json.more);
+ assert_eq!(parent_inscriptions_json.page, 0);
+
+ let parent_inscriptions_json = server.get_json::(format!(
+ "/r/parents/{child_inscription_id}/inscriptions/1"
+ ));
+
+ assert_eq!(parent_inscriptions_json.parents.len(), 11);
+
+ assert_eq!(
+ parent_inscriptions_json.parents[0].id,
+ hundred_first_parent_inscription_id
+ );
+ assert_eq!(parent_inscriptions_json.parents[0].number, -100);
+
+ assert_eq!(
+ parent_inscriptions_json.parents[10].id,
+ hundred_eleventh_parent_inscription_id
+ );
+ assert_eq!(parent_inscriptions_json.parents[10].number, -110);
+
+ assert!(!parent_inscriptions_json.more);
+ assert_eq!(parent_inscriptions_json.page, 1);
+ }
+
#[test]
fn inscriptions_in_block_page() {
let server = TestServer::builder()
diff --git a/src/subcommand/server/r.rs b/src/subcommand/server/r.rs
index 1eb00c217f..82f0e01820 100644
--- a/src/subcommand/server/r.rs
+++ b/src/subcommand/server/r.rs
@@ -175,31 +175,8 @@ pub(super) async fn children_inscriptions_paginated(
let children = ids
.into_iter()
- .map(|inscription_id| {
- let entry = index
- .get_inscription_entry(inscription_id)
- .unwrap()
- .unwrap();
-
- let satpoint = index
- .get_inscription_satpoint_by_id(inscription_id)
- .ok()
- .flatten()
- .unwrap();
-
- api::ChildInscriptionRecursive {
- charms: Charm::charms(entry.charms),
- fee: entry.fee,
- height: entry.height,
- id: inscription_id,
- number: entry.inscription_number,
- output: satpoint.outpoint,
- sat: entry.sat,
- satpoint,
- timestamp: timestamp(entry.timestamp.into()).timestamp(),
- }
- })
- .collect();
+ .map(|inscription_id| get_relative_inscription(&index, inscription_id))
+ .collect::>>()?;
Ok(
Json(api::ChildInscriptions {
@@ -430,6 +407,40 @@ pub(super) async fn parents(
parents_paginated(Extension(index), Path((inscription_id, 0))).await
}
+pub async fn parent_inscriptions(
+ Extension(index): Extension>,
+ Path(inscription_id): Path,
+) -> ServerResult {
+ parent_inscriptions_paginated(Extension(index), Path((inscription_id, 0))).await
+}
+
+pub async fn parent_inscriptions_paginated(
+ Extension(index): Extension>,
+ Path((child, page)): Path<(InscriptionId, usize)>,
+) -> ServerResult {
+ task::block_in_place(|| {
+ let entry = index
+ .get_inscription_entry(child)?
+ .ok_or_not_found(|| format!("inscription {child}"))?;
+
+ let (ids, more) = index.get_parents_by_sequence_number_paginated(entry.parents, 100, page)?;
+
+ let parents = ids
+ .into_iter()
+ .map(|inscription_id| get_relative_inscription(&index, inscription_id))
+ .collect::>>()?;
+
+ Ok(
+ Json(api::ParentInscriptions {
+ parents,
+ more,
+ page,
+ })
+ .into_response(),
+ )
+ })
+}
+
pub(super) async fn parents_paginated(
Extension(index): Extension>,
Path((inscription_id, page)): Path<(InscriptionId, usize)>,
@@ -439,7 +450,7 @@ pub(super) async fn parents_paginated(
.get_inscription_entry(inscription_id)?
.ok_or_not_found(|| format!("inscription {inscription_id}"))?;
- let (ids, more) = index.get_parents_by_sequence_number_paginated(child.parents, page)?;
+ let (ids, more) = index.get_parents_by_sequence_number_paginated(child.parents, 100, page)?;
let page_index =
u32::try_from(page).map_err(|_| anyhow!("page index {} out of range", page))?;
@@ -521,6 +532,31 @@ pub(super) async fn sat_at_index_content(
.await
}
+fn get_relative_inscription(
+ index: &Index,
+ id: InscriptionId,
+) -> ServerResult {
+ let entry = index
+ .get_inscription_entry(id)?
+ .ok_or_not_found(|| format!("inscription {id}"))?;
+
+ let satpoint = index
+ .get_inscription_satpoint_by_id(id)?
+ .ok_or_not_found(|| format!("satpoint for inscription {id}"))?;
+
+ Ok(api::RelativeInscriptionRecursive {
+ 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(),
+ })
+}
+
pub(super) async fn tx(
Extension(index): Extension>,
Path(txid): Path,