diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 73cb934de..a83ed2481 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,4 @@ [toolchain] -channel = "stable" +#channel = "stable" +channel = "1.88.0" components = ["rustfmt", "clippy"] diff --git a/zaino-fetch/src/jsonrpsee/connector.rs b/zaino-fetch/src/jsonrpsee/connector.rs index 46ce0c999..b5eec40f6 100644 --- a/zaino-fetch/src/jsonrpsee/connector.rs +++ b/zaino-fetch/src/jsonrpsee/connector.rs @@ -26,11 +26,11 @@ use crate::jsonrpsee::{ error::{JsonRpcError, TransportError}, response::{ block_subsidy::GetBlockSubsidy, peer_info::GetPeerInfo, GetBalanceError, - GetBalanceResponse, GetBlockCountResponse, GetBlockError, GetBlockHash, GetBlockResponse, - GetBlockchainInfoResponse, GetInfoResponse, GetMempoolInfoResponse, GetSubtreesError, - GetSubtreesResponse, GetTransactionResponse, GetTreestateError, GetTreestateResponse, - GetUtxosError, GetUtxosResponse, SendTransactionError, SendTransactionResponse, TxidsError, - TxidsResponse, + GetBalanceResponse, GetBlockCountResponse, GetBlockError, GetBlockHash, + GetBlockHeaderResponse, GetBlockResponse, GetBlockchainInfoResponse, GetInfoResponse, + GetMempoolInfoResponse, GetSubtreesError, GetSubtreesResponse, GetTransactionResponse, + GetTreestateError, GetTreestateResponse, GetUtxosError, GetUtxosResponse, + SendTransactionError, SendTransactionResponse, TxidsError, TxidsResponse, }, }; @@ -420,6 +420,18 @@ impl JsonRpSeeConnector { .await } + /// from online zcashd RPC reference docs: + /// getblockheader 'hash' ( verbose ) + /// + /// If verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'. + /// If verbose is true, returns an Object with information about blockheader . + pub async fn get_block_header( + &self, + ) -> Result> { + // do something + unreachable!("so far"); + } + /// Returns details on the active state of the TX memory pool. /// /// online zcash rpc reference: [`getmempoolinfo`](https://zcash.github.io/rpc/getmempoolinfo.html) diff --git a/zaino-fetch/src/jsonrpsee/response.rs b/zaino-fetch/src/jsonrpsee/response.rs index 6f07e09ff..5e0b8ae47 100644 --- a/zaino-fetch/src/jsonrpsee/response.rs +++ b/zaino-fetch/src/jsonrpsee/response.rs @@ -1526,3 +1526,140 @@ pub struct GetMempoolInfoResponse { impl ResponseToError for GetMempoolInfoResponse { type RpcError = Infallible; } + +/// The Zcash source code is considered canonical: +/// +/// block.cpp and block.h define the core data structures for blocks and their serialization, validation, and header logic +/// within `class CBlockHeader` the first two lines are enums and do not represent fields: +/// https://github.com/zcash/zcash/blob/b65b008a7b334a2f7c2eaae1b028e011f2e21dd1/src/primitives/block.h#L30 +/// The HEADER_SIZE enum defines the constant byte size of a block header, +/// while CURRENT_VERSION specifies the default version number used for newly created blocks. +/// The fields begin on this line: +/// https://github.com/zcash/zcash/blob/b65b008a7b334a2f7c2eaae1b028e011f2e21dd1/src/primitives/block.h#L32 +/// int32_t nVersion; +/// uint256 hashPrevBlock; +/// uint256 hashMerkleRoot; +/// uint256 hashBlockCommitments; +/// uint32_t nTime; +/// uint32_t nBits; +/// uint256 nNonce; +/// std::vector nSolution; +/// +/// SetNull() is used to clear field values: +/// https://github.com/zcash/zcash/blob/b65b008a7b334a2f7c2eaae1b028e011f2e21dd1/src/primitives/block.h#L60 +/// Cblock represents a full block, and inherits from CBlockHeader: +/// +/// +/// chain.cpp and .h represent the state of the blockchain: structure, indexing, and chain selection +/// The function does not modify the state of the object: it is called on `const`, +/// with a return type defined as CBlockHeader in chain.h file: +/// +/// +/// CBlockHeader GetBlockHeader() const +/// { +/// CBlockHeader header; +/// header.nVersion = nVersion; +/// header.hashPrevBlock = hashPrev; +/// header.hashMerkleRoot = hashMerkleRoot; +/// header.hashBlockCommitments = hashBlockCommitments; +/// header.nTime = nTime; +/// header.nBits = nBits; +/// header.nNonce = nNonce; +/// header.nSolution = nSolution; +/// return header; +/// } +/// matching what's above. +/// see also +/// [chain.cpp link](https://github.com/zcash/zcash/blob/b65b008a7b334a2f7c2eaae1b028e011f2e21dd1/src/chain.cpp#L82) +/// +// TODO: compare Chain.cpp, Block.cpp and the online RPC docs. +/// https://zcash.github.io/rpc/getblockheader.html : +/// --has these return fields documented that overlap with the C++ code: +/// version. +/// merkleroot. +/// time. +/// bits. +/// nonce. +/// prev. block hash. +/// --and these that do not: +/// hash (same as provided RPC argument) +/// confirmations (confirmations but only on best-chain, else -1) +/// height +/// finalsaplingroot (The root of the Sapling commitment tree after applying this block): see comment on hashBlockCommitments below. +/// difficulty ("x.xxx" - floating point) +/// next block hash +/// --leaving these in the C++ code unreported in the online docs: +/// nSolution +/// hashBlockCommitments (thought to be an aggregate including the merkleroot, and finalsapling root ) +/// +/// hashBlockCommitments = Merkle roots of transaction commitments, including transparent and shielded ones — into a single value that is effectively committed to by the block. +/// maybe kind of hashsum for all tx in block. +/// +/// A real return using `zcash-cli` from zcashd 6.3.0 running on mainnet: +/// getblockheader 000003f9071a74cd0a1f7dba0614cd3dbd38b8afa401849c41a624c6a7b919a3 +/// { +/// "hash": "000003f9071a74cd0a1f7dba0614cd3dbd38b8afa401849c41a624c6a7b919a3", +/// "confirmations": 1151, +/// "height": 304, +/// "version": 4, +/// "merkleroot": "670da2a6b2b7bcfce573b21edf8432863f5bf33eb904b0450ae4023a38ef8f70", +/// "finalsaplingroot": "0000000000000000000000000000000000000000000000000000000000000000", +/// "time": 1477673429, +/// "nonce": "0000cf5e461e3ed275a3dffecca8ace6c147bd6bcaa6692ad87d29f5aa1d0000", +/// "solution": "00c886a0b5c5853129ec616a66f0686d1656f9e7e02efa3ff27814cea191134a213b5774629c8e7ae3491eab5bee00f5f5b7d726027974c0edd91de72ac07f2d795feea493070b8b46d6e223f15596f402f87ebe056d6934c7037beaef55219d9c3e770a90913f4cf3187cf606c68bc1e1fb0b030c846e63e90d6a8a74e11a12e32667985829267d61f61fa3c49cb6edbc841e2f54eeaa069fd135eee6e3a256bdc0915b2e9b5e92025954d35a89f2cf8ef1637161ddd719c8d3bb6cd14a014ce3f9345925edf705593c35a4530d553c6cb814eb940a0f874de5da31c2d566b10675a2ac7337981c921355aecbae62abee979458724485eeb850b2530365dc2ca08ac2f8a7ac13e33fd7f82a55fcd4d842175e649848869e250a70c630f5604492216cdb366a10e717201957153f21df41bc97be7538d9e2da1f33172b33b9ee8a6704f21b03c0c0a0da11040f690f6600dc0fec2bc0d55ddf54e5c4b673414d854411fba40f805d4ac4ac88cf99d7c2b017b194eba7bc9dfa4f0e8a0e267e6424534133e240194d9f254b32d9c7e51b3a838981e89779b58185bab0def9c2e50ab8f38e024f25adefaebd5b944e854a07d48a6201ce34cff5125aa09784a404e1b3be66a5916bf4adafe4560aa6260bde0d8a560b5f7e3f82a1616f39497b34924179482f8afcde1cf343160ba74182e645de969d15becb0701f7ef0a54070afd74c64c63d655e9f52b5432cf33295ce70b0e5c7935202539b87ede4b4ad89b56bd23e62476649ef9b10b2bd92baa63d62a57e7b5b472300ccb5e0bdf731fb9e0e8ca1fd919fe67001d0abc115d593476cb7424f1a283bced761c20f393493ef0376324f752a7eb6b871125e04378344d84e74cef562e4c45b098cf5c8f68f5c3d857affa5bbd591e78cd318d29f3af1afbc7788f23ae8164cf39ff04748ff600d525ff32084c14fd817b89cc79d7379cf3fdb2a00228a1b8bb2df2e7e277c357feba972566ba2cdc971329aba7132054b5168ee132b131633af3e079f1514115d719f163ab9d3b7db58a993db1f995d1f10f990195a396b612210d8e0bf15424af0a74bcc9cd367a0ee2df0d6f7f6419fe8ca1e86f7451f95bb3f3676526bfd1a802570aa8d85e177d013cca82fc3579244a39e0b425bc96f0ebdbe0b59a65428a4db0cdf8f05b04180d39fb2bc95bdacf3207362048b66d74f93f60079778e2ffaf6dcbb53c880abd4648c28e7857e06c0f89b10d63adc5a9bbace110ae71d6ce746a1dc5c31b219b2cfd19ed88fa69238e4ba4cae6787c749e85046d8d3a04387d65e927c25dd5160b29774b96d8bd25d542221e0c8fdb38f062a3913abc8165e1eb96c405be5d2d4594ab2bcbe6725af82fe3f9f8adbd3f5d5caf33d5939e19ef2a0526f8ccb9c8fe1cfb5652a404f8f04682ce5a4334af2bef30f247978702dc38ae206db5c783e76d53bb85343bd925315d81382e18f11d5479b5254d818b6bf8f6c68fb9879a9b63fcbfb042711b4c372a8e23fd62d87cfee662fa51f0dce80d0ddc91503fdb029334c1b66929d5525a84f534700c38c00e14aad4949f687687aff2feab223712b6f002153967f0281ae9f5a40ce2b55b458b6aac65fd88702c70f4b070b01bc996d2b43a09d4a6466a7234cba405387387e25c4027e9aa409868d2ed627b429e70ff06020198ea5c5bcd61a8010784d49314f60d9fac24086c8c8b6824cdc7e433b9121cffc7fe80ac1d82331491de5cab0f8178ef76140ddaba6fc98a35b5bcaf0c8bfdab520fb807ea6377b1f8edfada6163f95e7714a078e1fe4d11d8e922c17cfa5bd219ecbc392e131bb4158b6c2a0ff16bb462fdf3f576116bc8f335f939d2ae0ca4ad72d39e4c5a93a97920db264f7e67fd", +/// "bits": "1e085354", +/// "difficulty": 245.990748139731, +/// "chainwork": "0000000000000000000000000000000000000000000000000000000006659ef3", +/// "previousblockhash": "000001b5ad3057566497fa4cf1ad5519fa6a39acb0cd249aa23ca7d3b2ebd8f5", +/// "nextblockhash": "0000064be84052d3a4cda52592db6a61cd4cb127e34cd42404bba18d870b1aaa" +/// } +/// +/// this includes all the fields in the online RPC docs, as well as: +/// solution (visble to me in C++ code) +/// chainwork (not seen yet) +/// ... according to https://zcash.github.io/rpc/getblockchaininfo.html +/// "chainwork": "xxxx" (string) total amount of work in active chain, in hexadecimal +/// this number does increment during chain sync with `getblockchaininfo`, +/// also present in getblockdeltas documentation - but I can confirm the zcash-cli RPC call is not functioning, at least in the same way. +/// but is a set amount when using getblockheader +/// therefore I think it is likely to be able to be found in the block, somehow. +/// https://github.com/zcash/zcash/blob/b65b008a7b334a2f7c2eaae1b028e011f2e21dd1/src/rpc/blockchain.cpp#L126 +/// https://github.com/zcash/zcash/blob/b65b008a7b334a2f7c2eaae1b028e011f2e21dd1/src/rpc/blockchain.cpp#L268 : +/// result.pushKV("chainwork", blockindex->nChainWork.GetHex()); +/// +/// however, zcash-cli when backed by zebrad 2.5.0 does NOT return chainwork and DOES return blockcommitments +/// (though my example with unsynced zebra was all 000s): +/// getblockheader 000003f9071a74cd0a1f7dba0614cd3dbd38b8afa401849c41a624c6a7b919a3 +/// { +/// "hash": "000003f9071a74cd0a1f7dba0614cd3dbd38b8afa401849c41a624c6a7b919a3", +/// "confirmations": 1848585, +/// "height": 304, +/// "version": 4, +/// "merkleroot": "670da2a6b2b7bcfce573b21edf8432863f5bf33eb904b0450ae4023a38ef8f70", +/// "blockcommitments": "0000000000000000000000000000000000000000000000000000000000000000", +/// "finalsaplingroot": "0000000000000000000000000000000000000000000000000000000000000000", +/// "time": 1477673429, +/// "nonce": "0000cf5e461e3ed275a3dffecca8ace6c147bd6bcaa6692ad87d29f5aa1d0000", +/// "solution": "00c886a0b5c5853129ec616a66f0686d1656f9e7e02efa3ff27814cea191134a213b5774629c8e7ae3491eab5bee00f5f5b7d726027974c0edd91de72ac07f2d795feea493070b8b46d6e223f15596f402f87ebe056d6934c7037beaef55219d9c3e770a90913f4cf3187cf606c68bc1e1fb0b030c846e63e90d6a8a74e11a12e32667985829267d61f61fa3c49cb6edbc841e2f54eeaa069fd135eee6e3a256bdc0915b2e9b5e92025954d35a89f2cf8ef1637161ddd719c8d3bb6cd14a014ce3f9345925edf705593c35a4530d553c6cb814eb940a0f874de5da31c2d566b10675a2ac7337981c921355aecbae62abee979458724485eeb850b2530365dc2ca08ac2f8a7ac13e33fd7f82a55fcd4d842175e649848869e250a70c630f5604492216cdb366a10e717201957153f21df41bc97be7538d9e2da1f33172b33b9ee8a6704f21b03c0c0a0da11040f690f6600dc0fec2bc0d55ddf54e5c4b673414d854411fba40f805d4ac4ac88cf99d7c2b017b194eba7bc9dfa4f0e8a0e267e6424534133e240194d9f254b32d9c7e51b3a838981e89779b58185bab0def9c2e50ab8f38e024f25adefaebd5b944e854a07d48a6201ce34cff5125aa09784a404e1b3be66a5916bf4adafe4560aa6260bde0d8a560b5f7e3f82a1616f39497b34924179482f8afcde1cf343160ba74182e645de969d15becb0701f7ef0a54070afd74c64c63d655e9f52b5432cf33295ce70b0e5c7935202539b87ede4b4ad89b56bd23e62476649ef9b10b2bd92baa63d62a57e7b5b472300ccb5e0bdf731fb9e0e8ca1fd919fe67001d0abc115d593476cb7424f1a283bced761c20f393493ef0376324f752a7eb6b871125e04378344d84e74cef562e4c45b098cf5c8f68f5c3d857affa5bbd591e78cd318d29f3af1afbc7788f23ae8164cf39ff04748ff600d525ff32084c14fd817b89cc79d7379cf3fdb2a00228a1b8bb2df2e7e277c357feba972566ba2cdc971329aba7132054b5168ee132b131633af3e079f1514115d719f163ab9d3b7db58a993db1f995d1f10f990195a396b612210d8e0bf15424af0a74bcc9cd367a0ee2df0d6f7f6419fe8ca1e86f7451f95bb3f3676526bfd1a802570aa8d85e177d013cca82fc3579244a39e0b425bc96f0ebdbe0b59a65428a4db0cdf8f05b04180d39fb2bc95bdacf3207362048b66d74f93f60079778e2ffaf6dcbb53c880abd4648c28e7857e06c0f89b10d63adc5a9bbace110ae71d6ce746a1dc5c31b219b2cfd19ed88fa69238e4ba4cae6787c749e85046d8d3a04387d65e927c25dd5160b29774b96d8bd25d542221e0c8fdb38f062a3913abc8165e1eb96c405be5d2d4594ab2bcbe6725af82fe3f9f8adbd3f5d5caf33d5939e19ef2a0526f8ccb9c8fe1cfb5652a404f8f04682ce5a4334af2bef30f247978702dc38ae206db5c783e76d53bb85343bd925315d81382e18f11d5479b5254d818b6bf8f6c68fb9879a9b63fcbfb042711b4c372a8e23fd62d87cfee662fa51f0dce80d0ddc91503fdb029334c1b66929d5525a84f534700c38c00e14aad4949f687687aff2feab223712b6f002153967f0281ae9f5a40ce2b55b458b6aac65fd88702c70f4b070b01bc996d2b43a09d4a6466a7234cba405387387e25c4027e9aa409868d2ed627b429e70ff06020198ea5c5bcd61a8010784d49314f60d9fac24086c8c8b6824cdc7e433b9121cffc7fe80ac1d82331491de5cab0f8178ef76140ddaba6fc98a35b5bcaf0c8bfdab520fb807ea6377b1f8edfada6163f95e7714a078e1fe4d11d8e922c17cfa5bd219ecbc392e131bb4158b6c2a0ff16bb462fdf3f576116bc8f335f939d2ae0ca4ad72d39e4c5a93a97920db264f7e67fd", +/// "bits": "1e085354", +/// "difficulty": 245.99074813973095, +/// "previousblockhash": "000001b5ad3057566497fa4cf1ad5519fa6a39acb0cd249aa23ca7d3b2ebd8f5", +/// "nextblockhash": "0000064be84052d3a4cda52592db6a61cd4cb127e34cd42404bba18d870b1aaa" +/// } + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetBlockHeaderResponse { + // fields taken from zcashd source code pasted above + version: (), // int32_t number + hash_previous_block: (), // uint256 hash + hash_merkle_root: (), // uint256 hash + hash_block_commitments: (), // uint256 hash + time: (), // uint32_t number + bits: (), // uint32_t number + nonce: (), // uint256 number + solution: (), // vec (char in C++ often represents raw bytes, and nSolution appears to mean 'number used once' or a misapplied convention.) +} + +impl ResponseToError for GetBlockHeaderResponse { + type RpcError = Infallible; +} diff --git a/zaino-state/src/backends/fetch.rs b/zaino-state/src/backends/fetch.rs index 2bafd9d78..c74cb5473 100644 --- a/zaino-state/src/backends/fetch.rs +++ b/zaino-state/src/backends/fetch.rs @@ -23,9 +23,8 @@ use zaino_fetch::{ jsonrpsee::{ connector::{JsonRpSeeConnector, RpcError}, response::{ - block_subsidy::GetBlockSubsidy, - peer_info::GetPeerInfo, - {GetMempoolInfoResponse, GetNetworkSolPsResponse}, + block_subsidy::GetBlockSubsidy, peer_info::GetPeerInfo, GetBlockHeaderResponse, + GetMempoolInfoResponse, GetNetworkSolPsResponse, }, }, }; @@ -216,6 +215,42 @@ impl ZcashIndexer for FetchServiceSubscriber { Ok(self.fetcher.get_info().await?.into()) } + /// getblockheader 'hash' ( verbose ) + /// If verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'. + /// If verbose is true, returns an Object with information about blockheader 'hash'. + /// + /// online zcashd reference: [`getblockheader`](https://zcash.github.io/rpc/getblockheader.html) + /// method: post + /// tags: blockchain + // TODO params are only demos + async fn get_block_header( + &self, + _hash_or_height_demo: HashOrHeight, + _verbose: Option, + ) -> Result { + Ok(self.fetcher.get_block_header().await?) + } + + /// getblockheader 'hash' ( verbose ) + /// If verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'. + /// If verbose is true, returns an Object with information about blockheader 'hash'. + /// + /// online zcashd reference: [`getblockheader`](https://zcash.github.io/rpc/getblockheader.html) + /// method: post + /// tags: blockchain + // TODO params are only demos + /* + async fn get_block_header_static( + _: _, + _: _, + _hash_or_height_demo: HashOrHeight, + _verbose: Option, + ) -> Result { + unreachable!("so far"); + //Ok(self.fetcher.get_block_header().await?) + } + */ + /// Returns blockchain state information, as a [`GetBlockchainInfoResponse`] JSON struct. /// /// zcashd reference: [`getblockchaininfo`](https://zcash.github.io/rpc/getblockchaininfo.html) diff --git a/zaino-state/src/backends/state.rs b/zaino-state/src/backends/state.rs index 19b48f655..905b48c36 100644 --- a/zaino-state/src/backends/state.rs +++ b/zaino-state/src/backends/state.rs @@ -43,11 +43,12 @@ use zaino_proto::proto::{ use zcash_protocol::consensus::NetworkType; use zebra_chain::{ - block::{Header, Height, SerializedBlock}, + block::{merkle::Root, Hash, Header, Height, SerializedBlock}, chain_tip::NetworkChainTipHeightEstimator, parameters::{ConsensusBranchId, Network, NetworkKind, NetworkUpgrade}, serialization::ZcashSerialize, subtree::NoteCommitmentSubtreeIndex, + work::{difficulty::CompactDifficulty, equihash::Solution}, }; use zebra_rpc::{ client::{ @@ -56,8 +57,8 @@ use zebra_rpc::{ }, methods::{ chain_tip_difficulty, AddressBalance, AddressStrings, ConsensusBranchIdHex, - GetAddressTxIdsRequest, GetAddressUtxos, GetBlock, GetBlockHash, GetBlockHeader, - GetBlockHeaderObject, GetBlockTransaction, GetBlockTrees, GetBlockchainInfoResponse, + GetAddressTxIdsRequest, GetAddressUtxos, GetBlock, GetBlockHash, GetBlockHeaderObject, + GetBlockHeaderResponse, GetBlockTransaction, GetBlockTrees, GetBlockchainInfoResponse, GetInfo, GetRawTransaction, NetworkUpgradeInfo, NetworkUpgradeStatus, SentTransactionHash, TipConsensusBranch, }, @@ -317,9 +318,6 @@ impl Drop for StateService { } } -/// A fetch service subscriber. -/// -/// Subscribers should be #[derive(Debug, Clone)] pub struct StateServiceSubscriber { /// Remote wrappper functionality for zebra's [`ReadStateService`]. @@ -368,148 +366,6 @@ impl StateServiceSubscriber { monitor: self.chain_tip_change.clone(), } } - /// Returns the requested block header by hash or height, as a [`GetBlockHeader`] JSON string. - /// If the block is not in Zebra's state, - /// returns [error code `-8`.](https://github.com/zcash/zcash/issues/5758) - /// if a height was passed or -5 if a hash was passed. - /// - /// zcashd reference: [`getblockheader`](https://zcash.github.io/rpc/getblockheader.html) - /// method: post - /// tags: blockchain - /// - /// # Parameters - /// - /// - `hash_or_height`: (string, required, example="1") The hash or height - /// for the block to be returned. - /// - `verbose`: (bool, optional, default=false, example=true) false for hex encoded data, - /// true for a json object - /// - /// # Notes - /// - /// The undocumented `chainwork` field is not returned. - /// - /// This rpc is used by get_block(verbose), there is currently no - /// plan to offer this RPC publicly. - async fn get_block_header( - state: &ReadStateService, - network: &Network, - hash_or_height: HashOrHeight, - verbose: Option, - ) -> Result { - let mut state = state.clone(); - let verbose = verbose.unwrap_or(true); - let network = network.clone(); - - let zebra_state::ReadResponse::BlockHeader { - header, - hash, - height, - next_block_hash, - } = state - .ready() - .and_then(|service| service.call(zebra_state::ReadRequest::BlockHeader(hash_or_height))) - .await - .map_err(|_| { - StateServiceError::RpcError(RpcError { - // Compatibility with zcashd. Note that since this function - // is reused by getblock(), we return the errors expected - // by it (they differ whether a hash or a height was passed) - code: LegacyCode::InvalidParameter as i64, - message: "block height not in best chain".to_string(), - data: None, - }) - })? - else { - return Err(StateServiceError::Custom( - "Unexpected response to BlockHeader request".to_string(), - )); - }; - - let response = if !verbose { - GetBlockHeader::Raw(HexData(header.zcash_serialize_to_vec()?)) - } else { - let zebra_state::ReadResponse::SaplingTree(sapling_tree) = state - .ready() - .and_then(|service| { - service.call(zebra_state::ReadRequest::SaplingTree(hash_or_height)) - }) - .await? - else { - return Err(StateServiceError::Custom( - "Unexpected response to SaplingTree request".to_string(), - )); - }; - // This could be `None` if there's a chain reorg between state queries. - let sapling_tree = sapling_tree.ok_or_else(|| { - StateServiceError::RpcError(zaino_fetch::jsonrpsee::connector::RpcError { - code: LegacyCode::InvalidParameter as i64, - message: "missing sapling tree for block".to_string(), - data: None, - }) - })?; - - let zebra_state::ReadResponse::Depth(depth) = state - .ready() - .and_then(|service| service.call(zebra_state::ReadRequest::Depth(hash))) - .await? - else { - return Err(StateServiceError::Custom( - "Unexpected response to Depth request".to_string(), - )); - }; - - // From - // TODO: Deduplicate const definition, consider - // refactoring this to avoid duplicate logic - const NOT_IN_BEST_CHAIN_CONFIRMATIONS: i64 = -1; - - // Confirmations are one more than the depth. - // Depth is limited by height, so it will never overflow an i64. - let confirmations = depth - .map(|depth| i64::from(depth) + 1) - .unwrap_or(NOT_IN_BEST_CHAIN_CONFIRMATIONS); - - let mut nonce = *header.nonce; - nonce.reverse(); - - let sapling_activation = NetworkUpgrade::Sapling.activation_height(&network); - let sapling_tree_size = sapling_tree.count(); - let final_sapling_root: [u8; 32] = - if sapling_activation.is_some() && height >= sapling_activation.unwrap() { - let mut root: [u8; 32] = sapling_tree.root().into(); - root.reverse(); - root - } else { - [0; 32] - }; - - let difficulty = header.difficulty_threshold.relative_to_network(&network); - let block_commitments = - header_to_block_commitments(&header, &network, height, final_sapling_root)?; - - let block_header = GetBlockHeaderObject::new( - hash, - confirmations, - height, - header.version, - header.merkle_root, - block_commitments, - final_sapling_root, - sapling_tree_size, - header.time.timestamp(), - nonce, - header.solution, - header.difficulty_threshold, - difficulty, - header.previous_block_hash, - next_block_hash, - ); - - GetBlockHeader::Object(Box::new(block_header)) - }; - - Ok(response) - } /// Return a list of consecutive compact blocks. #[allow(dead_code)] @@ -597,7 +453,7 @@ impl StateServiceSubscriber { }; let Ok(hash_or_height) = <[u8; 32]>::try_from(child_block.prev_hash.as_slice()) - .map(zebra_chain::block::Hash) + .map(Hash) .map(HashOrHeight::from) else { break; @@ -733,7 +589,7 @@ impl StateServiceSubscriber { let (fullblock, orchard_tree_response, header, block_info) = futures::join!( blockandsize_future, orchard_future, - StateServiceSubscriber::get_block_header( + StateServiceSubscriber::get_block_header_static( &state_3, network, hash_or_height, @@ -743,10 +599,12 @@ impl StateServiceSubscriber { ); let header_obj = match header? { - GetBlockHeader::Raw(_hex_data) => unreachable!( + GetBlockHeaderResponse::Raw(_hex_data) => unreachable!( "`true` was passed to get_block_header, an object should be returned" ), - GetBlockHeader::Object(get_block_header_object) => get_block_header_object, + GetBlockHeaderResponse::Object(get_block_header_object) => { + get_block_header_object + } }; let (transactions_response, size, block_info): (Vec, _, _) = @@ -873,6 +731,402 @@ impl ZcashIndexer for StateServiceSubscriber { .map_err(|e| StateServiceError::Custom(e.to_string())) } + /// Returns the requested block header by hash or height, as a [`GetBlockHeaderResponse`] JSON string. + /// If the block is not in Zebra's state, + /// returns [error code `-8`.](https://github.com/zcash/zcash/issues/5758) + /// if a height was passed or -5 if a hash was passed. + /// + /// zcashd reference: [`getblockheader`](https://zcash.github.io/rpc/getblockheader.html) + /// method: post + /// tags: blockchain + /// + /// # Parameters + /// + /// - `hash_or_height`: (string, required, example="1") The hash or height + /// for the block to be returned. + /// - `verbose`: (bool, optional, default=false, example=true) false for hex encoded data, + /// true for a json object + /// + /// # Notes + /// + /// The undocumented `chainwork` field is not returned. + /// + /// This rpc is used by get_block(verbose), there is currently no + /// plan to offer this RPC publicly. + async fn get_block_header_static( + state: &ReadStateService, + network: &Network, + hash_or_height: HashOrHeight, + verbose: Option, + ) -> Result { + let mut state = state.clone(); + let verbose = verbose.unwrap_or(true); + let network = network.clone(); + + let zebra_state::ReadResponse::BlockHeader { + header, + hash, + height, + next_block_hash, + } = state + .ready() + .and_then(|service| service.call(zebra_state::ReadRequest::BlockHeader(hash_or_height))) + .await + .map_err(|_| { + StateServiceError::RpcError(RpcError { + // Compatibility with zcashd. Note that since this function + // is reused by getblock(), we return the errors expected + // by it (they differ whether a hash or a height was passed) + code: LegacyCode::InvalidParameter as i64, + message: "block height not in best chain".to_string(), + data: None, + }) + })? + else { + return Err(StateServiceError::Custom( + "Unexpected response to BlockHeader request".to_string(), + )); + }; + + let response = if !verbose { + GetBlockHeaderResponse::Raw(HexData(header.zcash_serialize_to_vec()?)) + } else { + let zebra_state::ReadResponse::SaplingTree(sapling_tree) = state + .ready() + .and_then(|service| { + service.call(zebra_state::ReadRequest::SaplingTree(hash_or_height)) + }) + .await? + else { + return Err(StateServiceError::Custom( + "Unexpected response to SaplingTree request".to_string(), + )); + }; + // This could be `None` if there's a chain reorg between state queries. + let sapling_tree = sapling_tree.ok_or_else(|| { + StateServiceError::RpcError(zaino_fetch::jsonrpsee::connector::RpcError { + code: LegacyCode::InvalidParameter as i64, + message: "missing sapling tree for block".to_string(), + data: None, + }) + })?; + + let zebra_state::ReadResponse::Depth(depth) = state + .ready() + .and_then(|service| service.call(zebra_state::ReadRequest::Depth(hash))) + .await? + else { + return Err(StateServiceError::Custom( + "Unexpected response to Depth request".to_string(), + )); + }; + + // From + // TODO: Deduplicate const definition, consider + // refactoring this to avoid duplicate logic + const NOT_IN_BEST_CHAIN_CONFIRMATIONS: i64 = -1; + + // Confirmations are one more than the depth. + // Depth is limited by height, so it will never overflow an i64. + let confirmations = depth + .map(|depth| i64::from(depth) + 1) + .unwrap_or(NOT_IN_BEST_CHAIN_CONFIRMATIONS); + + let mut nonce = *header.nonce; + nonce.reverse(); + + let sapling_activation = NetworkUpgrade::Sapling.activation_height(&network); + let sapling_tree_size = sapling_tree.count(); + let final_sapling_root: [u8; 32] = + if sapling_activation.is_some() && height >= sapling_activation.unwrap() { + let mut root: [u8; 32] = sapling_tree.root().into(); + root.reverse(); + root + } else { + [0; 32] + }; + + let difficulty = header.difficulty_threshold.relative_to_network(&network); + let block_commitments = + header_to_block_commitments(&header, &network, height, final_sapling_root)?; + + let block_header = GetBlockHeaderObject::new( + hash, + confirmations, + height, + header.version, + header.merkle_root, + block_commitments, + final_sapling_root, + sapling_tree_size, + header.time.timestamp(), + nonce, + header.solution, + header.difficulty_threshold, + difficulty, + header.previous_block_hash, + next_block_hash, + ); + + GetBlockHeaderResponse::Object(Box::new(block_header)) + }; + + Ok(response) + } + + /// Returns the requested block header by hash or height, as a [`GetBlockHeaderResponse`] JSON string. + /// If the block is not in Zebra's state, + /// returns [error code `-8`.](https://github.com/zcash/zcash/issues/5758) + /// if a height was passed or -5 if a hash was passed. + /// + /// zcashd reference: [`getblockheader`](https://zcash.github.io/rpc/getblockheader.html) + /// method: post + /// tags: blockchain + /// + /// # Parameters + /// + /// - `hash_or_height`: (string, required, example="1") The hash or height + /// for the block to be returned. + /// - `verbose`: (bool, optional, default=false, example=true) false for hex encoded data, + /// true for a json object + /// + /// # Notes + /// + /// The undocumented `chainwork` field is not returned. + /// + /// This rpc is used by get_block(verbose), there is currently no + /// plan to offer this RPC publicly. + async fn get_block_header( + &self, + hash_or_height: HashOrHeight, + verbose: Option, + ) -> Result { + let mut state = state.clone(); + let verbose = verbose.unwrap_or(true); + let network = network.clone(); + + let zebra_state::ReadResponse::BlockHeader { + header, + hash, + height, + next_block_hash, + } = state + .ready() + .and_then(|service| service.call(zebra_state::ReadRequest::BlockHeader(hash_or_height))) + .await + .map_err(|_| { + StateServiceError::RpcError(RpcError { + // Compatibility with zcashd. Note that since this function + // is reused by getblock(), we return the errors expected + // by it (they differ whether a hash or a height was passed) + code: LegacyCode::InvalidParameter as i64, + message: "block height not in best chain".to_string(), + data: None, + }) + })? + else { + return Err(StateServiceError::Custom( + "Unexpected response to BlockHeader request".to_string(), + )); + }; + + let response = if !verbose { + GetBlockHeaderResponse::Raw(HexData(header.zcash_serialize_to_vec()?)) + } else { + let zebra_state::ReadResponse::SaplingTree(sapling_tree) = state + .ready() + .and_then(|service| { + service.call(zebra_state::ReadRequest::SaplingTree(hash_or_height)) + }) + .await? + else { + return Err(StateServiceError::Custom( + "Unexpected response to SaplingTree request".to_string(), + )); + }; + // This could be `None` if there's a chain reorg between state queries. + let sapling_tree = sapling_tree.ok_or_else(|| { + StateServiceError::RpcError(zaino_fetch::jsonrpsee::connector::RpcError { + code: LegacyCode::InvalidParameter as i64, + message: "missing sapling tree for block".to_string(), + data: None, + }) + })?; + + let zebra_state::ReadResponse::Depth(depth) = state + .ready() + .and_then(|service| service.call(zebra_state::ReadRequest::Depth(hash))) + .await? + else { + return Err(StateServiceError::Custom( + "Unexpected response to Depth request".to_string(), + )); + }; + + // From + // TODO: Deduplicate const definition, consider + // refactoring this to avoid duplicate logic + const NOT_IN_BEST_CHAIN_CONFIRMATIONS: i64 = -1; + + // Confirmations are one more than the depth. + // Depth is limited by height, so it will never overflow an i64. + let confirmations = depth + .map(|depth| i64::from(depth) + 1) + .unwrap_or(NOT_IN_BEST_CHAIN_CONFIRMATIONS); + + let mut nonce = *header.nonce; + nonce.reverse(); + + let sapling_activation = NetworkUpgrade::Sapling.activation_height(&network); + let sapling_tree_size = sapling_tree.count(); + let final_sapling_root: [u8; 32] = + if sapling_activation.is_some() && height >= sapling_activation.unwrap() { + let mut root: [u8; 32] = sapling_tree.root().into(); + root.reverse(); + root + } else { + [0; 32] + }; + + let difficulty = header.difficulty_threshold.relative_to_network(&network); + let block_commitments = + header_to_block_commitments(&header, &network, height, final_sapling_root)?; + + let block_header = GetBlockHeaderObject::new( + hash, + confirmations, + height, + header.version, + header.merkle_root, + block_commitments, + final_sapling_root, + sapling_tree_size, + header.time.timestamp(), + nonce, + header.solution, + header.difficulty_threshold, + difficulty, + header.previous_block_hash, + next_block_hash, + ); + + GetBlockHeaderResponse::Object(Box::new(block_header)) + }; + + Ok(response) + } + + /* + /// zcash online RPC docs: + /// Returns a string that is serialized, hex-encoded data for blockheader 'hash', or + /// if verbose, returns an Object with information about blockheader 'hash'. + /// Request parameters: + /// 1. "hash" (string, required) The block hash + /// 2. verbose (boolean, optional, default=true) true for a json object, false for the hex encoded data + /// + /// Result (for verbose = true): + /// { + /// "hash" : "hash", (string) the block hash (same as provided) + /// "confirmations" : n, (numeric) The number of confirmations, or -1 if the block is not on the main chain + /// "height" : n, (numeric) The block height or index + /// "version" : n, (numeric) The block version + /// "merkleroot" : "xxxx", (string) The merkle root + /// "finalsaplingroot" : "xxxx", (string) The root of the Sapling commitment tree after applying this block + /// "time" : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT) + /// "nonce" : n, (numeric) The nonce + /// "bits" : "1d00ffff", (string) The bits + /// "difficulty" : x.xxx, (numeric) The difficulty + /// "previousblockhash" : "hash", (string) The hash of the previous block + /// "nextblockhash" : "hash" (string) The hash of the next block + /// } + /// + /// The Zcash source is considered canonical: + /// + /// The function does not modify the state of the object: it is called on `const`, + /// with a return type defined as CBlockHeader in chain.h file: + /// + /// + /// see also + /// + /// GetBlochHeader() seems to take arg of CBlockHeader (hash of block) and has a return with these fields, + /// including a field of the same data used as argument: + /// { + // TODO: this is different than the online docs, we should drill into zcashd + // maybe setting some default fields when creating the block I don't see here? + /// CBlockHeader block; + /// block.nVersion = nVersion; + /// block.hashPrevBlock = hashPrevBlock; + /// block.hashMerkleRoot = hashMerkleRoot; + /// block.hashBlockCommitments = hashBlockCommitments; + /// block.nTime = nTime; + /// block.nBits = nBits; + /// block.nNonce = nNonce; + /// block.nSolution = nSolution; + /// return block; + /// } + /// [chain.cpp link](https://github.com/zcash/zcash/blob/b65b008a7b334a2f7c2eaae1b028e011f2e21dd1/src/chain.cpp#L82) + /// + /// The successful return Result of our function is GetBlockHeaderResponse, a type defined in zebra, + /// which is backed by BlockHeaderObjects in verbose form. + /// From Zebra Release 2.4.1: + /// + async fn get_block_header(&self) -> Result { + // let state = self.read_state_service.clone(); + // ReadStateService exposes a db in finalized state, but I am not sure how to + // access the block header response, see Zebra code and trace from that end to see how they get to it? + // + // TODO this is only a response for VERBOSE - arguments / parameters? compare with other RPCs that take arguments + Ok(GetBlockHeaderResponse::Object({ + Box::new(GetBlockHeaderObject::new( + // type: Hash + // hash: + Hash::from([0; 32]), + // confirmations: + 0, + // type: Height + // height: + Height(13), + // version: + 1, + // type: Root + // merkle_root: + Root::from([0; 32]), + // undocumented. type: [u8; 32] + // block_commitments: + [0; 32], + // final_sapling_root: + [1; 32], + // undocumented. type: u64 + // sapling_tree_size: + 87, + // unix epoch, u64 + // time: + 1755284848, + // [u8; 32] + // nonce: + [0; 32], + // undocumented. type: Solution + // The Equihash solution in the requested block header. + // solution: + Solution::Common([0; 1344]), + // type: CompactDifficulty + // from_hex() also possible , for example = "1d00ffff" + // bits: + CompactDifficulty::from_bytes_in_display_order(&[1; 4]).unwrap(), + // type: f64 + // difficulty: + 1.123, + // type: Hash + // previous_block_hash: + Hash::from([1; 32]), + // type: Option + // next_block_hash: + Some(Hash::from([2; 32])), + )) + })) + } + */ + async fn get_difficulty(&self) -> Result { chain_tip_difficulty( self.config.network.to_zebra_network(), @@ -1085,6 +1339,8 @@ impl ZcashIndexer for StateServiceSubscriber { .map_err(Into::into) } + // using get_block_inner using get_block_header + // also not an RPC call as far as I can tell. async fn z_get_block( &self, hash_or_height_string: String, @@ -1392,7 +1648,7 @@ impl ZcashIndexer for StateServiceSubscriber { Some(tx.confirmations), &self.config.network.into(), Some(tx.block_time), - Some(zebra_chain::block::Hash::from_bytes( + Some(Hash::from_bytes( self.block_cache .get_compact_block( HashOrHeight::Height(tx.height).to_string(), diff --git a/zaino-state/src/indexer.rs b/zaino-state/src/indexer.rs index dabe5ea5a..25bf84236 100644 --- a/zaino-state/src/indexer.rs +++ b/zaino-state/src/indexer.rs @@ -18,14 +18,16 @@ use zaino_proto::proto::{ TxFilter, }, }; -use zebra_chain::{block::Height, subtree::NoteCommitmentSubtreeIndex}; +use zebra_chain::{block::Height, parameters::Network, subtree::NoteCommitmentSubtreeIndex}; use zebra_rpc::{ client::{GetSubtreesByIndexResponse, GetTreestateResponse, ValidateAddressResponse}, methods::{ AddressBalance, AddressStrings, GetAddressTxIdsRequest, GetAddressUtxos, GetBlock, - GetBlockHash, GetBlockchainInfoResponse, GetInfo, GetRawTransaction, SentTransactionHash, + GetBlockHash, GetBlockHeaderResponse, GetBlockchainInfoResponse, GetInfo, + GetRawTransaction, SentTransactionHash, }, }; +use zebra_state::{HashOrHeight, ReadStateService}; use crate::{ status::StatusType, @@ -169,6 +171,33 @@ pub trait ZcashIndexer: Send + Sync + 'static { /// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L72-L89) async fn get_blockchain_info(&self) -> Result; + /// getblockheader 'hash' ( verbose ) + /// If verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'. + /// If verbose is true, returns an Object with information about blockheader 'hash'. + /// + /// zcashd web reference: [`getblockheader`](https://zcash.github.io/rpc/getblockheader.html) + /// method: post + /// tags: blockchain + /// + /// # Notes + /// + /// zebra includes several fields not in the zcashd web reference. + // TODO Check both zebra and zcashd source + // TODO add links to original implementation + async fn get_block_header( + &self, + hash_or_height: HashOrHeight, + verbose: Option, + ) -> Result; + + /// a shim to accomdate existing logic using Static methods + async fn get_block_header_static( + state: &ReadStateService, + network: &Network, + hash_or_height: HashOrHeight, + verbose: Option, + ) -> Result; + /// Returns the proof-of-work difficulty as a multiple of the minimum difficulty. /// /// zcashd reference: [`getdifficulty`](https://zcash.github.io/rpc/getdifficulty.html)