@@ -37,7 +37,9 @@ use zebra_node_services::mempool;
3737use zebra_state:: { HashOrHeight , MinedTx , OutputIndex , OutputLocation , TransactionLocation } ;
3838
3939use crate :: {
40- constants:: { INVALID_PARAMETERS_ERROR_CODE , MISSING_BLOCK_ERROR_CODE } ,
40+ constants:: {
41+ INVALID_ADDRESS_OR_KEY_ERROR_CODE , INVALID_PARAMETERS_ERROR_CODE , MISSING_BLOCK_ERROR_CODE ,
42+ } ,
4143 methods:: trees:: { GetSubtrees , GetTreestate , SubtreeRpcData } ,
4244 queue:: Queue ,
4345} ;
@@ -145,7 +147,8 @@ pub trait Rpc {
145147
146148 /// Returns the requested block by hash or height, as a [`GetBlock`] JSON string.
147149 /// If the block is not in Zebra's state, returns
148- /// [error code `-8`.](https://github.com/zcash/zcash/issues/5758)
150+ /// [error code `-8`.](https://github.com/zcash/zcash/issues/5758) if a height was
151+ /// passed or -5 if a hash was passed.
149152 ///
150153 /// zcashd reference: [`getblock`](https://zcash.github.io/rpc/getblock.html)
151154 /// method: post
@@ -154,16 +157,19 @@ pub trait Rpc {
154157 /// # Parameters
155158 ///
156159 /// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned.
157- /// - `verbosity`: (number, optional, default=1, example=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data.
160+ /// - `verbosity`: (number, optional, default=1, example=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data, and 3 for a partially filled json object (which is faster and useful for lightwalletd-only usage)
158161 ///
159162 /// # Notes
160163 ///
161- /// With verbosity=1, [`lightwalletd` only reads the `tx` field of the
162- /// result](https://github.com/zcash/lightwalletd/blob/dfac02093d85fb31fb9a8475b884dd6abca966c7/common/common.go#L152),
163- /// and other clients only read the `hash` and `confirmations` fields,
164- /// so we only return a few fields for now.
164+ /// Zebra previously partially supported verbosity=1 by returning only the
165+ /// fields required by lightwalletd ([`lightwalletd` only reads the `tx`
166+ /// field of the
167+ /// result](https://github.com/zcash/lightwalletd/blob/dfac02093d85fb31fb9a8475b884dd6abca966c7/common/common.go#L152)).
168+ /// That verbosity level was migrated to "3"; so while lightwalletd will
169+ /// still work by using verbosity=1, it will sync faster if it is changed to
170+ /// use verbosity=3.
165171 ///
166- /// `lightwalletd` and mining clients also do not use verbosity=2, so we don't support it .
172+ /// The undocumented `chainwork` field is not returned .
167173 #[ rpc( name = "getblock" ) ]
168174 fn get_block (
169175 & self ,
@@ -172,6 +178,9 @@ pub trait Rpc {
172178 ) -> BoxFuture < Result < GetBlock > > ;
173179
174180 /// Returns the requested block header by hash or height, as a [`GetBlockHeader`] JSON string.
181+ /// If the block is not in Zebra's state,
182+ /// returns [error code `-8`.](https://github.com/zcash/zcash/issues/5758)
183+ /// if a height was passed or -5 if a hash was passed.
175184 ///
176185 /// zcashd reference: [`getblockheader`](https://zcash.github.io/rpc/getblockheader.html)
177186 /// method: post
@@ -181,6 +190,10 @@ pub trait Rpc {
181190 ///
182191 /// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned.
183192 /// - `verbose`: (bool, optional, default=false, example=true) false for hex encoded data, true for a json object
193+ ///
194+ /// # Notes
195+ ///
196+ /// The undocumented `chainwork` field is not returned.
184197 #[ rpc( name = "getblockheader" ) ]
185198 fn get_block_header (
186199 & self ,
@@ -738,7 +751,9 @@ where
738751
739752 let mut state = self . state . clone ( ) ;
740753 let verbosity = verbosity. unwrap_or ( DEFAULT_GETBLOCK_VERBOSITY ) ;
754+ let self_clone = self . clone ( ) ;
741755
756+ let original_hash_or_height = hash_or_height. clone ( ) ;
742757 async move {
743758 let hash_or_height: HashOrHeight = hash_or_height. parse ( ) . map_server_error ( ) ?;
744759
@@ -766,6 +781,99 @@ where
766781 _ => unreachable ! ( "unmatched response to a block request" ) ,
767782 }
768783 } else if verbosity == 1 || verbosity == 2 {
784+ let r: Result < GetBlockHeader > = self_clone
785+ . get_block_header ( original_hash_or_height, Some ( true ) )
786+ . await ;
787+
788+ let GetBlockHeader :: Object ( h) = r? else {
789+ panic ! ( "must return Object" )
790+ } ;
791+ let hash = h. hash . 0 ;
792+
793+ // # Concurrency
794+ //
795+ // We look up by block hash so the hash, transaction IDs, and confirmations
796+ // are consistent.
797+ let requests = vec ! [
798+ // Get transaction IDs from the transaction index by block hash
799+ //
800+ // # Concurrency
801+ //
802+ // A block's transaction IDs are never modified, so all possible responses are
803+ // valid. Clients that query block heights must be able to handle chain forks,
804+ // including getting transaction IDs from any chain fork.
805+ zebra_state:: ReadRequest :: TransactionIdsForBlock ( hash. into( ) ) ,
806+ // Sapling trees
807+ zebra_state:: ReadRequest :: SaplingTree ( hash. into( ) ) ,
808+ // Orchard trees
809+ zebra_state:: ReadRequest :: OrchardTree ( hash. into( ) ) ,
810+ ] ;
811+
812+ let mut futs = FuturesOrdered :: new ( ) ;
813+
814+ for request in requests {
815+ futs. push_back ( state. clone ( ) . oneshot ( request) ) ;
816+ }
817+
818+ let tx_ids_response = futs. next ( ) . await . expect ( "`futs` should not be empty" ) ;
819+ let tx = match tx_ids_response. map_server_error ( ) ? {
820+ zebra_state:: ReadResponse :: TransactionIdsForBlock ( tx_ids) => tx_ids
821+ . ok_or_server_error ( "Block not found" ) ?
822+ . iter ( )
823+ . map ( |tx_id| tx_id. encode_hex ( ) )
824+ . collect ( ) ,
825+ _ => unreachable ! ( "unmatched response to a transaction_ids_for_block request" ) ,
826+ } ;
827+
828+ let sapling_tree_response = futs. next ( ) . await . expect ( "`futs` should not be empty" ) ;
829+ let sapling_note_commitment_tree_count =
830+ match sapling_tree_response. map_server_error ( ) ? {
831+ zebra_state:: ReadResponse :: SaplingTree ( Some ( nct) ) => nct. count ( ) ,
832+ zebra_state:: ReadResponse :: SaplingTree ( None ) => 0 ,
833+ _ => unreachable ! ( "unmatched response to a SaplingTree request" ) ,
834+ } ;
835+
836+ let orchard_tree_response = futs. next ( ) . await . expect ( "`futs` should not be empty" ) ;
837+ let orchard_note_commitment_tree_count =
838+ match orchard_tree_response. map_server_error ( ) ? {
839+ zebra_state:: ReadResponse :: OrchardTree ( Some ( nct) ) => nct. count ( ) ,
840+ zebra_state:: ReadResponse :: OrchardTree ( None ) => 0 ,
841+ _ => unreachable ! ( "unmatched response to a OrchardTree request" ) ,
842+ } ;
843+
844+ let sapling = SaplingTrees {
845+ size : sapling_note_commitment_tree_count,
846+ } ;
847+
848+ let orchard = OrchardTrees {
849+ size : orchard_note_commitment_tree_count,
850+ } ;
851+
852+ let trees = GetBlockTrees { sapling, orchard } ;
853+
854+ Ok ( GetBlock :: Object {
855+ hash : h. hash ,
856+ confirmations : h. confirmations ,
857+ height : Some ( h. height ) ,
858+ version : Some ( h. version ) ,
859+ merkle_root : Some ( h. merkle_root ) ,
860+ time : Some ( h. time ) ,
861+ nonce : Some ( h. nonce ) ,
862+ solution : Some ( h. solution ) ,
863+ bits : Some ( h. bits ) ,
864+ difficulty : Some ( h. difficulty ) ,
865+ // TODO
866+ tx,
867+ trees,
868+ // TODO
869+ size : None ,
870+ final_sapling_root : Some ( h. final_sapling_root ) ,
871+ // TODO
872+ final_orchard_root : None ,
873+ previous_block_hash : Some ( h. previous_block_hash ) ,
874+ next_block_hash : h. next_block_hash ,
875+ } )
876+ } else if verbosity == 3 {
769877 // # Performance
770878 //
771879 // This RPC is used in `lightwalletd`'s initial sync of 2 million blocks,
@@ -920,6 +1028,17 @@ where
9201028 time,
9211029 tx,
9221030 trees,
1031+ size : None ,
1032+ version : None ,
1033+ merkle_root : None ,
1034+ final_sapling_root : None ,
1035+ final_orchard_root : None ,
1036+ nonce : None ,
1037+ bits : None ,
1038+ difficulty : None ,
1039+ previous_block_hash : None ,
1040+ next_block_hash : None ,
1041+ solution : None ,
9231042 } )
9241043 } else {
9251044 Err ( Error {
@@ -952,7 +1071,18 @@ where
9521071 . clone ( )
9531072 . oneshot ( zebra_state:: ReadRequest :: BlockHeader ( hash_or_height) )
9541073 . await
955- . map_server_error ( ) ?
1074+ . map_err ( |_| Error {
1075+ // Compatibility with zcashd. Note that since this function
1076+ // is reused by getblock(), we return the errors expected
1077+ // by it (they differ whether a hash or a height was passed)
1078+ code : if hash_or_height. hash ( ) . is_some ( ) {
1079+ INVALID_ADDRESS_OR_KEY_ERROR_CODE
1080+ } else {
1081+ MISSING_BLOCK_ERROR_CODE
1082+ } ,
1083+ message : "block height not in best chain" . to_string ( ) ,
1084+ data : None ,
1085+ } ) ?
9561086 else {
9571087 panic ! ( "unexpected response to BlockHeader request" )
9581088 } ;
@@ -1688,8 +1818,9 @@ impl Default for SentTransactionHash {
16881818/// Response to a `getblock` RPC request.
16891819///
16901820/// See the notes for the [`Rpc::get_block`] method.
1691- #[ derive( Clone , Debug , Eq , PartialEq , serde:: Serialize ) ]
1821+ #[ derive( Clone , Debug , PartialEq , serde:: Serialize ) ]
16921822#[ serde( untagged) ]
1823+ #[ allow( clippy:: large_enum_variant) ] //TODO: create a struct for the Object and Box it
16931824pub enum GetBlock {
16941825 /// The request block, hex-encoded.
16951826 Raw ( #[ serde( with = "hex" ) ] SerializedBlock ) ,
@@ -1702,21 +1833,84 @@ pub enum GetBlock {
17021833 /// or -1 if it is not in the best chain.
17031834 confirmations : i64 ,
17041835
1836+ /// The block size. TODO: fill it
1837+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
1838+ size : Option < i64 > ,
1839+
17051840 /// The height of the requested block.
17061841 #[ serde( skip_serializing_if = "Option::is_none" ) ]
17071842 height : Option < Height > ,
17081843
1709- /// The height of the requested block.
1844+ /// The version field of the requested block.
17101845 #[ serde( skip_serializing_if = "Option::is_none" ) ]
1711- time : Option < i64 > ,
1846+ version : Option < u32 > ,
17121847
1848+ /// The merkle root of the requesteed block.
1849+ #[ serde( with = "opthex" , rename = "merkleroot" ) ]
1850+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
1851+ merkle_root : Option < block:: merkle:: Root > ,
1852+
1853+ // `blockcommitments` would be here. Undocumented. TODO: decide if we want to support it
1854+ // `authdataroot` would be here. Undocumented. TODO: decide if we want to support it
1855+ //
1856+ /// The root of the Sapling commitment tree after applying this block.
1857+ #[ serde( with = "opthex" , rename = "finalsaplingroot" ) ]
1858+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
1859+ final_sapling_root : Option < [ u8 ; 32 ] > ,
1860+
1861+ /// The root of the Orchard commitment tree after applying this block.
1862+ #[ serde( with = "opthex" , rename = "finalorchardroot" ) ]
1863+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
1864+ final_orchard_root : Option < [ u8 ; 32 ] > ,
1865+
1866+ // `chainhistoryroot` would be here. Undocumented. TODO: decide if we want to support it
1867+ //
17131868 /// List of transaction IDs in block order, hex-encoded.
17141869 //
17151870 // TODO: use a typed Vec<transaction::Hash> here
1871+ // TODO: support Objects
17161872 tx : Vec < String > ,
17171873
1874+ /// The height of the requested block.
1875+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
1876+ time : Option < i64 > ,
1877+
1878+ /// The nonce of the requested block header.
1879+ #[ serde( with = "opthex" ) ]
1880+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
1881+ nonce : Option < [ u8 ; 32 ] > ,
1882+
1883+ /// The Equihash solution in the requested block header.
1884+ /// Note: presence of this field in getblock is not documented in zcashd.
1885+ #[ serde( with = "opthex" ) ]
1886+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
1887+ solution : Option < Solution > ,
1888+
1889+ /// The difficulty threshold of the requested block header displayed in compact form.
1890+ #[ serde( with = "opthex" ) ]
1891+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
1892+ bits : Option < CompactDifficulty > ,
1893+
1894+ /// Floating point number that represents the difficulty limit for this block as a multiple
1895+ /// of the minimum difficulty for the network.
1896+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
1897+ difficulty : Option < f64 > ,
1898+
1899+ // `chainwork` would be here, but we don't plan on supporting it
1900+ // `anchor` would be here. Undocumented. TODO: decide if we want to support it
1901+ // `chainSupply` would be here, TODO: implement
1902+ // `valuePools` would be here, TODO: implement
1903+ //
17181904 /// Information about the note commitment trees.
17191905 trees : GetBlockTrees ,
1906+
1907+ /// The previous block hash of the requested block header.
1908+ #[ serde( rename = "previousblockhash" , skip_serializing_if = "Option::is_none" ) ]
1909+ previous_block_hash : Option < GetBlockHash > ,
1910+
1911+ /// The next block hash after the requested block header.
1912+ #[ serde( rename = "nextblockhash" , skip_serializing_if = "Option::is_none" ) ]
1913+ next_block_hash : Option < GetBlockHash > ,
17201914 } ,
17211915}
17221916
@@ -1729,6 +1923,17 @@ impl Default for GetBlock {
17291923 time : None ,
17301924 tx : Vec :: new ( ) ,
17311925 trees : GetBlockTrees :: default ( ) ,
1926+ size : None ,
1927+ version : None ,
1928+ merkle_root : None ,
1929+ final_sapling_root : None ,
1930+ final_orchard_root : None ,
1931+ nonce : None ,
1932+ bits : None ,
1933+ difficulty : None ,
1934+ previous_block_hash : None ,
1935+ next_block_hash : None ,
1936+ solution : None ,
17321937 }
17331938 }
17341939}
@@ -2088,3 +2293,22 @@ pub fn height_from_signed_int(index: i32, tip_height: Height) -> Result<Height>
20882293 Ok ( Height ( sanitized_height) )
20892294 }
20902295}
2296+
2297+ mod opthex {
2298+ use hex:: ToHex ;
2299+ use serde:: Serializer ;
2300+
2301+ pub fn serialize < S , T > ( data : & Option < T > , serializer : S ) -> Result < S :: Ok , S :: Error >
2302+ where
2303+ S : Serializer ,
2304+ T : ToHex ,
2305+ {
2306+ match data {
2307+ Some ( data) => {
2308+ let s = data. encode_hex :: < String > ( ) ;
2309+ serializer. serialize_str ( & s)
2310+ }
2311+ None => serializer. serialize_none ( ) ,
2312+ }
2313+ }
2314+ }
0 commit comments