From 94bcc1172008caf4983ec6a05d48fcf900ca2384 Mon Sep 17 00:00:00 2001 From: Alex Tsokurov Date: Tue, 4 Mar 2025 13:04:28 +0100 Subject: [PATCH 1/2] getblockmeta: allow request by height --- client/src/client.rs | 4 +- client/src/rpc.rs | 124 +++++++++++++++++++++++++-------------- protocol/src/validate.rs | 2 +- 3 files changed, 82 insertions(+), 48 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index b6a1e59..618631f 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -44,7 +44,7 @@ pub struct BlockMeta { pub tx_meta: Vec, } -#[derive(Clone, Serialize, Deserialize, Encode, Decode)] +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] pub struct TxEntry { #[serde(flatten)] pub changeset: TxChangeSet, @@ -52,7 +52,7 @@ pub struct TxEntry { pub tx: Option, } -#[derive(Clone, Serialize, Deserialize, Encode, Decode)] +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] pub struct TxData { pub position: u32, pub raw: Bytes, diff --git a/client/src/rpc.rs b/client/src/rpc.rs index a205376..7677b8a 100644 --- a/client/src/rpc.rs +++ b/client/src/rpc.rs @@ -80,6 +80,20 @@ pub struct RootAnchor { pub block: ChainAnchor, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum BlockIdentifier { + Hash(BlockHash), + Height(u32), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BlockMetaWithHash { + pub hash: BlockHash, + pub height: u32, + pub tx_meta: Vec, +} + pub enum ChainStateCommand { CheckPackage { txs: Vec, @@ -105,8 +119,8 @@ pub enum ChainStateCommand { resp: Responder>>, }, GetBlockMeta { - block_hash: BlockHash, - resp: Responder>>, + block_identifier: BlockIdentifier, + resp: Responder>, }, EstimateBid { target: usize, @@ -179,8 +193,8 @@ pub trait Rpc { #[method(name = "getblockmeta")] async fn get_block_meta( &self, - block_hash: BlockHash, - ) -> Result, ErrorObjectOwned>; + block_identifier: BlockIdentifier, + ) -> Result; #[method(name = "gettxmeta")] async fn get_tx_meta(&self, txid: Txid) -> Result, ErrorObjectOwned>; @@ -599,19 +613,6 @@ impl WalletManager { Ok(()) } - pub async fn get_block_hash( - &self, - client: &reqwest::Client, - height: u32, - ) -> anyhow::Result { - let hash = self - .rpc - .send_json(&client, &self.rpc.get_block_hash(height)) - .await?; - - Ok(BlockId { height, hash }) - } - async fn get_wallet_start_block(&self, client: &reqwest::Client) -> anyhow::Result { let count: i32 = self .rpc @@ -797,11 +798,11 @@ impl RpcServer for RpcServerImpl { async fn get_block_meta( &self, - block_hash: BlockHash, - ) -> Result, ErrorObjectOwned> { + block_identifier: BlockIdentifier, + ) -> Result { let data = self .store - .get_block_meta(block_hash) + .get_block_meta(block_identifier) .await .map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::))?; @@ -1072,55 +1073,74 @@ impl AsyncChainState { BlockHash::from_str(info.get("blockhash").and_then(|t| t.as_str()).ok_or_else( || anyhow!("Could not retrieve block hash for tx (is it in the mempool?)"), )?)?; - let block = Self::get_indexed_block(index, &block_hash, client, rpc, chain_state).await?; + let block = Self::get_indexed_block( + index, + BlockIdentifier::Hash(block_hash), + client, + rpc, + chain_state, + ) + .await?; - if let Some(block) = block { - return Ok(block - .tx_meta - .into_iter() - .find(|tx| &tx.changeset.txid == txid)); - } - Ok(None) + Ok(block + .tx_meta + .into_iter() + .find(|tx| &tx.changeset.txid == txid)) } async fn get_indexed_block( index: &mut Option, - block_hash: &BlockHash, + block_identifier: BlockIdentifier, client: &reqwest::Client, rpc: &BitcoinRpc, chain_state: &mut LiveSnapshot, - ) -> Result, anyhow::Error> { + ) -> Result { let index = index .as_mut() .ok_or_else(|| anyhow!("block index must be enabled"))?; - let hash = BaseHash::from_slice(block_hash.as_ref()); - let block: Option = index - .get(hash) - .context("Could not fetch block from index")?; + let hash = match block_identifier { + BlockIdentifier::Hash(hash) => hash, + BlockIdentifier::Height(height) => rpc + .send_json(client, &rpc.get_block_hash(height)) + .await + .map_err(|e| anyhow!("Could not retrieve block hash ({})", e))?, + }; - if let Some(block_set) = block { - return Ok(Some(block_set)); + if let Some(BlockMeta { height, tx_meta }) = index + .get(BaseHash::from_slice(hash.as_ref())) + .context("Could not fetch block from index")? + { + return Ok(BlockMetaWithHash { + hash, + height, + tx_meta, + }); } let info: serde_json::Value = rpc - .send_json(client, &rpc.get_block_header(block_hash)) + .send_json(client, &rpc.get_block_header(&hash)) .await .map_err(|e| anyhow!("Could not retrieve block ({})", e))?; let height = info .get("height") .and_then(|t| t.as_u64()) + .and_then(|h| u32::try_from(h).ok()) .ok_or_else(|| anyhow!("Could not retrieve block height"))?; let tip = chain_state.tip.read().expect("read meta").clone(); - if height > tip.height as u64 { + if height > tip.height { return Err(anyhow!( "Spaces is syncing at height {}, requested block height {}", tip.height, height )); } - Ok(None) + Ok(BlockMetaWithHash { + hash, + height, + tx_meta: Vec::new(), + }) } pub async fn handle_command( @@ -1168,10 +1188,18 @@ impl AsyncChainState { .context("could not fetch spaceout"); let _ = resp.send(result); } - ChainStateCommand::GetBlockMeta { block_hash, resp } => { - let res = - Self::get_indexed_block(block_index, &block_hash, client, rpc, chain_state) - .await; + ChainStateCommand::GetBlockMeta { + block_identifier, + resp, + } => { + let res = Self::get_indexed_block( + block_index, + block_identifier, + client, + rpc, + chain_state, + ) + .await; let _ = resp.send(res); } ChainStateCommand::GetTxMeta { txid, resp } => { @@ -1479,10 +1507,16 @@ impl AsyncChainState { resp_rx.await? } - pub async fn get_block_meta(&self, block_hash: BlockHash) -> anyhow::Result> { + pub async fn get_block_meta( + &self, + block_identifier: BlockIdentifier, + ) -> anyhow::Result { let (resp, resp_rx) = oneshot::channel(); self.sender - .send(ChainStateCommand::GetBlockMeta { block_hash, resp }) + .send(ChainStateCommand::GetBlockMeta { + block_identifier, + resp, + }) .await?; resp_rx.await? } diff --git a/protocol/src/validate.rs b/protocol/src/validate.rs index a26a79e..64503ab 100644 --- a/protocol/src/validate.rs +++ b/protocol/src/validate.rs @@ -17,7 +17,7 @@ use crate::{ #[derive(Debug, Clone)] pub struct Validator {} -#[derive(Clone)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "bincode", derive(Encode, Decode))] /// A `TxChangeSet` captures all resulting state changes. From 3488283d4d83bc8848b9b67f73a87cc3c1a55f78 Mon Sep 17 00:00:00 2001 From: Alex Tsokurov Date: Tue, 4 Mar 2025 19:03:57 +0100 Subject: [PATCH 2/2] better naming for block meta data --- client/src/client.rs | 2 +- client/src/rpc.rs | 44 +++++++++++++++++++++++--------------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index 618631f..61b943c 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -38,7 +38,7 @@ pub struct Client { /// A block structure containing validated transaction metadata /// relevant to the Spaces protocol -#[derive(Clone, Serialize, Deserialize, Encode, Decode)] +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] pub struct BlockMeta { pub height: u32, pub tx_meta: Vec, diff --git a/client/src/rpc.rs b/client/src/rpc.rs index 7677b8a..a10b6d3 100644 --- a/client/src/rpc.rs +++ b/client/src/rpc.rs @@ -82,7 +82,7 @@ pub struct RootAnchor { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] -pub enum BlockIdentifier { +pub enum HeightOrHash { Hash(BlockHash), Height(u32), } @@ -90,8 +90,8 @@ pub enum BlockIdentifier { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BlockMetaWithHash { pub hash: BlockHash, - pub height: u32, - pub tx_meta: Vec, + #[serde(flatten)] + pub block_meta: BlockMeta, } pub enum ChainStateCommand { @@ -119,7 +119,7 @@ pub enum ChainStateCommand { resp: Responder>>, }, GetBlockMeta { - block_identifier: BlockIdentifier, + height_or_hash: HeightOrHash, resp: Responder>, }, EstimateBid { @@ -193,7 +193,7 @@ pub trait Rpc { #[method(name = "getblockmeta")] async fn get_block_meta( &self, - block_identifier: BlockIdentifier, + height_or_hash: HeightOrHash, ) -> Result; #[method(name = "gettxmeta")] @@ -798,11 +798,11 @@ impl RpcServer for RpcServerImpl { async fn get_block_meta( &self, - block_identifier: BlockIdentifier, + height_or_hash: HeightOrHash, ) -> Result { let data = self .store - .get_block_meta(block_identifier) + .get_block_meta(height_or_hash) .await .map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::))?; @@ -1075,7 +1075,7 @@ impl AsyncChainState { )?)?; let block = Self::get_indexed_block( index, - BlockIdentifier::Hash(block_hash), + HeightOrHash::Hash(block_hash), client, rpc, chain_state, @@ -1083,6 +1083,7 @@ impl AsyncChainState { .await?; Ok(block + .block_meta .tx_meta .into_iter() .find(|tx| &tx.changeset.txid == txid)) @@ -1090,7 +1091,7 @@ impl AsyncChainState { async fn get_indexed_block( index: &mut Option, - block_identifier: BlockIdentifier, + height_or_hash: HeightOrHash, client: &reqwest::Client, rpc: &BitcoinRpc, chain_state: &mut LiveSnapshot, @@ -1098,22 +1099,21 @@ impl AsyncChainState { let index = index .as_mut() .ok_or_else(|| anyhow!("block index must be enabled"))?; - let hash = match block_identifier { - BlockIdentifier::Hash(hash) => hash, - BlockIdentifier::Height(height) => rpc + let hash = match height_or_hash { + HeightOrHash::Hash(hash) => hash, + HeightOrHash::Height(height) => rpc .send_json(client, &rpc.get_block_hash(height)) .await .map_err(|e| anyhow!("Could not retrieve block hash ({})", e))?, }; - if let Some(BlockMeta { height, tx_meta }) = index + if let Some(block_meta) = index .get(BaseHash::from_slice(hash.as_ref())) .context("Could not fetch block from index")? { return Ok(BlockMetaWithHash { hash, - height, - tx_meta, + block_meta, }); } @@ -1138,8 +1138,10 @@ impl AsyncChainState { } Ok(BlockMetaWithHash { hash, - height, - tx_meta: Vec::new(), + block_meta: BlockMeta { + height, + tx_meta: Vec::new(), + }, }) } @@ -1189,12 +1191,12 @@ impl AsyncChainState { let _ = resp.send(result); } ChainStateCommand::GetBlockMeta { - block_identifier, + height_or_hash, resp, } => { let res = Self::get_indexed_block( block_index, - block_identifier, + height_or_hash, client, rpc, chain_state, @@ -1509,12 +1511,12 @@ impl AsyncChainState { pub async fn get_block_meta( &self, - block_identifier: BlockIdentifier, + height_or_hash: HeightOrHash, ) -> anyhow::Result { let (resp, resp_rx) = oneshot::channel(); self.sender .send(ChainStateCommand::GetBlockMeta { - block_identifier, + height_or_hash, resp, }) .await?;