@@ -37,7 +37,9 @@ use zebra_node_services::mempool;
37
37
use zebra_state:: { HashOrHeight , MinedTx , OutputIndex , OutputLocation , TransactionLocation } ;
38
38
39
39
use 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
+ } ,
41
43
methods:: trees:: { GetSubtrees , GetTreestate , SubtreeRpcData } ,
42
44
queue:: Queue ,
43
45
} ;
@@ -145,7 +147,8 @@ pub trait Rpc {
145
147
146
148
/// Returns the requested block by hash or height, as a [`GetBlock`] JSON string.
147
149
/// 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.
149
152
///
150
153
/// zcashd reference: [`getblock`](https://zcash.github.io/rpc/getblock.html)
151
154
/// method: post
@@ -154,16 +157,19 @@ pub trait Rpc {
154
157
/// # Parameters
155
158
///
156
159
/// - `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)
158
161
///
159
162
/// # Notes
160
163
///
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.
165
171
///
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 .
167
173
#[ rpc( name = "getblock" ) ]
168
174
fn get_block (
169
175
& self ,
@@ -172,6 +178,9 @@ pub trait Rpc {
172
178
) -> BoxFuture < Result < GetBlock > > ;
173
179
174
180
/// 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.
175
184
///
176
185
/// zcashd reference: [`getblockheader`](https://zcash.github.io/rpc/getblockheader.html)
177
186
/// method: post
@@ -181,6 +190,10 @@ pub trait Rpc {
181
190
///
182
191
/// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned.
183
192
/// - `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.
184
197
#[ rpc( name = "getblockheader" ) ]
185
198
fn get_block_header (
186
199
& self ,
@@ -738,7 +751,9 @@ where
738
751
739
752
let mut state = self . state . clone ( ) ;
740
753
let verbosity = verbosity. unwrap_or ( DEFAULT_GETBLOCK_VERBOSITY ) ;
754
+ let self_clone = self . clone ( ) ;
741
755
756
+ let original_hash_or_height = hash_or_height. clone ( ) ;
742
757
async move {
743
758
let hash_or_height: HashOrHeight = hash_or_height. parse ( ) . map_server_error ( ) ?;
744
759
@@ -766,6 +781,99 @@ where
766
781
_ => unreachable ! ( "unmatched response to a block request" ) ,
767
782
}
768
783
} 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 {
769
877
// # Performance
770
878
//
771
879
// This RPC is used in `lightwalletd`'s initial sync of 2 million blocks,
@@ -920,6 +1028,17 @@ where
920
1028
time,
921
1029
tx,
922
1030
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 ,
923
1042
} )
924
1043
} else {
925
1044
Err ( Error {
@@ -952,7 +1071,18 @@ where
952
1071
. clone ( )
953
1072
. oneshot ( zebra_state:: ReadRequest :: BlockHeader ( hash_or_height) )
954
1073
. 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
+ } ) ?
956
1086
else {
957
1087
panic ! ( "unexpected response to BlockHeader request" )
958
1088
} ;
@@ -1688,8 +1818,9 @@ impl Default for SentTransactionHash {
1688
1818
/// Response to a `getblock` RPC request.
1689
1819
///
1690
1820
/// See the notes for the [`Rpc::get_block`] method.
1691
- #[ derive( Clone , Debug , Eq , PartialEq , serde:: Serialize ) ]
1821
+ #[ derive( Clone , Debug , PartialEq , serde:: Serialize ) ]
1692
1822
#[ serde( untagged) ]
1823
+ #[ allow( clippy:: large_enum_variant) ] //TODO: create a struct for the Object and Box it
1693
1824
pub enum GetBlock {
1694
1825
/// The request block, hex-encoded.
1695
1826
Raw ( #[ serde( with = "hex" ) ] SerializedBlock ) ,
@@ -1702,21 +1833,84 @@ pub enum GetBlock {
1702
1833
/// or -1 if it is not in the best chain.
1703
1834
confirmations : i64 ,
1704
1835
1836
+ /// The block size. TODO: fill it
1837
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
1838
+ size : Option < i64 > ,
1839
+
1705
1840
/// The height of the requested block.
1706
1841
#[ serde( skip_serializing_if = "Option::is_none" ) ]
1707
1842
height : Option < Height > ,
1708
1843
1709
- /// The height of the requested block.
1844
+ /// The version field of the requested block.
1710
1845
#[ serde( skip_serializing_if = "Option::is_none" ) ]
1711
- time : Option < i64 > ,
1846
+ version : Option < u32 > ,
1712
1847
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
+ //
1713
1868
/// List of transaction IDs in block order, hex-encoded.
1714
1869
//
1715
1870
// TODO: use a typed Vec<transaction::Hash> here
1871
+ // TODO: support Objects
1716
1872
tx : Vec < String > ,
1717
1873
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
+ //
1718
1904
/// Information about the note commitment trees.
1719
1905
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 > ,
1720
1914
} ,
1721
1915
}
1722
1916
@@ -1729,6 +1923,17 @@ impl Default for GetBlock {
1729
1923
time : None ,
1730
1924
tx : Vec :: new ( ) ,
1731
1925
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 ,
1732
1937
}
1733
1938
}
1734
1939
}
@@ -2088,3 +2293,22 @@ pub fn height_from_signed_int(index: i32, tip_height: Height) -> Result<Height>
2088
2293
Ok ( Height ( sanitized_height) )
2089
2294
}
2090
2295
}
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