diff --git a/api/src/foreign.rs b/api/src/foreign.rs index f45b9ffdc..88d147192 100644 --- a/api/src/foreign.rs +++ b/api/src/foreign.rs @@ -23,12 +23,12 @@ use crate::handlers::pool_api::PoolHandler; use crate::handlers::transactions_api::TxHashSetHandler; use crate::handlers::version_api::VersionHandler; use crate::pool::{self, BlockChain, PoolAdapter, PoolEntry}; -use crate::rest::*; use crate::types::{ BlockHeaderPrintable, BlockPrintable, LocatedTxKernel, OutputListing, OutputPrintable, Tip, Version, }; use crate::util::RwLock; +use crate::{rest::*, BlockListing}; use std::sync::Weak; /// Main interface into all node API functions. @@ -139,6 +139,36 @@ where block_handler.get_block(&hash, include_proof, include_merkle_proof) } + /// Returns a [`BlockListing`](types/struct.BlockListing.html) of available blocks + /// between `min_height` and `max_height` + /// The method will query the database for blocks starting at the block height `min_height` + /// and continue until `max_height`, skipping any blocks that aren't available. + /// + /// # Arguments + /// * `start_height` - starting height to lookup. + /// * `end_height` - ending height to to lookup. + /// * 'max` - The max number of blocks to return. + /// Note this is overriden with BLOCK_TRANSFER_LIMIT if BLOCK_TRANSFER_LIMIT is exceeded + /// + /// # Returns + /// * Result Containing: + /// * A [`BlockListing`](types/struct.BlockListing.html) + /// * or [`Error`](struct.Error.html) if an error is encountered. + /// + + pub fn get_blocks( + &self, + start_height: u64, + end_height: u64, + max: u64, + include_proof: Option, + ) -> Result { + let block_handler = BlockHandler { + chain: self.chain.clone(), + }; + block_handler.get_blocks(start_height, end_height, max, include_proof) + } + /// Returns the node version and block header version (used by grin-wallet). /// /// # Returns diff --git a/api/src/foreign_rpc.rs b/api/src/foreign_rpc.rs index 306293b87..7415e3541 100644 --- a/api/src/foreign_rpc.rs +++ b/api/src/foreign_rpc.rs @@ -21,8 +21,8 @@ use crate::pool::PoolEntry; use crate::pool::{BlockChain, PoolAdapter}; use crate::rest::Error; use crate::types::{ - BlockHeaderPrintable, BlockPrintable, LocatedTxKernel, OutputListing, OutputPrintable, Tip, - Version, + BlockHeaderPrintable, BlockListing, BlockPrintable, LocatedTxKernel, OutputListing, + OutputPrintable, Tip, Version, }; use crate::util; @@ -246,6 +246,251 @@ pub trait ForeignRpc: Sync + Send { commit: Option, ) -> Result; + /** + Networked version of [Foreign::get_blocks](struct.Foreign.html#method.get_blocks). + + # Json rpc example + + ``` + # grin_api::doctest_helper_json_rpc_foreign_assert_response!( + # r#" + { + "jsonrpc": "2.0", + "method": "get_blocks", + "params": [2299309, 2300309, 2, false], + "id": 1 + } + # "# + # , + # r#" + { + "id": 1, + "jsonrpc": "2.0", + "result": { + "Ok": { + "blocks": [ + { + "header": { + "cuckoo_solution": [ + 20354215, + 100524565, + 169529296, + 259818619, + 261952555, + 265003136, + 290685286, + 307792709, + 329993483, + 331550733, + 478902211, + 707186317, + 717277083, + 742312701, + 763869950, + 785680094, + 791217416, + 1156641404, + 1244452354, + 1277970471, + 1405106926, + 1663783361, + 1701259732, + 1795507572, + 1845900835, + 2060172013, + 2067055232, + 2169213199, + 2191128830, + 2253855427, + 2626425322, + 2678973678, + 2815586448, + 2921010487, + 3042894274, + 3103031603, + 3492595971, + 3603041347, + 3853538391, + 3974438280, + 4199558832, + 4262968379 + ], + "edge_bits": 32, + "hash": "0004331bb122685f12644e40b163e4557951b2b835ad2493502750ea787af7cc", + "height": 2299309, + "kernel_mmr_size": 8568165, + "kernel_root": "6b4adb9ee193ad043910b5a8c1bac0864ab99f57845101a3b422031bcf5c2ce1", + "nonce": 4185528505858938389, + "output_mmr_size": 13524183, + "output_root": "b642891741b56adaf7762813490d161377d0fbf7b47170d235beef33c25a4d77", + "prev_root": "a0ba3206b6a8089ef05690d40767c41cc0514eaa5031ebce1960a7cc2edcc211", + "previous": "000207548609a9007eacd7dfcdc8006252d6b1ad70864ea8ddebe4ca9e82bd74", + "range_proof_root": "d8cefda00f325fd9a1223454f23276b73d8a1d7c72ec74cdfb9bdf5c77a04dee", + "secondary_scaling": 0, + "timestamp": "2023-06-05T20:18:45+00:00", + "total_difficulty": 2072532663425232, + "total_kernel_offset": "b0a0c21326532b4a91c18d2355aedca4d8ed68b77db9882feb85da8120b4f533", + "version": 5 + }, + "inputs": [ + "092b140b1500812ac58ef68c17a2bbf2ec3531bcf0ce4dc32bbf8a29351d1784d7", + "083b72230921abeacd637dae8505233ab035c20dff1bfdab5ff5bb41b2f5238458" + ], + "kernels": [ + { + "excess": "08ab720dc374f099e6726e2dceada508a0331bb1f13b8a4e56afde83ff42f7a351", + "excess_sig": "6858120e9758d7587e27fd5dc9c26117a2ce0d5a7d871ce805e03eb494bfa1f86a27991865b3ab709064c43692433fd58f008c3bba2c88ad5f95a0c8ff3cf11f", + "features": "Plain", + "fee": 23500000, + "fee_shift": 0, + "lock_height": 0 + }, + { + "excess": "08d0a44b22952b03b29e3d88391102c281dcab4763def22cab65ed45e35b9078e8", + "excess_sig": "32f91d5671e334a87843a8b02c550c9e0fbdfe507ee62417cc123b5078d7884701a42e257357a1bed9dc4a8e07540b1629e9fa95a05c44adb5cb001c8fb777ee", + "features": "Coinbase", + "fee": 0, + "fee_shift": 0, + "lock_height": 0 + } + ], + "outputs": [ + { + "block_height": 2299309, + "commit": "0857c94df51dd226fa0c5920aae6d73d069603f973b2e06551698c6d39fdc2c192", + "merkle_proof": null, + "mmr_index": 13524176, + "output_type": "Coinbase", + "proof": null, + "proof_hash": "0937291a8a3c81cea4421fa0d0b291aacb5d46065cfd93747a15f58d99d781b6", + "spent": false + }, + { + "block_height": 2299309, + "commit": "08d4681b904695edee6e183cd40564ea0f5589b35d4d386da2eb980a6a92b1b307", + "merkle_proof": null, + "mmr_index": 0, + "output_type": "Transaction", + "proof": null, + "proof_hash": "41694ab6dcd9b1664ca28e79c3302144b99a4c1cb45d13c8728604c1d26e37bf", + "spent": true + }, + { + "block_height": 2299309, + "commit": "08255a260a65fc87cfd924780d896eaadb42468b0fe3ba6adeace378793b5d8172", + "merkle_proof": null, + "mmr_index": 13524182, + "output_type": "Transaction", + "proof": null, + "proof_hash": "58c77a5716ec4806dbddac64a83d6e4351b6eeffca391be1b11ec74aac0514dc", + "spent": false + } + ] + }, + { + "header": { + "cuckoo_solution": [ + 898450, + 353949138, + 440882514, + 500154010, + 555236503, + 615120852, + 740100750, + 754668484, + 1056458121, + 1071299788, + 1130460099, + 1414281857, + 1444894533, + 1481124421, + 1551877341, + 1666859923, + 1682642953, + 1837365586, + 1845508478, + 1872787697, + 2040619654, + 2078971700, + 2104947318, + 2206501084, + 2233951742, + 2360961460, + 2378988856, + 2402500295, + 2438384422, + 2532261092, + 2879360933, + 3011869457, + 3023365279, + 3412207020, + 3509607650, + 3793770861, + 3850043972, + 3873426868, + 3965579806, + 4007877324, + 4090157476, + 4141650723 + ], + "edge_bits": 32, + "hash": "00006871e1fb8e7dddcc46343d7fbba14d08946c67b4568f3c2e98ec8c554ae9", + "height": 2299310, + "kernel_mmr_size": 8568166, + "kernel_root": "87184dc2f9efa6467ce797191c5d3ef086403d0103ba0b5adc6a71ed203a053c", + "nonce": 13726392224838330049, + "output_mmr_size": 13524184, + "output_root": "9570fbccef29609c5d3c68b07771bf4e7e80d0b139d9bd0215d1e9d1aaaed813", + "prev_root": "df1c67366b9cdd8deea570534a00a320748899e146288be067c0f402038e6aa0", + "previous": "0004331bb122685f12644e40b163e4557951b2b835ad2493502750ea787af7cc", + "range_proof_root": "987d7aff01e201269d4c6b00e885b9ed9c10f47205edd7727e3490aab953ca80", + "secondary_scaling": 0, + "timestamp": "2023-06-05T20:19:27+00:00", + "total_difficulty": 2072532872584027, + "total_kernel_offset": "b0a0c21326532b4a91c18d2355aedca4d8ed68b77db9882feb85da8120b4f533", + "version": 5 + }, + "inputs": [], + "kernels": [ + { + "excess": "08224a7946a75071b127af45496ddd3fc438db325cc35c3e4b0fdf23ed27703dd8", + "excess_sig": "d8c81bd8130c30016e38655a32b4c7a1f8fffda34a736dd8cdbcad05d28d09e3708d1f01e21276747eb03f28b9f5a834cb0ef8532330183df2b10d47ae7e68c6", + "features": "Coinbase", + "fee": 0, + "fee_shift": 0, + "lock_height": 0 + } + ], + "outputs": [ + { + "block_height": 2299310, + "commit": "09997d3c1eff72b7efa7bfb52032d713f5907755838c01a6e178a87a0ac170a279", + "merkle_proof": null, + "mmr_index": 13524184, + "output_type": "Coinbase", + "proof": null, + "proof_hash": "6c2c10af5c4b6d2bcf71084c2bd9685ae91427f03a8b78736ab27d6c5bc7e4db", + "spent": false + } + ] + } + ], + "last_retrieved_height": 2299310 + } + } + } + # "# + # ); + ``` + */ + fn get_blocks( + &self, + start_height: u64, + end_height: u64, + max: u64, + include_proof: Option, + ) -> Result; + /** Networked version of [Foreign::get_version](struct.Foreign.html#method.get_version). @@ -760,6 +1005,7 @@ where } Foreign::get_header(self, height, parsed_hash, commit) } + fn get_block( &self, height: Option, @@ -775,6 +1021,16 @@ where Foreign::get_block(self, height, parsed_hash, commit) } + fn get_blocks( + &self, + start_height: u64, + end_height: u64, + max: u64, + include_proof: Option, + ) -> Result { + Foreign::get_blocks(self, start_height, end_height, max, include_proof) + } + fn get_version(&self) -> Result { Foreign::get_version(self) } diff --git a/api/src/handlers/blocks_api.rs b/api/src/handlers/blocks_api.rs index ca46db16e..71defddb5 100644 --- a/api/src/handlers/blocks_api.rs +++ b/api/src/handlers/blocks_api.rs @@ -24,6 +24,8 @@ use hyper::{Body, Request, StatusCode}; use regex::Regex; use std::sync::Weak; +pub const BLOCK_TRANSFER_LIMIT: u64 = 1000; + /// Gets block headers given either a hash or height or an output commit. /// GET /v1/headers/ /// GET /v1/headers/ @@ -138,6 +140,69 @@ impl BlockHandler { .map_err(|_| Error::Internal("chain error".to_owned())) } + pub fn get_blocks( + &self, + mut start_height: u64, + end_height: u64, + mut max: u64, + include_proof: Option, + ) -> Result { + // set a limit here + if max > BLOCK_TRANSFER_LIMIT { + max = BLOCK_TRANSFER_LIMIT; + } + let tail_height = self.get_tail_height()?; + + if start_height < tail_height { + start_height = tail_height; + } + + let mut result_set = BlockListing { + last_retrieved_height: 0, + blocks: vec![], + }; + let mut block_count = 0; + for h in start_height..=end_height { + result_set.last_retrieved_height = h; + + let hash = match self.parse_inputs(Some(h), None, None) { + Err(e) => { + if let Error::NotFound = e { + continue; + } else { + return Err(e); + } + } + Ok(h) => h, + }; + + let block_res = self.get_block(&hash, include_proof == Some(true), false); + + match block_res { + Err(e) => { + if let Error::NotFound = e { + continue; + } else { + return Err(e); + } + } + Ok(b) => { + block_count += 1; + result_set.blocks.push(b); + } + } + if block_count == max { + break; + } + } + Ok(result_set) + } + + pub fn get_tail_height(&self) -> Result { + let chain = w(&self.chain)?; + Ok(chain.get_tail().map_err(|_| Error::NotFound)?.height) + } + fn get_compact_block(&self, h: &Hash) -> Result { let chain = w(&self.chain)?; let block = chain.get_block(h).map_err(|_| Error::NotFound)?; diff --git a/api/src/types.rs b/api/src/types.rs index 13d7baa22..6b0ee0e55 100644 --- a/api/src/types.rs +++ b/api/src/types.rs @@ -722,6 +722,15 @@ pub struct OutputListing { pub outputs: Vec, } +// For traversing a set of all available blocks +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BlockListing { + /// The last height retrieved + pub last_retrieved_height: u64, + /// A printable version of the retrieved Blocks + pub blocks: Vec, +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct LocatedTxKernel { pub tx_kernel: TxKernel, diff --git a/chain/src/chain.rs b/chain/src/chain.rs index 25b24dc64..6d2382cc0 100644 --- a/chain/src/chain.rs +++ b/chain/src/chain.rs @@ -1445,6 +1445,13 @@ impl Chain { .map_err(|e| Error::StoreErr(e, "chain get block".to_owned())) } + /// Gets the earliest stored block (tail) + pub fn get_tail(&self) -> Result { + self.store + .tail() + .map_err(|e| Error::StoreErr(e, "chain get tail".to_owned())) + } + /// Gets a block header by hash pub fn get_block_header(&self, h: &Hash) -> Result { self.store