diff --git a/docs/src/inscriptions/recursion.md b/docs/src/inscriptions/recursion.md index 77b2ef2fb0..5ce7e8901a 100644 --- a/docs/src/inscriptions/recursion.md +++ b/docs/src/inscriptions/recursion.md @@ -43,13 +43,14 @@ The recursive endpoints are: - `/r/children//inscriptions`: details of the first 100 child inscriptions. - `/r/children//inscriptions/`: details of the set of 100 child inscriptions on ``. - `/r/inscription/`: information about an inscription +- `/r/inscriptions//`: information about inscriptions in a block,``. Paginated,``. - `/r/metadata/`: JSON string containing the hex-encoded CBOR metadata. - `/r/parents/`: the first 100 parent inscription ids. - `/r/parents//`: the set of 100 parent inscription ids on ``. - `/r/sat/`: the first 100 inscription ids on a sat. - `/r/sat//`: the set of 100 inscription ids on ``. - `/r/sat//at/`: the inscription id at `` of all inscriptions on a sat. `` may be a negative number to index from the back. `0` being the first and `-1` being the most recent for example. - +- `/r/txs/:query`: transaction input and outputs in a block. Note: `` only allows the actual number of a sat no other sat notations like degree, percentile or decimal. We may expand to allow those in the future. @@ -196,6 +197,22 @@ percentile in sats/vB. } ``` +- `/r/inscriptions/800000/0` + +```json +{ + "ids": [ + "965f866bf8623bbf956c1b2aeec1efc1ad162fd428ab7fb89f128a0754ebbc32i0", + "1d11c135b165b5d9fe07f1b015ed21536aa1c235ff75cd7da5e73746d464cc97i0", + ... + "0981a67c05405eb30810d2e1fcc3b383ede360a7cfed7b901e663fa2d902e2dbi0", + "366469b47dd78d49c9c62cffc0d3ff999b32b7e6139b6b3b18ed3ae28769fbe9i0" + ], + "more": true, + "page_index": 0 +} +``` + - `/r/metadata/35b66389b44535861c44b2b18ed602997ee11db9a30d384ae89630c9fc6f011fi3`: ```json @@ -221,3 +238,69 @@ percentile in sats/vB. "id":"17541f6adf6eb160d52bc6eb0a3546c7c1d2adfe607b1a3cddc72cc0619526adi0" } ``` +`/r/txs/:query`: +```json +{ +"best_height": 828677, + "hash": "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054", + "height": 800000, + "target": "0000000000000000000538940000000000000000000000000000000000000000", + "parsed_transactions": [ + { + "txid": "b75ca3106ed100521aa50e3ec267a06431c6319538898b25e1b757a5736f5fb4", + "input": [ + { + "previous_output": "0000000000000000000000000000000000000000000000000000000000000000:4294967295", + "sequence": 4294967295 + } + ], + "output": [ + { + "value": 638687680, + "script_pubkey": "a914c3f8f898ae5cab4f4c1d597ecb0f3a81a9b146c387" + }, + { + "value": 0, + "script_pubkey": "6a24aa21a9ed9fbe517a588ccaca585a868f3cf19cb6897e3c26f3351361fb28ac8509e69a7e" + } + ] + }, + { + "txid": "d41f5de48325e79070ccd3a23005f7a3b405f3ce1faa4df09f6d71770497e9d5", + "input": [ + { + "previous_output": "a992dbddbeb7382e3defc6914f970ea769ef813e69a923afa336976f2cbf0465:1", + "sequence": 4294967295 + } + ], + "output": [ + { + "value": 143332, + "script_pubkey": "51202d618c1f73d5133fdc97d545bfbf55b4cba2ab2a9d41e4596b1df6b8ea9d9348" + }, + { + "value": 291851, + "script_pubkey": "001464dbbc84f12f32699ca5010faa618d6a25559b6f" + } + ] + }, + ... + { + "txid": "b2088e443cf4b28ade8873cc6b3f6a67557f104ec4dc5b5e293c12973ab8b6b8", + "input": [ + { + "previous_output": "856bce86ad1f0039ccd9eabe49e58e640e3f05952bd0a26d6687710d3de9a5ad:0", + "sequence": 0 + } + ], + "output": [ + { + "value": 499441, + "script_pubkey": "00149549a8a78144db492bedbbe57b99343d034bfafb" + } + ] + } + ] +} +``` + diff --git a/src/api.rs b/src/api.rs index 45200115e6..084c31278b 100644 --- a/src/api.rs +++ b/src/api.rs @@ -42,6 +42,55 @@ impl Block { } } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockTxs { + pub best_height: u32, + pub hash: BlockHash, + pub height: u32, + pub target: BlockHash, + pub parsed_transactions: Vec, +} +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct ParsedTransaction { + pub txid: bitcoin::Txid, + input: Vec, + output: Vec, +} +#[derive(Debug, PartialEq, Serialize, Deserialize)] +struct ParsedInput { + previous_output: bitcoin::OutPoint, + sequence: Sequence, +} + +impl BlockTxs { + pub(crate) fn new( + block: bitcoin::Block, + height: Height, + best_height: Height, + ) -> Self { + let parsed_transactions: Vec = block.txdata.iter().map(|tx| { + let pruned_inputs: Vec = tx.input.iter().map(|input| { + ParsedInput { + previous_output: input.previous_output, + sequence: input.sequence, + } + }).collect(); + ParsedTransaction { + txid: tx.txid(), + input: pruned_inputs, + output: tx.output.clone(), // Clone outputs + } + }).collect(); + Self { + hash: block.header.block_hash(), + target: target_as_block_hash(block.header.target()), + height: height.0, + best_height: best_height.0, + parsed_transactions, + } + } +} + #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct BlockInfo { pub average_fee: u64, diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 70727cca0c..1112c130ee 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -236,6 +236,7 @@ impl Server { .route("/r/blockheight", get(Self::block_height)) .route("/r/blocktime", get(Self::block_time)) .route("/r/blockinfo/:query", get(Self::block_info)) + .route("/r/inscriptions/:height/:page", get(Self::inscriptions_in_block_paginated_json)) .route( "/r/inscription/:inscription_id", get(Self::inscription_recursive), @@ -268,6 +269,8 @@ impl Server { "/r/sat/:sat_number/at/:index", get(Self::sat_inscription_at_index), ) + .route("/r/tx/:txid", get(Self::transaction_json)) + .route("/r/txs/:query", get(Self::transactions_in_block_json)) .route("/range/:start/:end", get(Self::range)) .route("/rare.txt", get(Self::rare_txt)) .route("/rune/:rune", get(Self::rune)) @@ -970,6 +973,31 @@ impl Server { }) } + async fn transaction_json( + Extension(server_config): Extension>, + Extension(index): Extension>, + Path(txid): Path, + ) -> ServerResult { + task::block_in_place(|| { + let transaction = index + .get_transaction(txid)? + .ok_or_not_found(|| format!("transaction {txid}"))?; + + let inscription_count = index.inscription_count(txid)?; + + Ok(Json(api::Transaction { + chain: server_config.chain, + etching: index.get_etching(txid)?, + inscription_count, + transaction, + txid, + }) + .into_response() + ) + }) + } + + async fn decode( Extension(index): Extension>, Path(txid): Path, @@ -2001,6 +2029,76 @@ impl Server { }) } + async fn inscriptions_in_block_paginated_json( + Extension(index): Extension>, + Path((block_height, page_index)): Path<(u32, u32)>, + ) -> ServerResult { + task::block_in_place(|| { + let page_size = 100; + + let page_index_usize = usize::try_from(page_index).unwrap_or(usize::MAX); + let page_size_usize = usize::try_from(page_size).unwrap_or(usize::MAX); + + let mut inscriptions = index + .get_inscriptions_in_block(block_height)? + .into_iter() + .skip(page_index_usize.saturating_mul(page_size_usize)) + .take(page_size_usize.saturating_add(1)) + .collect::>(); + + let more = inscriptions.len() > page_size_usize; + + if more { + inscriptions.pop(); + } + + Ok(Json(api::Inscriptions { + ids: inscriptions, + page_index, + more, + }) + .into_response()) + + }) + + } + + async fn transactions_in_block_json( + Extension(index): Extension>, + Path(DeserializeFromStr(query)): Path>, + ) -> ServerResult { + task::block_in_place(|| { + let (block, height) = match query { + query::Block::Height(height) => { + let block = index + .get_block_by_height(height)? + .ok_or_not_found(|| format!("block {height}"))?; + + (block, height) + } + query::Block::Hash(hash) => { + let info = index + .block_header_info(hash)? + .ok_or_not_found(|| format!("block {hash}"))?; + + let block = index + .get_block_by_hash(hash)? + .ok_or_not_found(|| format!("block {hash}"))?; + + (block, u32::try_from(info.height).unwrap()) + } + }; + Ok(Json(api::BlockTxs::new( + block, + Height(height), + Self::index_height(&index)?, + )) + .into_response()) + }) + + + } + async fn parents( Extension(server_config): Extension>, Extension(index): Extension>,