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,